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.
Related
Is it possible to return create copy of object with an attribute set, in Ruby?
Of course, a method can be defined to do this -
class URI::Generic
def with_query(new_query)
ret = self.dup
ret.query = new_query
ret
end
end
But this might get a bit tedious to do with every attribute.
You can use options hash to pass multiple attribute-value pairs. Here is an illustrative example.
class Sample
attr_accessor :foo, :bar
def with(**options)
dup.tap do |copy|
options.each do |attr, value|
m = copy.method("#{attr}=") rescue nil
m.call(value) if m
end
end
end
end
obj1 = Sample.new
#=> #<Sample:0x000000029186e0>
obj2 = obj1.with(foo: "Hello")
#=> #<Sample:0x00000002918550 #foo="Hello">
obj3 = obj2.with(foo: "Hello", bar: "World")
#=> #<Sample:0x00000002918348 #foo="Hello", #bar="World">
# Options hash is optional - can be used to just dup the object as well
obj4 = obj3.with
#=> #<Sample:0x0000000293bf00 #foo="Hello", #bar="World">
PS: There may be few variations on how to implement options hash, however, essence of approach will be more or less same.
I noticed some weird behavior with instance variables in Ruby the other day. I was trying to add an instance variable array, containing other instance variable "attributes" of the class. The class is initialized without any parameters, but I still wanted to create this array at initialization. Here's an example of a (stripped-down) class:
class Foo
attr_accessor :bar, :baz
attr_reader :attrs
def initialize
#attrs = [#bar, #baz]
end
end
Here's where it gets weird:
f = Foo.new #=><Foo.0x[object_id] #attrs=[nil, nil]>
f.bar = "bar" #=>"bar"
f.baz = "baz" #=>"baz"
f.attrs #=>[nil, nil]
At initialization, I can see that Foo.attrs is [nil, nil]. But after updating Foo.bar and Foo.baz, why is Foo.attrs still returning [nil, nil]? Why aren't their new values reflected?
I figured this wasn't the best way to do this, and found a way around it, but I'm still curious about this behavior.
Because that's how variables work, here and in virtually every other programming language.
Your array contains the values of #bar and #baz at the time the array was created. It does not contain references to the variables themselves. Modifying one does not modify the other.
Effectively you've done this:
x = 3;
y = x;
x = 4;
# Why doesn't y equal 4?
y is not 4 because x and y share a value but are otherwise unrelated. Reassigning x to a new value does not modify the value that y contains.
If you want this to work, you need to make an accessor that builds the array on-demand, using the current values of your member variables:
class Foo
attr_accessor :bar, :baz
def attrs
[#bar, #baz]
end
end
You can simply add a puts and see what happens
class Foo
attr_accessor :bar, :baz
attr_reader :attrs
def initialize
#attrs = [#bar, #baz]
puts "inside initialize"
end
end
Now you can see initialize gets executed when you create an instance of Foo
f = Foo.new
#=> inside initialize
#=> #<Foo:0x2bc1bb0 #attrs=[nil, nil]>
f.bar = "bar" #=>"bar" , "inside initialize" not printed
If you do want to get them assigned then create a setter
class Foo
attr_accessor :bar, :baz
attr_reader :attrs
def initialize
#attrs = [#bar, #baz]
puts "inside initialize"
end
def bar=(v)
#bar = v
#attrs = [#bar,#baz]
end
def baz=(v)
#baz = v
#attrs = [#bar,#baz]
end
end
f = Foo.new
#=> inside initialize
#=> #<Foo:0x2bc1bb0 #attrs=[nil, nil]>
f.bar = "bar"
f.attrs #=> ["bar", nil]
f.baz = "baz"
f.attrs #=> ["bar", "baz"]
If I have a ruby class called Node:
class Node
attr_accessor :a, :b, :c
attr_reader :key
def initialize(key)
#key = key
end
def [](letter)
if letter == 'a'
return self.a
elsif letter == 'b'
return self.b
elsif letter == 'c'
return self.c
end
end
end
How can I optimize def [](letter) so I won't have repetitive code? More specifically, how can I access an attribute of an object (that is a ruby symbol :a, :b, or :c) by using a corresponding string?
You can use send, which invokes a method dynamically on the caller, in this case self:
class Node
def [](key)
key = key.to_sym
send(key) if respond_to?(key)
end
end
Note that we check that self has the appropriate method before calling it. This avoids getting a NoMethodError and instead results in node_instance[:banana] returning nil, which is appropriate given the interface.
By the way, if this is the majority of the behavior of your Node class, you may simply want to use an OpenStruct:
require 'ostruct'
node_instance = OpenStruct.new(a: 'Apple', b: 'Banana')
node_instance.a #=> 'Apple'
node_instance['b'] #=> 'Banana'
node_instance.c = 'Chocolate'
node_instance[:c] #=> 'Chocolate'
How can I call a nested hash of methods names on an object?
For example, given the following hash:
hash = {:a => {:b => {:c => :d}}}
I would like to create a method that, given the above hash, does the equivalent of the following:
object.send(:a).send(:b).send(:c).send(:d)
The idea is that I need to get a specific attribute from an unknown association (unknown to this method, but known to the programmer).
I would like to be able to specify a method chain to retrieve that attribute in the form of a nested hash. For example:
hash = {:manufacturer => {:addresses => {:first => :postal_code}}}
car.execute_method_hash(hash)
=> 90210
I'd use an array instead of a hash, because a hash allows inconsistencies (what if there is more than one key in a (sub)hash?).
object = Thing.new
object.call_methods [:a, :b, :c, :d]
Using an array, the following works:
# This is just a dummy class to allow introspection into what's happening
# Every method call returns self and puts the methods name.
class Thing
def method_missing(m, *args, &block)
puts m
self
end
end
# extend Object to introduce the call_methods method
class Object
def call_methods(methods)
methods.inject(self) do |obj, method|
obj.send method
end
end
end
Within call_methods we use inject in the array of symbols, so that we send every symbol to the result of the method execution that was returned by the previous method send. The result of the last send is automatically returned by inject.
There's a much simpler way.
class Object
def your_method
attributes = %w(thingy another.sub_thingy such.attribute.many.method.wow)
object = Object.find(...)
all_the_things << attributes.map{ |attr| object.send_chain(attr.split('.')) }
end
def send_chain(methods)
methods.inject(self, :try)
end
end
There is no predefined method, but you can define your own method for that:
class Object
def send_chain(chain)
k = chain.keys.first
v = chain.fetch(k)
r = send(k)
if v.kind_of?(Hash)
r.send_chain(v)
else
r.send(v)
end
end
end
class A
def a
B.new
end
end
class B
def b
C.new
end
end
class C
def c
D.new
end
end
class D
def d
12345
end
end
chain = { a: { b: { c: :d } } }
a = A.new
puts a.send_chain(chain) # 12345
Tested with http://ideone.com/mQpQmp
i want to do the following:
I want to declare the instance variables of a class iterating over a dictionary.
Let's assume that i have this hash
hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}
and i want to have each key as instance variable of a class. I want to know if i could declare the variables iterating over that hash. Something like this:
class MyClass
def initialize()
hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}
hash.each do |k,v|
#k = v
end
end
end
I know this doesn't work! I only put this piece of code to see if you could understand what i want more clearly.
Thanks!
class MyClass
def initialize()
hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}
hash.each do |k,v|
instance_variable_set("##{k}",v)
# if you want accessors:
eigenclass = class<<self; self; end
eigenclass.class_eval do
attr_accessor k
end
end
end
end
The eigenclass is a special class belonging just to a single object, so methods defined there will be instance methods of that object but not belong to other instances of the object's normal class.
class MyClass
def initialize
# define a hash and then
hash.each do |k,v|
# attr_accessor k # optional
instance_variable_set(:"##{k}", v)
end
end
end
Chuck's answer is better than my last two attempts. The eigenclass is not self.class like I had thought; it took a better test than I had written to realize this.
Using my old code, I tested in the following manner and found that the class was indeed manipulated and not the instance:
a = MyClass.new :my_attr => 3
b = MyClass.new :my_other_attr => 4
puts "Common methods between a & b:"
c = (a.public_methods | b.public_methods).select { |v| a.respond_to?(v) && b.respond_to?(v) && !Object.respond_to?(v) }
c.each { |v| puts " #{v}" }
The output was:
Common methods between a & b:
my_other_attr=
my_attr
my_attr=
my_other_attr
This clearly disproves my presupposition. My apologies Chuck, you were right all along.
Older answer:
attr_accessor only works when evaluated in a class definition, not the initialization of an instance. Therefore, the only method to directly do what you want is to use instance_eval with a string:
class MyClass
def initialize(params)
#hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}
params.each do |k,v|
instance_variable_set("##{k}", v)
instance_eval %{
def #{k}
instance_variable_get("##{k}")
end
def #{k}= (new_val)
instance_variable_set("##{k}", new_val)
end
}
end
end
end
To test this try:
c = MyClass.new :my_var => 1
puts c.my_var
http://facets.rubyforge.org/apidoc/api/more/classes/OpenStructable.html
OpensStructable is a mixin module
which can provide OpenStruct behavior
to any class or object. OpenStructable
allows extention of data objects with
arbitrary attributes.