shivam shivam - 1 month ago 5
Ruby Question

Generic method to set attributes

In my model I have attributes:

is_a
,
is_b
and
is_c
. By default all are null.

I need APIs to set them. These attributes can be set as strictly one or in group. If I am to write APIs, I will be doing following in my model:

def set_as_a # strictly a
self.update_attributes!(:is_a => true, :is_b => false, :is_c => false)
end

def set_as_b # strictly b
self.update_attributes!(:is_a => false, :is_b => true, :is_c => false)
end
... # strictly c
def set_as_a_and_b # a and b
self.update_attributes!(:is_a => true, :is_b => true, :is_c => false)
end
..... # so on


While this works, it does not look elegant. Also if in future if the set has more than 3 attributes, it will result more repetitive code. What is the correct elegant way to achieve this?

Answer
class SettableAsABC
  ATTRS = [:a, :b, :c]
  METHOD_RE = /^set_as_([[:alnum:]]+?(?:_and_[[:alnum:]]+?)*)$/

  def method_missing(name, *args)
    if name.to_s =~ METHOD_RE
      trues = $1.split('_and_').map(&:to_sym)
      attrs = Hash[ATTRS.map { |a| ["is_#{a}".to_sym, trues.include?(a)] }]
      update_attributes(attrs)
    else
      super
    end
  end

  def respond_to_missing?(name, include_private = false)
    !!(name =~ METHOD_RE) || super
  end
end

a = SettableAsABC.new
a.set_as_a_and_c

No defining 2^N methods, just plain Ruby metaprogramming.

EDIT: Good point, @Stefan.

EDIT2: My previous edit introduced a bug. Fixed now.

EDIT3: TIL about respond_to_missing?

Comments