vikas95prasad
vikas95prasad

Reputation: 1332

Ruby metaprogramming to achieve dynamic methods?

Want to achieve the following code using metaprogramming.

@resource = {}
@voters = {}
@is_upvoted = {}

def resource(comment)
  @resource[comment.id]
end

def voters(comment)
  @voters[comment.id]
end

def is_upvoted(comment)
  @is_upvoted[comment.id]
end

How can I create these methods using ruby metaprogramming and access the hash?

Can you tell me what is wrong in my code ?

['resource', 'voters', 'is_upvoted'].each do |attribute|
  define_method("#{attribute}") do |comment|
    instance_variable_set("@#{attribute}", comment.id)
  end
end

Upvotes: 0

Views: 76

Answers (3)

vikas95prasad
vikas95prasad

Reputation: 1332

This is how I used it and it works

['resource', 'voters', 'is_upvoted'].each do |attribute|
  define_method("#{attribute}") do |comment|
    instance_variable_get("@#{attribute}")[comment.id]
  end
end

Upvotes: 0

jvillian
jvillian

Reputation: 20263

This bit seems redundant:

@resource = {}
@voters = {}
@is_upvoted = {}

Since you're already looping an array to do your metaprogramming.

You might try something like:

class Foo 

  %w(
    resource
    voters
    is_upvoted
  ).each do |attr_sym|
    define_method attr_sym do |comment|
      instance_variable_set("@#{attr_sym}", {}) unless instance_variable_get("@#{attr_sym}")
      instance_variable_get("@#{attr_sym}")[comment.id]
    end
  end

end

Which I believe will give you methods roughly like:

class Foo 

  def resource(comment)
    @resource ||= {}
    @resource[comment.id]
  end

end

Personally, it seems not great to me to have comment.id in your method. Because what if someday you want to use a different attribute (or something else altogether) as the key?

So, I think I would do:

class Foo 

  %w(
    resource
    voters
    is_upvoted
  ).each do |attr_sym|
    define_method attr_sym do |key|
      instance_variable_set("@#{attr_sym}", {}) unless instance_variable_get("@#{attr_sym}")
      instance_variable_get("@#{attr_sym}")[key]
    end
  end

end

Now, it seems like you're going to want an easy way to set key-value pairs on your instance variable, so I guess I would try something like:

class Foo 

  %w(
    resource
    voters
    is_upvoted
  ).each do |attr_sym|
    define_method attr_sym do |key=nil|
      instance_variable_set("@#{attr_sym}", {}) unless instance_variable_get("@#{attr_sym}")
      hsh = instance_variable_get("@#{attr_sym}")
      return hsh[key] if key
      hsh
    end
  end

end

In which case you should be able to do (assuming you have a @comment variable that responds to id):

@comment.id 
 => 1 
foo = Foo.new
 => #<Foo:0x000056536d7504b0>
foo.resource 
 => {}
foo.resource[@comment.id] = :bar
 => :bar 
foo.resource 
 => {1=>:bar}
foo.resource[@comment.id]
 => :bar

Upvotes: 2

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230336

Can you tell me what is wrong in my code ?

It's doing the equivalent of this:

def resource(comment)
  @resource = comment.id
end

instance_variable_get would be a better choice.

Upvotes: 1

Related Questions