joshblour
joshblour

Reputation: 1034

Dynamic method chain?

How can I call a nested hash of methods names on an object?

For example, given the following hash:

hash = {:a => {:b => {:c => :d}}}

I would like to create a method that, given the above hash, does the equivalent of the following:

object.send(:a).send(:b).send(:c).send(:d)

The idea is that I need to get a specific attribute from an unknown association (unknown to this method, but known to the programmer).

I would like to be able to specify a method chain to retrieve that attribute in the form of a nested hash. For example:

hash = {:manufacturer => {:addresses => {:first => :postal_code}}}
car.execute_method_hash(hash)
=> 90210

Upvotes: 6

Views: 2751

Answers (3)

Archonic
Archonic

Reputation: 5362

There's a much simpler way.

class Object

  def your_method
    attributes = %w(thingy another.sub_thingy such.attribute.many.method.wow)
    object = Object.find(...)
    all_the_things << attributes.map{ |attr| object.send_chain(attr.split('.')) }
  end

  def send_chain(methods)
    methods.inject(self, :try)
  end

end

Upvotes: 2

tessi
tessi

Reputation: 13574

I'd use an array instead of a hash, because a hash allows inconsistencies (what if there is more than one key in a (sub)hash?).

object = Thing.new
object.call_methods [:a, :b, :c, :d]

Using an array, the following works:

# This is just a dummy class to allow introspection into what's happening
# Every method call returns self and puts the methods name.
class Thing
  def method_missing(m, *args, &block)
    puts m
    self
  end
end

# extend Object to introduce the call_methods method
class Object
  def call_methods(methods)
    methods.inject(self) do |obj, method|
      obj.send method
    end
  end
end

Within call_methods we use inject in the array of symbols, so that we send every symbol to the result of the method execution that was returned by the previous method send. The result of the last send is automatically returned by inject.

Upvotes: 11

Aleksei Chernenkov
Aleksei Chernenkov

Reputation: 1051

There is no predefined method, but you can define your own method for that:

class Object
  def send_chain(chain)
    k = chain.keys.first
    v = chain.fetch(k)
    r = send(k)
    if v.kind_of?(Hash)
      r.send_chain(v)
    else
      r.send(v)
    end
  end
end

class A
  def a
    B.new
  end
end

class B
  def b
    C.new
  end
end

class C
  def c
    D.new
  end
end

class D
  def d
    12345
  end
end

chain = { a: { b: { c: :d } } }
a = A.new
puts a.send_chain(chain)  # 12345

Tested with http://ideone.com/mQpQmp

Upvotes: 0

Related Questions