- Read Tutorial
- Watch Guide Video
So far, we have seen:
- A basic overview of metaprogramming
- How we can use it in a custom class
- How to use it on existing Ruby classes, such as the String class.
In this guide we are going to see how metaprogramming can be used in real world applications.
I'm going to open up a Ruby on Rails application because Rails leverages metaprogramming as well as any program I've ever seen. We're going to walk through how the Rails framework generates database lookup methods on the fly.
Here is the database schema file for the Rails application we're going to look at:
create_table "contacts", force: true do |t| t.string "name" t.string "email" t.text "message" t.string "category" t.datetime "created_at" t.datetime "updated_at" end create_table "customers", force: true do |t| t.string "name" t.datetime "created_at" t.datetime "updated_at" t.string "email" t.text "logo" end create_table "location_posts", force: true do |t| t.integer "post_id" t.integer "location_id" t.datetime "created_at" t.datetime "updated_at" end
We're going to use the
customers table as our case study for this guide.
In the Rails console (which gives us the ability to perform database queries), I can bring up the record of the last customer with the command:
Now, what happens if we only know the name of the customer and no other details, and we want to pull up a record based on the name.
To do that, the command is:
If you run this code you'll see that it returns the same record as before.
The cool thing about this method is that Rails only has a method called
find_by and it uses metaprogramming to allow users to dynamically enter the database's field. In this sense
name is simply an argument for the
find_by method, though it may look like a complete method by itself.
If you go to the Rails source code, you can see this method:
# Finds the first record matching the specified conditions. There # is no implied ordering so if order matters, you should specify it # yourself. # # If no record is found, returns <tt>nil</tt>. # # Post.find_by name: 'Spartacus', rating: 4 # Post.find_by "published_at < ?", 2.weeks.ago def find_by(arg, *args) where(arg, *args).take rescue RangeError nil end
If you see, this method references the first argument, and it takes on any number of arguments.
Now, to prove that the
find_by method takes other parameters too, let us pull up with a record based on the email:
The output brings up the exact same record. Let's take this a bit further, let's look at the second table in our above list of tables, which is
locations. There is no
name attribute in this table at all. So, if you use
name here, it will throw an error. The code would be:
The application throws an
undefined method error.
Rails is intelligent enough to know that it can only utilize existing attributes for its
find_by method, and not just any random attribute.
This goes to show that
find_by_name is not a method by itself, rather Rails takes the first argument and creates a method dynamically using metaprogramming. You may also have noticed that I circles the
method_missing message in the terminal.
method_missing is going to be the topic of what we're going to explore in future guides. As I've mentioned before
method_missing is the core component that allows for Ruby's most powerful metaprogramming features.