eql? override doesn't work in Hash - ruby

Hash checks its keys with eql?:
foo = 'str'
bar = 'str'
foo.equal?(bar) #=> false
foo.eql?(bar) #=> true
h = { foo => 1 }
h[foo] #=> 1
h[bar] #=> 1
But this doesn't work if I use my own class as a key:
class Cl
attr_reader :a
def initialize(a)
#a = a
end
def eql?(obj)
#a == obj.a
end
end
foo = Cl.new(10)
bar = Cl.new(10)
foo.equal?(bar) #=> false
foo.eql?(bar) #=> true
h = { foo => 1 }
h[foo] #=> 1
h[bar] #=> nil
Why does the last line return nil instead of 1?

eql? must be used in conjunction with a hash method that returns a hash code:
class Cl
attr_reader :a
def initialize(a)
#a = a
end
def eql?(obj)
#a == obj.a
end
def hash
#a
end
end
See this blog post.

Related

Automatically generate 'attr_reader' for symbols

I have the following code where I set attr_reader and attr_writer manually.
class Pairs
attr_reader :pair, :asks, :bids, :isFrozen, :seq, :main_currency, :sub_currency
attr_writer :pair, :asks, :bids, :isFrozen, :seq
def initialize (key, args)
#pair = key
#main_currency, #sub_currency = key.split('_')
args.each {|k,v|
if numeric?(v) then v=v.to_f end
self.instance_variable_set("##{k}".to_sym, v)
}
end
private
def numeric?(string)
Float(string) != nil rescue false
end
end
Is there a way to automatically set them based on the keys of the arguments, like I'm automatically filling #k with v? Can I set attr_reader for each #k?
I suppose something like:
self.attr_reader("##{k}")
or even better for all objects of the class, something like:
Pairs << attr_reader("##{k}")
I am going to assume that you may be creating this with many keys specific to different Hash if this is the case then rather than clutter the individual instances with unneeded readers for non existent keys let's use the singleton_class for this.
So your final Pairs class could look something like
class Pairs
attr_reader :main_currency, :sub_currency
attr_accessor :pair, :asks, :bids, :isFrozen, :seq
def initialize (key, args)
#pair = key
#main_currency, #sub_currency = key.split('_')
args.each do |k,v|
singleton_class.send(:attr_reader,k)
instance_variable_set("##{k}", convert_numeric(v))
end
# Alternatively:
# args.each do |k,v|
# val = convert_numeric(v)
# define_singleton_method(k) {val}
# end
end
private
def convert_numeric(val)
Float(Rational(val)) rescue val
end
end
TL;DR
For Example: (using #mudasobwa's approach)
class C
def extend_self_with_reader name
self.class.send :attr_reader, name
end
def initialize *keys
keys.each(&method(:extend_self_with_reader))
end
end
This causes subsequent readers to clutter the instance and bleed across instances:
a = C.new(:a,:b)
a.a #=> nil
b = C.new
b.a #=> nil
c = C.new(:r)
c.a #=> nil
c.r #=> nil
a.methods.sort - Object.methods
#=> [:a, :b, :extend_self_with_reader, :r]
a.r #=> nil (hmmmmm)
Instead localize these readers buy using the singleton_class of the instance like:
class C
def initialize *keys
singleton_class.send(:attr_reader, *keys)
end
end
Then
a = C.new(:a,:b)
a.a #=> nil
b = C.new
b.a #=> NoMethodError: undefined method `a'
c = C.new(:r)
c.a #=> NoMethodError: undefined method `a'
c.r #=> nil
a.r #=> NoMethodError: undefined method `r'
a.methods.sort - Object.methods
#=> [:a,:b]
b.methods.sort - Object.methods
#=> []
Using the singleton_class localizes these readers to the instance of the object rather than bleeding them into the Class definition. If attr_reader is not a requirement then this would also be sufficient:
keys.each {|k| define_singleton_method(k) {}}
I doubt I understood the question, but from what I get you want to dynamically extend your class with attribute readers at runtime.
This method would do:
def extend_self_with_reader name
self.class.send :attr_reader, name
end
Test:
class C
def extend_self_with_reader name
self.class.send :attr_reader, name
end
def initialize *keys
puts keys.inspect
keys.each(&method(:extend_self_with_reader))
end
end
cc = C.new(*%i|a b c|)
cc.a #⇒ nil
Perhaps look into define_method.
I'm not 100% sure that I understand the problem, but check this out:
hash = { a: 1, b: 2, c: 3 }
hash.keys.each do |key|
define_method(key) do
hash[key]
end
end
There are now methods for a, b, and c:
a => 1
b => 2
c => 3
That essentially makes an attr_reader for all the keys in the hash. You could do something similar for an attr_writer.

How to implement autovivification for Ruby structs?

Ruby has support for autovivification for hashes by passing a block to Hash.new:
hash = Hash.new { |h, k| h[k] = 42 }
hash[:foo] += 1 # => 43
I'd like to implement autovivification for structs, also. This is the best I can come up with:
Foo = Struct.new(:bar) do
def bar
self[:bar] ||= 42
end
end
foo = Foo.new
foo.bar += 1 # => 43
and of course, this only autovivifies the named accessor (foo.bar), not the [] form (foo[:bar]). Is there a better way to implement autovivification for structs, in particular one that works robustly for both the foo.bar and foo[:bar] forms?
I would go with the following approach:
module StructVivificator
def self.prepended(base)
base.send(:define_method, :default_proc) do |&λ|
instance_variable_set(:#λ, λ)
end
end
def [](name)
super || #λ && #λ.() # or more sophisticated checks
end
end
Foo = Struct.new(:bar) do
prepend StructVivificator
end
foo = Foo.new
foo.default_proc { 42 } # declare a `default_proc` as in Hash
foo[:bar] += 1 # => 43
foo.bar += 1 # => 44
foo.bar above calls foo[:bar] under the hood through method_missing magic, so the only thing to overwrite is a Struct#[] method.
Prepending a module makes it more robust, per-instance and in general more flexible.
The code above is just an example. To copy the behavior of Hash#default_proc one might (credits to #Stefan for comments):
module StructVivificator
def self.prepended(base)
raise 'Sorry, structs only!' unless base < Struct
base.singleton_class.prepend(Module.new do
def new(*args, &λ) # override `new` to accept block
super(*args).tap { #λ = λ }
end
end)
base.send(:define_method, :default_proc=) { |λ| #λ = λ }
base.send(:define_method, :default_proc) { |&λ| λ ? #λ = λ : #λ }
# override accessors (additional advantage: performance/clarity)
base.members.each do |m|
base.send(:define_method, m) { self[m] }
base.send(:define_method, "#{m}=") { |value| self[m] = value }
end
end
def [](name)
super || default_proc && default_proc.(name) # or more sophisticated checks
end
end
Now default_proc lambda will receive a name to decide how to behave in such a case.
Foo = Struct.new(:bar, :baz) do
prepend StructVivificator
end
foo = Foo.new
foo.default_proc = ->(name) { name == :bar ? 42 : 0 }
puts foo.bar # => 42
puts foo[:bar] += 1 # => 43
puts foo.bar += 1 # => 44
puts foo[:baz] += 1 # => 1

Conditionally call chained method

A chained method should only be called under certain circumstances in the following code.
class Klass
def foo
puts 'foo'
self
end
def bar
puts 'bar'
self
end
end
klass = Klass.new
a = 2
id = klass.foo{conditionally chain bar if a == 2}.bar
Can you insert an expression or method between chained methods that conditionally continues or halts the method chain?
This is simple and who will come after you will understand immediately:
klass = klass.foo
klass = klass.bar if a == 2
etc...
This works well if the chained methods take no arguments
klass.define_singleton_method :chain_if do |b, *s|
return unless b
klass = self
s.each do |x|
klass = klass.send x
end
klass
end
klass.foo.chain_if(true, :foo, :bar).chain_if(false, :bar)
Here some duplicated threads!
conditional chaining in ruby
Add method to an instanced object
Here I found another solution that I personally like:
my_object.tap{|o|o.method_a if a}.tap{|o|o.method_b if b}.tap{|o|o.method_c if c}
EDIT:
beware tap is defined as follows:
class Object
def tap
yield self
self
end
end
What you need might look like this, if the chained method returns a new immutable object:
class Object
def tap_and_chain
yield self
end
end
def chain_maybe(klass, condition, *args)
args[1..-1].reduce(klass.send(args.first)) { |r,m| (condition && r.send(m)) || r }
end
or:
def chain_maybe(klass, condition, *args)
first, *others = args
others = [] unless condition
others.reduce(klass.send(first)) { |r,m| r.send(m) }
end
For:
class Klass
def foo
puts 'foo'
self
end
def bar
puts 'bar'
self
end
def baz
puts 'baz'
self
end
end
c = Klass.new
chain_maybe(c, true, :foo, :bar, :baz)
foo
bar
baz
#=> #<Klass:0x007fccea8da388>
chain_maybe(c, false, :foo, :bar, :baz)
foo
#=> #<Klass:0x007fccea8da388>
chain_maybe(c, true, :foo, :bar)
foo
bar
#=> #<Klass:0x007fccea8da388>
chain_maybe(c, true, :bar, :baz)
bar
baz
#=> #<Klass:0x007fccea8da388>
If there is to be a condition for each argument, this can be generalized to:
def chain_maybe(klass, conditions, args)
return nil if conditions.empty?
first, *others = args.zip(conditions).select(&:last).map(&:first)
others.reduce(klass.send(first)) { |r,m| r.send(m) }
end
args = [:foo, :bar, :baz]
chain_maybe(c, [true, true, true], args)
foo
bar
baz
#=> #<Klass:0x007fccea8da388>
chain_maybe(c, [false, true, true], args)
bar
baz
#=> #<Klass:0x007fccea8da388>
chain_maybe(c, [true, false, true], args)
foo
baz
#=> #<Klass:0x007fccea8da388>
You can use tap if the block you are chaining is mutable, i.e: It will change the value of self (as explained here)
However, if you are chaining immutable blocks like QueryMethods, you can use try like:
data
.try { |d| group ? d.group(group) : d }
.try { |d| group ? d.order(order => :desc) : d }
try with nothing else but a block is essentially:
def try
yield self
end
(reference: #try)

Ruby.Metaprogramming. class_eval

There seem to be a mistake in my code. However I just can't find it out.
class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s
attr_reader attr_name
attr_writer attr_name
attr_reader attr_name + "_history"
class_eval %Q{
##{attr_name}_history=[1,2,3]
}
end
end
class Foo
attr_accessor_with_history :bar
end
f = Foo.new
f.bar = 1
f.bar = 2
puts f.bar_history.to_s
I would expect it to return an array [1,2,3]. However, it doesn't return anything.
You shouldn't be opening Class to add new methods. That's what modules are for.
module History
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s
attr_accessor attr_name
class_eval %Q{
def #{attr_name}_history
[1, 2, 3]
end
}
end
end
class Foo
extend History
attr_accessor_with_history :bar
end
f = Foo.new
f.bar = 1
f.bar = 2
puts f.bar_history.inspect
# [1, 2, 3]
And here's the code you probably meant to write (judging from the names).
module History
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s
class_eval %Q{
def #{attr_name}
##{attr_name}
end
def #{attr_name}= val
##{attr_name}_history ||= []
##{attr_name}_history << #{attr_name}
##{attr_name} = val
end
def #{attr_name}_history
##{attr_name}_history
end
}
end
end
class Foo
extend History
attr_accessor_with_history :bar
end
f = Foo.new
f.bar = 1
f.bar = 2
puts f.bar_history.inspect
# [nil, 1]
Solution:
class Class
def attr_accessor_with_history(attr_name)
ivar = "##{attr_name}"
history_meth = "#{attr_name}_history"
history_ivar = "##{history_meth}"
define_method(attr_name) { instance_variable_get ivar }
define_method "#{attr_name}=" do |value|
instance_variable_set ivar, value
instance_variable_set history_ivar, send(history_meth) << value
end
define_method history_meth do
value = instance_variable_get(history_ivar) || []
value.dup
end
end
end
Tests:
describe 'Class#attr_accessor_with_history' do
let(:klass) { Class.new { attr_accessor_with_history :bar } }
let(:instance) { instance = klass.new }
it 'acs as attr_accessor' do
instance.bar.should be_nil
instance.bar = 1
instance.bar.should == 1
instance.bar = 2
instance.bar.should == 2
end
it 'remembers history of setting' do
instance.bar_history.should == []
instance.bar = 1
instance.bar_history.should == [1]
instance.bar = 2
instance.bar_history.should == [1, 2]
end
it 'is not affected by mutating the history array' do
instance.bar_history << 1
instance.bar_history.should == []
instance.bar = 1
instance.bar_history << 2
instance.bar_history.should == [1]
end
end
You will find a solution for your problem in Sergios answer. Here an explanation, what's going wrong in your code.
With
class_eval %Q{
##{attr_name}_history=[1,2,3]
}
you execute
#bar_history = [1,2,3]
You execute this on class level, not in object level.
The variable #bar_history is not available in a Foo-object, but in the Foo-class.
With
puts f.bar_history.to_s
you access the -never on object level defined- attribute #bar_history.
When you define a reader on class level, you have access to your variable:
class << Foo
attr_reader :bar_history
end
p Foo.bar_history #-> [1, 2, 3]
#Sergio Tulentsev's answer works, but it promotes a problematic practice of using string eval which is in general fraught with security risks and other surprises when the inputs aren't what you expect. For example, what happens to Sergio's version if one calls (no don't try it):
attr_accessor_with_history %q{foo; end; system "rm -rf /"; def foo}
It is often possible to do ruby meta-programming more carefully without string eval. In this case, using simple interpolation and define_method of closures with instance_variable_[get|set], and send:
module History
def attr_accessor_with_history(attr_name)
getter_sym = :"#{attr_name}"
setter_sym = :"#{attr_name}="
history_sym = :"#{attr_name}_history"
iv_sym = :"##{attr_name}"
iv_hist = :"##{attr_name}_history"
define_method getter_sym do
instance_variable_get(iv_sym)
end
define_method setter_sym do |val|
instance_variable_set( iv_hist, [] ) unless send(history_sym)
send(history_sym).send( :'<<', send(getter_sym) )
instance_variable_set( iv_sym, val #)
end
define_method history_sym do
instance_variable_get(iv_hist)
end
end
end
Here is what should be done. The attr_writer need be defined withing class_eval instead in Class.
class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s
attr_reader attr_name
#attr_writer attr_name ## moved into class_eval
attr_reader attr_name + "_history"
class_eval %Q{
def #{attr_name}=(value)
##{attr_name}_history=[1,2,3]
end
}
end
end

Instance Eval Within Block

I have a Builder class that lets you add to one of it's instance variables:
class Builder
def initialize
#lines = []
end
def lines
block_given? ? yield(self) : #lines
end
def add_line( text )
#lines << text
end
end
Now, how do I change this
my_builder = Builder.new
my_builder.lines { |b|
b.add_line "foo"
b.add_line "bar"
}
p my_builder.lines # => ["foo", "bar"]
Into this?
my_builder = Builder.new
my_builder.lines {
add_line "foo"
add_line "bar"
}
p my_builder.lines # => ["foo", "bar"]
class Builder
def initialize
#lines = []
end
def lines(&block)
block_given? ? instance_eval(&block) : #lines
end
def add_line( text )
#lines << text
end
end
my_builder = Builder.new
my_builder.lines {
add_line "foo"
add_line "bar"
}
p my_builder.lines # => ["foo", "bar"]
You can also use the method use in ruby best practice using the length of arguments with arity:
class Foo
attr_accessor :list
def initialize
#list=[]
end
def bar(&blk)
blk.arity>0 ? blk.call(self) : instance_eval(&blk)
end
end
x=Foo.new
x.bar do
list << 1
list << 2
list << 3
end
x.bar do |foo|
foo.list << 4
foo.list << 5
foo.list << 6
end
puts x.list.inspect

Resources