Extending uniq method - ruby

This is Ruby 1.8 Question:
We all know how to use Array#uniq :
[1,2,3,1].uniq #=> [1,2,3]
However I'm wondering if we can monkey patch it in a way to work with complex objects. The current behavior is like this:
[{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].uniq
#=> [{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}]
The requested one is:
[{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].uniq
#=> [{"three"=>"3"}, {"three"=>"4"}]

To make Array#uniq work for any object you must override two methods: hash and eql?
All objects have a hash method which calculates the hash value for that object, so for two objects to be equal their values when hashed must also be equal.
Example--a user is unique when their email address is unique:
class User
attr_accessor :name,:email
def hash
#email.hash
end
def eql?(o)
#email == o.email
end
end
>> [User.new('Erin Smith','roo#example.com'),User.new('E. Smith','roo#example.com')].uniq
=> [#<User:0x1015a97e8 #name="Erin Smith", #email="maynurd#example.com"]

It already works for me in 1.8.7.
1:~$ ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [i486-linux]
1:~$ irb -v
irb 0.9.5(05/04/13)
1:~$ irb
>> [{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].uniq
=> [{"three"=>"3"}, {"three"=>"4"}]

The problem is that Hash#hash and Hash#eql? both give bogus results in Ruby 1.8.6. This is one of the very rare monkey patches I've been willing to perform, because this bug seriously breaks a lot of code — in particular memoizing functions. Just be careful with monkey patches that you don't override non-broken behavior.
So:
class Hash
if {}.hash != {}.hash
def hash
# code goes here
end
end
if !{}.eql?({})
def eql?(other)
# code goes here
end
end
end
But if you're doing something where you control the deploy environment, just raise an error if the app gets started with 1.8.6.

How about this?
h={}
[{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].select {|e| need=!h.key?(e) ; h[e]=1 ; need}
#=> [{"three"=>"3"}, {"three"=>"4"}]

I've run into this myself many times. Hash equality in Ruby 1.8.6 is broken:
require 'test/unit'
class TestHashEquality < Test::Unit::TestCase
def test_that_an_empty_Hash_is_equal_to_another_empty_Hash
assert({}.eql?({}), 'Empty Hashes should be eql.')
end
end
Passes in Ruby 1.9 and Ruby 1.8.7, fails in Ruby 1.8.6.

1.8.7 :039 > [{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].uniq {|x|x.values}
=> [{"three"=>"3"}, {"three"=>"4"}]
1.8.7 :040 > [{"three"=>"3"}, {"three"=>"4"}, {"three"=>"3"}].uniq {|x|x.keys}
=> [{"three"=>"3"}]
How about something like that? just uniq_by the hash value or hash key via the block.

Related

Ruby Script: undefined method `symbolize_keys' error loading YAML files

I have a ruby script for yaml merging as follows
#!/usr/bin/env ruby
require 'yaml'
raise "wrong number of parameters" unless ARGV.length == 2
y1 = YAML.load_file(ARGV[0]).symbolize_keys
y2 = YAML.load_file(ARGV[1]).symbolize_keys
puts y1.merge!(y2).to_yaml
when I execute it:
./test.rb ./src/api/config/config1.yml ./src/api/config/config2.yml
I've got the following error:
./test.rb:5:in `<main>': undefined method `symbolize_keys' for {"root"=>{"cloud.n2"=>{"accessKey"=>"I5VAJUYNR4AAKIZDH777"}}}:Hash (NoMethodError)
Hash#symbolize_keys method comes from activesupport gem (activesupport/lib/active_support/core_ext/hash/keys.rb).
In order to use it, you need to add the following line to your script:
require "active_support"
While the other answers/comments are correct it seems like overkill to require all of ActiveSupport for this. Instead either use:
require 'active_support/core_ext/hash/keys'
Or if you have control over the yml files then just make the keys symbols there and avoid any transformation. For Example
require 'yaml'
yml = <<YML
:root:
:cloud.n2:
:accessKey: "I5VAJUYNR4AAKIZDH777"
YML
YAML.load(yml)
#=> {:root=>{:"cloud.n2"=>{:accessKey=>"I5VAJUYNR4AAKIZDH777"}}}
This does not really the answer your question, but Ruby 2.5.0 introduced Hash#transform_keys (release notes) which also can be used to symbolize keys and is in core Ruby.
{'a' => 1, 'b' => 2}.transform_keys(&:to_sym)
#=> {:a=>1, :b=>2}
There is also a bang version which mutates the hash instead of creating a new one.
As other have already noted, symbolize_keys is an ActiveSupport method. If you are not using ActiveSupport, and/or on a pre-2.5 version of Ruby that does not include transform_keys, you could define it yourself.
class Hash
def transform_keys
return enum_for(:transform_keys) unless block_given?
result = self.class.new
each_key do |key|
result[yield(key)] = self[key]
end
result
end
def transform_keys!
return enum_for(:transform_keys!) unless block_given?
keys.each do |key|
self[yield(key)] = delete(key)
end
self
end
def symbolize_keys
transform_keys{ |key| key.to_sym rescue key }
end
def symbolize_keys!
transform_keys!{ |key| key.to_sym rescue key }
end
end
This is not to say that there are not likely other dependencies on Rails or ActiveSupport that will be required for your script.

How to use let variables in rails console?

using
rspec 2.6.4
rails 3.1.6
How to use let variables in rails test console?
1.9.3-p0 :032 > let(:user) { create(:user) }
NoMethodError: undefined method `let' for main:Object
Please advise, which library should be required here?
For example: below is executed in console to use stub methods in console.
require 'rspec/mocks/standalone'
Is it possible, to define and call let variables in rails console?
If you are fine with let just creating globals, you can polyfill it like this:
def let(name)
Object.send :instance_variable_set, "##{name}", yield
Object.send :define_method, name do
Object.send :instance_variable_get, "##{name}"
end
end
Usage is the same as rspec:
irb(main):007:0> let(:foo) { 1 }
=> :foo
irb(main):008:0> foo
=> 1
though you really shouldn't be pasting your test code into console to debug it. It's much better to use a breakpoint tool like pry or byebug.
let in rspec is not much more than a lazily executed and memoized method definition. If you must have in the irb you could define it like this:
$ cat let.rb
def let(sym)
$let ||= {}
define_method sym do
$let[sym] ||= yield
end
end
require './let in irb or place it in .irbrc and you have your rspec-like let. Note, that rspec reevaluates let in each new example (it or specify block). Since you don't have them in irb you may need to clear your let cache manually ($let = {}) to force re-evaluation.

Responding to one method but not to other in the same scope

With the following code
class User < ActiveRecord::Base
# ...
# code filtered from here
# ...
# both methods below are public methods
def humanized_roles
roles.collect {|e| e.name.humanize }.join(', ')
end
def role=( arg = nil)
self.roles = []
self.add_role arg.to_sym
end
end
This is happening
User.new.respond_to? :humanized_roles
# => false
User.new.respond_to? "role=".to_sym
# => true
rvm, ruby, rails versions
rvm 1.26.3
ruby 1.9.3p448 (2013-06-27 revision 41675) [x86_64-linux]
Rails 3.2.14
Am I missing something obvious?
In the interest of product release, I circumvented this issue and used a helper method to accept user object as parameter, instead of user.humanize_roles
Thank you everyone for your valuable time & responses.

How do I use an ActionView::Helper in a Ruby script, outside of Rails?

I am looking to use ActionView::Helpers::NumberHelper from a Ruby script.
What all do I need to require etc.?
~> irb
ruby-1.9.2-p180 :001 > require 'action_view'
=> true
ruby-1.9.2-p180 :002 > ActionView::Base.new.number_to_currency 43
=> "$43.00"
As of Rails 3.2.13, you can do the following:
class MyClass
include ActionView::Helpers::NumberHelper
def my_method
...
number_with_precision(number, precision: 2)
...
end
end
You might need to require 'action_view' too.
Edit: This answer is still valid in Rails 4.2.3.

Printing the source code of a Ruby block

I have a method that takes a block.
Obviously I don't know what is going to be passed in and for bizarre reasons that I won't go into here I want to print the contents of the block.
Is there a way to do this?
You can do this with Ruby2Ruby which implements a to_ruby method.
require 'rubygems'
require 'parse_tree'
require 'parse_tree_extensions'
require 'ruby2ruby'
def meth &block
puts block.to_ruby
end
meth { some code }
will output:
"proc { some(code) }"
I would also check out this awesome talk by Chris Wanstrath of Github http://goruco2008.confreaks.com/03_wanstrath.html He shows some interesting ruby2ruby and parsetree usage examples.
In Ruby 1.9+ (tested with 2.1.2), you can use https://github.com/banister/method_source
Print out the source via block#source:
#! /usr/bin/ruby
require 'rubygems'
require 'method_source'
def wait &block
puts "Running the following code: #{block.source}"
puts "Result: #{yield}"
puts "Done"
end
def run!
x = 6
wait { x == 5 }
wait { x == 6 }
end
run!
Note that in order for the source to be read you need to use a file and execute the file (testing it out from irb will result in the following error: MethodSource::SourceNotFoundError: Could not load source for : No such file or directory # rb_sysopen - (irb)
Building on Evangenieur's answer, here's Corban's answer if you had Ruby 1.9:
# Works with Ruby 1.9
require 'sourcify'
def meth &block
# Note it's to_source, not to_ruby
puts block.to_source
end
meth { some code }
My company uses this to display the Ruby code used to make carbon calculations... we used ParseTree with Ruby 1.8 and now sourcify with Ruby 1.9.
In Ruby 1.9, you can try this gem which extract the code from source file.
https://github.com/ngty/sourcify
In Ruby 2.5 the following works
puts block.source
In ruby 2.7, using the method_source gem (pry depends on it)
Set.instance_method(:merge).source.display
# =>
def merge(enum)
if enum.instance_of?(self.class)
#hash.update(enum.instance_variable_get(:#hash))
else
do_with_enum(enum) { |o| add(o) }
end
self
end
The repo says it works for procs, but I haven't tested it.

Resources