Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Ruby's metaprogramming to reduce method count

I have a bunch of methods that are repeating, and I am sure I can use Ruby's metaprogramming somehow.

My class looks like this:

class SomePatterns

  def address_key
    "..."
  end

  def user_key
    "..."
  end

  def location_key
    "..."
  end

  def address_count
    redis.scard("#{address_key}")
  end

  def location_count
    redis.scard("#{location_key}")
  end

  def user_count
    redis.scard("#{user_key}")
  end

end

I was thinking I could have only one method like:

def count(prefix)
  redis.scard("#{prefix}_key") # wrong, but something like this
end

The above is wrong, but I'm saying that the *_count methods will follow a pattern. I'm hoping to learn to use metaprogramming to avoid the duplication.

How could I do this?

like image 534
Blankman Avatar asked Jan 04 '23 04:01

Blankman


2 Answers

I would put all of the "function prefixes" into an array. Upon initialization you can use the :define_singleton_method on these prefixes to dynamically create a instance method on every one of them:

class SomePatterns
  def initialize()
    prefixes = [:address, :location, :user]
    prefixes.each do |prefix|
      define_singleton_method("#{prefix}_count") { redis.scard("#{prefix}_key") }
    end
  end
end

EDIT:

:define_singleton_method might actually be overkill. It will work for what you want, but it will define these functions for that specific instance (hence why its called singleton). The difference is subtle, but important. Instead, it would probably be better to use :class_eval in conjunction with :define_method.

class SomePatterns
    # ...
    class_eval do
      [:address, :location, :user].each do |prefix|
        define_method("#{prefix}_count") { redis.scard("#{prefix}_key") }
      end
    end
end
like image 127
Kyle Boss Avatar answered Jan 13 '23 15:01

Kyle Boss


You could create a macro-style method to tidy things up. For example:

Create a new class Countable:

class Countable
  def self.countable(key, value)
    define_method("#{key}_count") do
      redis.scard(value)
    end

    # You might not need these methods anymore? If so, this can be removed
    define_method("#{key}_key") do
      value
    end
  end
end

Inherit from Countable and then just use the macro. This is just an example - you could also e.g. implement it as an ActiveSupport Concern, or extend a module (as suggested in the comments below):

class SomePatterns < Countable
  countable :address, '...'
  countable :user, '...'
  countable :location, '...'
end
like image 23
gwcodes Avatar answered Jan 13 '23 16:01

gwcodes