How do I set an attr_accessor for a dynamic instance variable?
this answer doesn't pollutes the class space, example.. if i do mine.my_number 4
then the other instances of Mine
will not get the my_4
method.. this happens because we use the singleton class of the object instead of the class.
class Mine
def my_number num
singleton_class.class_eval { attr_accessor "my_#{num}" }
send("my_#{num}=", num)
end
end
a = Mine.new
b = Mine.new
a.my_number 10 #=> 10
a.my_10 #=> 10
b.my_10 #=> NoMethodError
Ruby: dynamically generate attribute_accessor
You need to call the (private) class method attr_accessor
on the Event
class:
self.class.send(:attr_accessor, name)
I recommend you add the @
on this line:
instance_variable_set("@#{name}", value)
And don't use them in the hash.
data = {:datetime => '2011-11-23', :duration => '90', :class => {:price => '£7', :level => 'all'}}
Dynamically Create Class Attributes with attr_accessor
One way (there are others) is to use instance_variable_set
and instance_variable_get
as so:
class Test
def create_method( name, &block )
self.class.send( :define_method, name, &block )
end
def create_attr( name )
create_method( "#{name}=".to_sym ) { |val|
instance_variable_set( "@" + name, val)
}
create_method( name.to_sym ) {
instance_variable_get( "@" + name )
}
end
end
t = Test.new
t.create_attr( "bob" )
t.bob = "hello"
puts t.bob
Set dynamic values when generating setter methods using attr_accessor in ruby
attr_accessor
has no magic embedded. For each of params passed to it, it basically executes something like (the code is simplified and lacks necessary checks etc):
def attr_accessor(*vars)
vars.each do |var|
define_method var { instance_variable_get("@#{var}") }
define_method "#{var}=" { |val| instance_variable_set("@#{var}", val) }
end
end
That said, the attr_accessor :var1, :var2
DSL simply brings new 4 plain old good ruby methods. For what you are asking, one might take care about defining these methods (or some of them, or none,) themselves. For instance, for cumbersome setting with checks one might do:
attr_reader :variable # reader is reader, no magic
def variable=(val) do
raise ArgumentError, "You must be kidding" if val.nil?
@variable = val
end
The above is called as usual:
instance.variable = 42
#⇒ 42
instance.variable = nil
#⇒ ArgumentError: You must be kidding
Ruby Metaprogramming: dynamic instance variable names
The method you are looking for is instance_variable_set
. So:
hash.each { |name, value| instance_variable_set(name, value) }
Or, more briefly,
hash.each &method(:instance_variable_set)
If your instance variable names are missing the "@" (as they are in the OP's example), you'll need to add them, so it would be more like:
hash.each { |name, value| instance_variable_set("@#{name}", value) }
How to set dynamically variable values?
attr_accessor :variable1, :variable2, :variable3
def set_variables(*attributes)
attributes.each {|attribute| self.send("#{attribute}=", true)}
end
create instance variable dynamically in ruby unknown number variables needed
Think it from this approach:
- You have objects (lets say GenericObject)
- Objects have many attributes (GenericObject#attributes => [GenericObject::Attribute])
- Attributes have a name, a value, and a type (GenericObject::Attribute#value, #name and #type)
- Objects have many attributes (GenericObject#attributes => [GenericObject::Attribute])
Which translates into code like this:
class GenericObject
attr_accessor :attributes
def add_attribute(name, value, type)
(@attributes ||= []) << Attribute.new(name, value, type)
end
class Attribute
attr_accessor :name, :value, :type
def initialize(name, value, type)
@name, @value, @type = name, value, type
end
end
end
# so...
cat = GenericObject.new
cat.add_attribute :leg_number, 4, :integer
cat.add_attribute :fur_color, 'Orange', :color
cat.add_attribute :name, 'Garfield', :string
cat.attributes.each { |attr| puts "My cat's #{attr.name} is #{attr.value} (#{attr.type})" }
# My cat's leg_number is 4 (integer)
# My cat's fur_color is Orange (color)
# My cat's name is Garfield (string)
You can make a fancy initializer for GenericObject or whatever you see fit.
Or you can just to a little fix
class GenericObjectArray
def initialize(attrs = {})
attrs.each { |attr,val| instance_variable_set "@#{attr}", val }
end
end
GenericObjectArray.new(:data_fields=> ["may_sales", "june_sales", "july_sales"])
Dynamic Variables to access class methods in Ruby
You could either try to call the getter (preferably, since it honors encapsulation):
pdc = PoorlyDesignedClass.new
1.upto(number_of_things.times do |i|
pdc.public_send(:"thing#{i}").bar = value[i]
end
or get the instance variable (less preferred, since it breaks encapsulation):
pdc = PoorlyDesignedClass.new
1.upto(number_of_things) do |i|
pdc.instance_variable_get(:"@thing#{i}").bar = value[i]
end
So, you were on the right track, there were just two problems with your code: instance variable names start with an @
sign, and .
is not a legal character in an identifier.
Related Topics
Rails Model Name Conflict with Included Gem
Replacing Text in One CSV Column Using Fastercsv
How to Specify Local .Gem Files in My Gemfile
How to Generate a Random Number Between a and B in Ruby
Add Element to an Array If It's Not There Already
Raise Custom Exception with Arguments
Rails: Hasmanythroughassociationnotfounderror
How to Suppress the Output of Return Value in Irb/Rails Console
How to Emit Comments in a Yaml Document Using Psych
Why Doesn't 'User Installation Directory' Match with the Ruby Version
Good Cucumber Examples in the Wild
Ruby - Elegantly Convert Variable to an Array If Not an Array Already
Rails Mapping Array of Hashes Onto Single Hash
Install Latest Stable Version of Ruby Using Rbenv