views:

41

answers:

2

I've got a module called AB. Right now it looks something like this:

module AB
    extend self

    def some_method(hash)
        ....
    end

    ....
end

We use it like this: AB.some_method(:thing=>:whatever,:etc=>'you get the idea'). There are about a half-dozen strings that the user has to pass in that I'd like to turn into dynamic methods so that instead of AB.some_method(:thing => :whatever...) They'd just call AB.whatever(...) or AB::whatever(...). I thought I could do this with method_missing, but I guess I don't get it. I did something like this:

module AB
    def method_missing(name,*args)
        super unless THINGS.include?(name.to_s)
        ...
    end
end

But I never get into that method when trying to call AB::whatever. I thought about looping over THINGS and using define_method, but I wasn't sure how to define methods that take arguments.

Any help appreciated.

+1  A: 

I think you have two different approaches here you can use, one is defining methods dynamically, another is relying on method_missing. Here are examples of both:

# first approach, define methods
class MyClassDefine
  def some_method args
    p args
  end

  [:foo,:bar].each do |name|
    define_method name do |args|
      # assume args is a hash, fix me
      some_method args.merge({:thing => name})
    end
  end
end

# second approach, use method_missing
class MyClassMissing
    def some_method args
    p args
  end

  def method_missing name, args
    super unless [:foo, :bar].include? name
    # again, assuming argument is a hash
    some_method args.merge(:thing => name)
  end
end

mcd = MyClassDefine.new
mcd.foo :etc => 'you get idea'
#=> {:etc=>"you get idea", :thing=>:foo}

mcm = MyClassMissing.new
mcm.foo :etc => 'you get idea'
#=> {:etc=>"you get idea", :thing=>:foo}

What's left to do here is cover the cases when the methods are passed something else than a hash, as an argument.

Mladen Jablanović
Does your answer mean you can only do it with a class (and not a module)? That's the only difference I'm seeing in your version.
Sam
I think the problem is that you are mixing class and instance methods in your example: `some_method` is both instance and class method, `method_missing` is an instance method, and you are trying to call `whatever` as a class method.
Mladen Jablanović
As I said, I'm happy to call AB::whatever or AB.whatever... I don't care. Neither seems to work when using method_missing in a Module.
Sam
`AB::whatever` and `AB.whatever` are both class method calls. In order to use instance method, you have to create an instance first. However, AB is a module, not a class so that's not an option. So, you would have to make `method_missing` a class method (take a look at Arto's code, for example).
Mladen Jablanović
+1  A: 

The problem in your second code example is that the method_missing ought to be declared as self.method_missing. The following works as expected:

module AB
  THINGS = %w(whatever)

  def self.method_missing(name, *args)
    super unless THINGS.include?(name.to_s)
    "responding to #{name}"
  end
end

p AB.whatever    #=> "responding to whatever"
p AB.something   #=> NoMethodError
Arto Bendiken
Thanks this is exactly what I wanted.
Sam