Search code examples
ruby-on-railsrubymetaprogramminginstance-variables

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

Solution

  • 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