47

I want to create a bunch of methods for a find_by feature. I don't want to write the same thing over and over again so I want to use metaprogramming.

Say I want to create a method for finding by name, accepting the name as an argument. How would I do it? I've used define_method in the past but I didn't have any arguments for the method to take. Here's my (bad) approach

["name", "brand"].each do |attribute| define_method("self.find_by_#{attribute}") do |attr_| all.each do |prod| return prod if prod.attr_ == attr_ end end end 

Any thoughts? Thanks in advance.

4
  • Be aware if all is a large data set this could cause performance issues. Also I sincerely hope this is outside of the rails context as rails already implements find_by_XXX for every attribute. Commented Jul 6, 2016 at 20:32
  • 1
    Note: this will define two methods named self.find_by_name and self.find_by_brand. While it is possible to create such methods, it is impossible to call them using normal method calling syntax, because . is not a legal character in an identifier. Is there any particular reason why you want to define a method with an illegal name? Commented Jul 6, 2016 at 20:44
  • @engineersmnky It's not rails! all just returns an array of Products for the inventory system of a Toy store. It's for the final project for the Ruby Nanodegree at Udacity. Commented Jul 6, 2016 at 20:44
  • @JörgWMittag I had no idea about that, and I was wondering why the NoMethodError, since I had a self.something method. Commented Jul 6, 2016 at 20:46

3 Answers 3

59

If I understand your question correctly, you want something like this:

class Product class << self [:name, :brand].each do |attribute| define_method :"find_by_#{attribute}" do |value| all.find {|prod| prod.public_send(attribute) == value } end end end end 

(I'm assuming that the all method returns an Enumerable.)

The above is more-or-less equivalent to defining two class methods like this:

class Product def self.find_by_name(value) all.find {|prod| prod.name == value } end def self.find_by_brand(value) all.find {|prod| prod.brand == value } end end 
Sign up to request clarification or add additional context in comments.

5 Comments

This works for me but... Can you explain how you got here? I searched for class << self and saw that it opens up a class and changes it's behavior but, why would I need to do that inside the class itself?
You need to do that because you want to define a class method. Without that, define_method will define an instance method, and define_method("self.foo") as in your example doesn't work.
Thank you very much! I'll look into it more to be more comfortable with Metaprogramming but for now, you helped me a lot! :)
The more idiomatic way would be to use define_singleton_method.
The distinction of defining a class method vs an instance method is extremely important for the sake of performance. Thanks for mentioning that @Jordan
12

It if you read the examples here http://apidock.com/ruby/Module/define_method you will find this one:

define_method(:my_method) do |foo, bar| # or even |*args| # do something end 

is the same as

def my_method(foo, bar) # do something end 

1 Comment

This keeps giving me a NoMethodError :/
4

When you do this: define_method("self.find_by_#{attribute}")

that is incorrect. The argument to define_method is a symbol with a single word.

Let me show you some correct code, hopefully this will be clear:

class MyClass < ActiveRecord::Base ["name", "brand"].each do |attribute| define_method(:"find_by_#{attribute}") do |attr_| first(attribute.to_sym => attr_) end end end 

This will produce class methods for find_by_brand and find_by_name.

Note that if you're looking into metaprogramming, this is a good use-case for method_missing. here's a tutorial to use method_missing to implement the same functionality you're going for (find_by_<x>)

3 Comments

If this is rails than this is a useless exercise since find_by_XXX is already defined. Also define_method can accept a String without issue.
method_missing is something I hate. There is no easy way to find methods defined in this way. Avoid if possible.
@akostadinov agreed

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.