Yuval Karmi
Yuval Karmi

Reputation: 26723

How to make a dynamically named method in Rails?

I have a method in my class that takes a parameter that's a number. It looks something like this:

def with_at_least_x_posts(min_posts)
    self.where("posts_counter >= ?", min_posts)
end

I'd like to make a method that takes that parameter inside its name, rather than in parenthesis, so instead of calling

User.with_at_least_x_posts(10)

I can call

User.with_at_least_10_posts

This would require that method to be defined via some sort of regular expression mechanism. I know that the find_by method works that way (i.e. find_by_some_column), so it should be possible?

Could someone save me some time by telling me how to achieve that in Ruby 1.9.2 and Rails 3.1 without digging into the Rails core and finding out for myself?

Thanks!


Update: while waiting for an answer, I've been digging into the Rails core. Seems like they're overriding the method_missing method in ActiveRecord::Base and delegating the handling of custom methods to a module called DynamicFinderMatch. Interesting stuff! Would be REALLY nice to be able to define methods in Ruby like this:

def /with_at_least_[0-9]+_posts/
   x = $0
end

but I guess that's not really possible at this point in time. Interesting stuff!

Upvotes: 2

Views: 295

Answers (2)

hayesgm
hayesgm

Reputation: 9096

You should define a call within the general Ruby method method_missing

 def with_at_least_x_posts(min_posts)
   self.where("posts_counter >= ?", min_posts)
 end

 def method_missing(sym, *args, &block)
   begin
     if sym.to_s =~ /with_at_least_(\d+)_posts/
       return send :with_at_least_x_posts, *(args.push($1.to_i)), &block
     end
   rescue NoMethodError => e
     raise "Unable to find method: #{e.inspect}"
   end

   return super
 end

From here, you'll be able to call myObj.with_at_least_10_posts. Give it a go! You can read more here.

P.S. This is common in Ruby (it's what ActiveRecord does). Beware that if you get a StackOverflow error, it's because a method_missing error raised in method_missing will lead to an infinate recursion. Be cautious!

Upvotes: 3

Peter
Peter

Reputation: 132407

You could do this with method_missing, but this is a dangerous method that can lead to very messy code. Instead, how about

User.with_more_posts_than(10)

which is much cleaner (and much easier to work with and maintain). It still reads correctly.

As one specific point among many objections, you shouldn't convert 10 to a string and back. This brings in a huge new class of potential errors which you will later have to deal with.

Upvotes: 2

Related Questions