3

I define class methods dynamically in Rails as follows:

class << self
  %w[school1 school2].each do |school|
    define_method("self.find_by_#{school}_id") do |id|
        MyClass.find_by(school: school, id: id)
    end
  end
end

How can I use method missing to call find_by_SOME_SCHOOL_id without having to predefine these schools in %w[school1 school2]?

mihai
  • 37,072
  • 9
  • 60
  • 86
AdamNYC
  • 19,887
  • 29
  • 98
  • 154

2 Answers2

3

It is not completely clear to me what you want to achieve. If you want to call a method, you naturally have to define it first (as long as ActiveRecord does not handle this). You could do something like this:

class MyClass
  class << self
    def method_missing(m, *args, &block)  
      match = m.to_s.match(/find_by_school([0-9]+)_id/)
      if match
        match.captures.first
      else
        nil
      end
    end  
  end
end

puts MyClass.find_by_school1_id
puts MyClass.find_by_school2_id
puts MyClass.find_by_school22_id
puts MyClass.find_by_school_id

This will output:

1
2
22
nil

You could then do something with the ID contained in the method name. If you are sure that the method is defined, you can also use send(m, args) to call that method on an object/class. Beware though, if you do that on the same class that receives the method missing call and the method is not defined, you will get a stack overflow.

mario
  • 1,248
  • 9
  • 9
  • 1
    Thanks @mario. Method_missing is what I am looking for. I guess I need to parse *arg to dynamically generate methods associated with school names, then parse *block to pass an ID to these methods. Could you kindly explain how I would do this? – AdamNYC Jun 24 '13 at 19:15
  • 1
    @AdamNYC Not exactly. What you need to parse is the `m` parameter. This contains the name of the called method, as a symbol. In my example I extract the ID from the method name using regular expressions. You can instead of course just check if the method exists already with `defined?` and then use `define_method` with `m` to generate that method if it does not exist. – mario Jun 24 '13 at 19:19
0

I recommend return the super unless you have a match


class MyClass
  class << self
    def method_missing(m, *args, &block)  
      match = m.to_s.match(/find_by_school([0-9]+)_id/)
      match ? match.captures.first : super
    end  
  end
end
msroot
  • 817
  • 11
  • 13