# Meta Programming Help

**URL:** <https://www.ruby-forum.com/t/meta-programming-help/76422>\
**Category:** Rails\
**Created:** [March 3, 2006, 10:30pm UTC](https://www.ruby-forum.com/t/meta-programming-help/76422 "2006-03-03T22:30:58Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![jashugan](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/bcef8e/32.png) [@jashugan](https://www.ruby-forum.com/u/jashugan)\
**Post date:** [March 3, 2006, 10:30pm UTC](https://www.ruby-forum.com/t/meta-programming-help/76422/1 "2006-03-03T22:30:58Z")

</div>

I have the following two methods:

def ProductFile::find\_images(mode, prod\_id)  
# convert to symbol in case it is not (most commonly it may be a  
String)  
mode = mode.to\_sym  
case mode  
when :all, :first  
ProductFile::find(mode, :conditions =\> [“product\_id = ? AND  
file\_type LIKE ?”, prod\_id, “image%”])  
end  
end

def ProductFile::find\_documents(mode, prod\_id)  
mode = mode.to\_sym  
case mode  
when :all, :first  
ProductFile::find(mode, :conditions =\> [“product\_id = ? AND  
(file\_type LIKE ? or file\_type LIKE ?)”, prod\_id, “%pdf”, “%msword”])  
end  
end

Is it possible to create one meta-method that contains the stub for  
these methods? I’m NOT looking for something like:

def ProductFile::find\_by\_type(type, mode, prod\_id)  
//code snip  
end

Perhaps something using the define\_method feature?

---

<div class="post-metadata">

**Author:** ![jashugan](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/bcef8e/32.png) [@jashugan](https://www.ruby-forum.com/u/jashugan)\
**Post date:** [March 3, 2006, 11:16pm UTC](https://www.ruby-forum.com/t/meta-programming-help/76422/2 "2006-03-03T23:16:30Z")

</div>

Try this out. The “make\_find :documents” and “make\_find :images” calls  
at  
the bottom will create the class methods you want.

The sql used is a little different since I didn’t think you needed to  
use  
the parameters for the document types. If you want to extend it that  
way,  
feel free 😉

It also could be a little shorter but I thought clarity was more  
important.

# Begin here

class ProductFile \< ActiveRecord::Base

# Makes a method named find\_item, which will take a product\_id and mode

argument. The method

# will use the file\_types list given to generate SQL which matches

against  
those types and the product\_id

# Usage example:

# 

# make\_find :documents, “pdf”, “msword”

# make\_find :images, “image%”

# 

# 

def self.make\_find(item, \*file\_types)  
sql = “product\_id = ? "  
if file\_types.length \> 0  
sql \<\< " AND (”  
sql \<\< file\_types.inject("") do |str, file\_type|  
str \<\< " OR " unless str.empty?  
str \<\< “file\_type LIKE ‘#{file\_type}’”  
end  
sql \<\< “)”  
end

# We want the method created to ‘live’ in ProductFile as a class

method,

# so we must actually create the method in the meta-class of

ProductFile.  
If

# we used ProductFile directly, we would create an instance method.

meta = class \<\< self; self; end;

# have to use send here because define\_method is private

meta.send(:define\_method, “find\_#{item}”) do |mode, prod\_id|

# For debugging, outputs method definition when you run it

puts “mode: #{mode}, prod\_id #{prod\_id}”  
puts “sql: #{sql}”  
mode = mode.to\_sym  
case mode  
when :all, :first  
self.find(mode, :conditions =\> [sql, prod\_id])  
end  
end  
end

# Create the two methods originally requested.

make\_find :documents, “%pdf”, “%msword”  
make\_find :images, “image%”  
end

---

<div class="post-metadata">

**Author:** ![jashugan](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/bcef8e/32.png) [@jashugan](https://www.ruby-forum.com/u/jashugan)\
**Post date:** [March 4, 2006, 3:36am UTC](https://www.ruby-forum.com/t/meta-programming-help/76422/3 "2006-03-04T03:36:01Z")

</div>

Thanks for the response. I’ve been struggling with the code for a  
little while, and couldn’t get it to work.

When I go into the console and try this:  
=\> ProductFile.find\_images(:first, 1)

I get:  
=\> NoMethodError: undefined method `make\_find’ for ProductFile:Class

I think that this might be the issue:

> make\_find :documents, “%pdf”, “%msword”  
> make\_find :images, “image%”

I wasn’t sure where to put these calls, so I put them in the  
ProductFile class. Is this where it goes?

I would like to get some background into meta-programming. Are there  
any resources you can recommend? I would especially like to understand  
this:

---

<div class="post-metadata">

**Author:** ![jashugan](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/bcef8e/32.png) [@jashugan](https://www.ruby-forum.com/u/jashugan)\
**Post date:** [March 4, 2006, 7:24pm UTC](https://www.ruby-forum.com/t/meta-programming-help/76422/4 "2006-03-04T19:24:51Z")

</div>

Copy all the code I sent into your ProductFile class (except the “class  
ProductFile” bit, of course). That code should be able to be added to  
the  
class and work. Just make sure to remove your existing find\_images and  
find\_documents methods.

As for the meta programming bit

meta = class \<\< self; self; end;

That’s a little Ruby idiom that gets you the “class of the class.” It’s  
a  
tough concept to explain, but it’s critical to know when adding methods  
to  
classes or doing other metaprogramming tricks. Check out Dwemthy’s Array  
at

[http://poignantguide.net/dwemthy/](http://poignantguide.net/dwemthy/)

and [http://www.ruby-garden.org](http://www.ruby-garden.org) for more info (just search for  
“metaprogramming” or “singleton class”). Dwemthy’s Array is bizarre, to  
say  
the least, but if you spend some time with it it’s very rewarding.

---

<div class="post-metadata">

**Author:** ![jashugan](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/bcef8e/32.png) [@jashugan](https://www.ruby-forum.com/u/jashugan)\
**Post date:** [March 9, 2006, 12:45am UTC](https://www.ruby-forum.com/t/meta-programming-help/76422/5 "2006-03-09T00:45:23Z")

</div>

Ok. It works. I was under the (false) impression that I could put the  
make\_find calls anywhere. It turns out that, as in your code, I needed  
to add them underneath the definition for make\_find.

However, in the end, I chose to go with a different implementation. In  
ProductFiles, I did the following:

def ProductFile::find\_by\_file\_type(f\_type, mode, prod\_id)  
f\_type = f\_type.to\_sym  
case f\_type  
when :images  
sql\_where = [“product\_id = ? AND file\_type LIKE ‘image%’”,  
prod\_id]  
when :documents  
sql\_where = [“product\_id = ? AND (file\_type LIKE ‘%pdf’ OR  
file\_type LIKE ‘%msword’)”, prod\_id]  
end

```
unless sql_where.nil?
  mode = mode.to_sym
  case mode
  when :all, :first
    ProductFile::find(mode, :conditions => sql_where)
  end
end

```

end

and in my Product model, I did this:

def Product::make\_find(name)  
define\_method(“find\_#{name}”) do |mode|  
mode.to\_sym  
case mode  
when :all, :first  
ProductFile.find\_by\_file\_type(mode, name, self.id)  
end  
end  
end

make\_find “images”  
make\_find “documents”

I am tempted to use the missing\_method method, but I believe that it  
is used by ActiveRecord, which Product inherits from. I don’t want to  
override the ActiveRecord definition, so I’m leaving it alone for now.  
Is there a better way to do this?

Thanks!

- Nithin

---

<div class="post-metadata">

**Author:** ![jashugan](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/bcef8e/32.png) [@jashugan](https://www.ruby-forum.com/u/jashugan)\
**Post date:** [March 9, 2006, 6:01pm UTC](https://www.ruby-forum.com/t/meta-programming-help/76422/6 "2006-03-09T18:01:36Z")

</div>

> def method\_missing(sym, \*args, &blk)  
> if sym.to\_s == “make\_images”
> 
> # your code …
> 
> else
> 
> # Call parent class (i.e. ActiveRecord)
> 
> super(sym, \*args, &blk)  
> end  
> end

Thanks!

> I also think you may be over-engineering this a bit - why not just call into  
> ProductFiles::find\_by\_file\_type directly - but that’s just my opinion 😉

You may be right, but unless I can do something like  
ProductFiles::find\_by\_file\_type(/image/) or  
ProductFiles::find\_by\_file\_type(/(msword|pdf)/), then it won’t work  
for me.

- Nithin

---

<div class="post-metadata">

**Author:** ![jashugan](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/bcef8e/32.png) [@jashugan](https://www.ruby-forum.com/u/jashugan)\
**Post date:** [March 9, 2006, 1:00am UTC](https://www.ruby-forum.com/t/meta-programming-help/76422/7 "2006-03-09T01:00:36Z")

</div>

> I am tempted to use the missing\_method method, but I believe that it  
> is used by ActiveRecord, which Product inherits from. I don’t want to  
> override the ActiveRecord definition, so I’m leaving it alone for now.  
> Is there a better way to do this?

If you define your own method\_missing, you can call into ActiveRecords  
just  
by using the “super” keyword. For example,

def method\_missing(sym, \*args, &blk)  
if sym.to\_s == “make\_images”

# your code …

else

# Call parent class (i.e. ActiveRecord)

super(sym, \*args, &blk)  
end  
end

I also think you may be over-engineering this a bit - why not just call  
into  
ProductFiles::find\_by\_file\_type directly - but that’s just my opinion 😉

---

<div class="post-metadata">

**Author:** ![jashugan](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/bcef8e/32.png) [@jashugan](https://www.ruby-forum.com/u/jashugan)\
**Post date:** [March 9, 2006, 6:10pm UTC](https://www.ruby-forum.com/t/meta-programming-help/76422/8 "2006-03-09T18:10:55Z")

</div>

I believe this is the case of a solution looking for a problem. I was  
looking at the code for the file\_column plugin and was confused /  
awe-struck by all the meta-programming that was going on. So, in an  
effort to learn, I decided to implement some the magic into my  
project. Of course, I should probably KISS.
