How to Dynamically Call Accessor Methods in Ruby

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



Leave a reply



Submit