How to dynamically call accessor methods in Ruby
I am not a ruby expert, but I think that you could do:
instance.send("a=", "value")
How to call methods dynamically based on their name?
What you want to do is called dynamic dispatch. It’s very easy in Ruby, just use public_send
:
method_name = 'foobar'
obj.public_send(method_name) if obj.respond_to? method_name
If the method is private/protected, use send
instead, but prefer public_send
.
This is a potential security risk if the value of method_name
comes from the user. To prevent vulnerabilities, you should validate which methods can be actually called. For example:
if obj.respond_to?(method_name) && %w[foo bar].include?(method_name)
obj.send(method_name)
end
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
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.
Dynamically building accessors in ruby on object initialization
You can create attributes with attr_accessor
inside the initialize
method. You only need to reach to it like below:
module Learning360
class User
def initialize(options = {})
options.each do |(k, v)|
self.class.attr_accessor(k)
send("#{k}=", v)
end
end
end
end
user = Learning360::User.new({ name: "Matz" })
puts user.name # Matz
It is also possible to use class name diectly just like User.attr_accessor(k)
.
How do you access a Ruby instance variable/method dynamically?
The #send
method takes a method name. You can call #intern
on a string to make a Symbol from it. So this is the equivalent:
foo = Foo.new
bar = 'bar'
puts foo.send(bar.intern)
Instance variables are private by default. To expose them, the normal thing to do is to add a attr_reader
call to the class definition:
class Foo
attr_reader :bar
def initialize(value_of_bar)
@bar = value_of_bar
end
end
bar = 'bar'
foo = Foo.new("bar_value")
puts foo.bar # => "bar_value"
puts foo.send(:bar) # => "bar_value"
puts foo.send(bar.intern) # => "bar_value"
To reach in and access instance variables that don't have reader methods, #instance_variable_get
will work, though it's often better to avoid it.
Dynamically creating accessors in Ruby module
First, attr_accessor
is unusable for Module, even if normally described.
module Conf
attr_accessor :s3_key
end
Second, the error of overwriting is because method_missing
is executed only once
def self.method_missing(m, *args)
#:
instance_variable_set("@#{m}", args)
module_eval("def self.#{m};@#{m};end") # <- method defined
the method is defined in first call.
And the number of arguments is 0
Conf::s3_key('1234ABC') # call method_missing
Conf::s3_key('4567DEF') # call self.s3_key()
For example, how about like this:
module Conf
def self.method_missing(m, *args)
args = args.pop if args.length==1
instance_variable_set("@#{m}", args)
module_eval(<<EOS)
def self.#{m}(*args)
if (args.empty?)
@#{m}
else
@#{m} = (args.length==1) ? args.pop : args
end
end
EOS
end
end
Conf::s3_key('foo')
Conf::s3_key('bar')
p Conf::s3_key # "bar"
Or
module Conf
def self.method_missing(m, *args)
if (m.to_s =~ /^(.+)=$/)
args = args.pop if args.length==1
instance_variable_set("@#{$1}", args)
else
instance_variable_get("@#{m}")
end
end
end
Conf::s3_key = 'foo'
Conf::s3_key = 'bar'
p Conf::s3_key # "bar"
How to declare custom attribute accessor with dynamic argument in Ruby?
footnote_attrs :title, :body
is a method invocation. If you want to call the method in your class like this:
class Foo
footnote_attrs :title, :body
end
You have to define the method accordingly:
class Foo
def self.footnote_attrs(*args)
@footnote_attrs = args unless args.empty?
@footnote_attrs
end
# if footnote_attrs is to be accessed like an instance method
def footnote_attrs
self.class.footnote_attrs
end
footnote_attrs :title, :body
end
Foo.footnote_attrs #=> [:title, :body]
footnote_attrs #=> [:title, :body]
The implementation is very basic. If the method is called with arguments, it sets the instance variable accordingly. If it is called without arguments, it just returns it.
You might want to return an empty array if footnote_attrs
has not been called yet (currently it would return nil
). It might also be a good idea to return a copy (dup
) instead of the actual array to prevent modification.
How to access Model attributes dynamically in a loop in ruby?
You can do like this:
item.send((attr + "="), values from some other source)
or:
hash = {}
['item_url','item_url','item_label'].each do |attr|
hash[attr] = value
end
item.attributes = hash
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'}}
Related Topics
Including a Virtual Attribute in the Respond_With Hash
Ruby: Remove Whitespace Chars at the Beginning of a String
Rspec View Undefined Method Stub_Model
Retrieving Image Height with Carrierwave
Convert 12 Hr Time to 24 Hr Format in Ruby
Solving the Travelling Salesman Problem in Ruby (50+ Locations)
Rspec Testing Has_Many :Through and After_Save
Built in Way to List Directories in a Directory in Ruby
How to Do Named Capture in Ruby
Ending a Rails 2 Url with an Ip Address Causes Routing Error
Consuming Non-Rest APIs in Rails with Activeresource
Add a Callback Function to a Ruby Array to Do Something When an Element Is Added
How Rails Delegate Method Works
Is Assignment in a Conditional Clause Good Ruby Style
Architecture for a Modular, Component-Based Sinatra Application