What does $1.to_sym => args[0] and ($1.to_sym,*args,&block) do in the following line of code?
class Table
def method_missing(id,*args,&block)
return as($1.to_sym,*args,&block) if id.to_s =~ /^to_(.*)/
return rows_with($1.to_sym => args[0]) if id.to_s =~ /^rows_with_(.*)/
super
end
# ...
end
Context:
Ruport is a Ruby reporting library. You can use the Ruport::Data::Table class to create tabular data and convert it to different formats—text, for example:
require 'ruport'
table = Ruport::Data::Table.new :column_names => ["country" , "wine" ], :data => [["France" , "Bordeaux" ], ["Italy" , "Chianti" ], ["France" , "Chablis" ]]
puts table.to_text
⇒
+--------------------+
| country | wine |
+--------------------+
| France | Bordeaux |
| Italy | Chianti |
| France | Chablis |
+--------------------+
Let’s say you select only the French wines and convert them to comma-separated values:
found = table.rows_with_country("France" )
found.each do |row|
puts row.to_csv
end
⇒
France, Bordeaux
France, Chablis
What you just did is call a method named rows_with_country( ) on Ruport::Data::Table. But how could the author of this class know you were going to have a column named country? The fact is, the author didn’t know that. If you look inside Ruport, you see that both rows_with_country( ) and to_csv( ) are Ghost Methods. The Ruport::Data::Table class is somewhat as defined above.
A call to rows_with_country( ) becomes a call to a more traditional-looking
method, rows_with(:country), which takes the column name as an argu-
ment. Also, a call to to_csv( ) becomes a call to as(:csv). If the method
name doesn’t start with either of these two prefixes, Ruport falls back
to Kernel#method_missing( ), which throws a NoMethodError. (That’s what
the super keyword is for.)
Let’s look at
method_missing:The requisite background:
method_missingis called on an object when the requested method isn’t explicitly defined.idwill be a Symbol of the method called;argswill be an array of the arguments, andblockwill be aProcif there was a block, ornil.The execution really begins at the end, in the
if: it checks the conditionThis basically does a match of the regexp on the string of the called method.
x =~ yreturns the integer offset inxof whereymatched, if anywhere, otherwise nil. e.g.:Remember that
0is treated as truth in boolean conditions, and so theifwill only be considered to be true if the name of the called function starts withto_. The rest of the method name is captured by(.*).Now, we don’t explicitly save the capture groups, but Ruby borrows from Perl in that the first capture group’s contents will get saved in
$1, the second in$2, etc.:Now, back to the line in question:
So, if the called method’s name is of the form
to_XYZ, it calls theas()method with the first argument set to:XYZ, and the rest of the arguments from the call appended, and the block passed through (if any).To continue on:
This is basically the same: if the method name is like
rows_with_ABC, then it callsrows_with()with a hash{:ABC => args[0]}, whereargs[0]is the first argument given to the missing method call.