I recently gave a presentation to beginning Sinatra/Rails developers on Active Record methods I find myself commonly using. All of this information can be found in the Rails guides, specifically the Active Record Query Interface section and I encourage you to read more about them there. But if you want some quick information, here’s what I talked about:
Much of what I talked about was in the context of a simple, multiple-author blogging app built with Sinatra. Users have many posts and posts have and belong to many tags. You can view the source code here.
Through the magic of metaprogramming, Active Record creates methods for you that make querying certain records very easy. For each model attribute, Active Record generates a specific
find_by_ method that you can complete with the specific attribute you want to look for. An added advantage of these methods is they will return
nil if they can’t find a valid record rather than an
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
build when building relations
I get sad when I see code that looks like this:
1 2 3 4 5 6 7
Creating related records separately can get unwieldy. Also, if the parameters don’t pass validations we’ve got on our models, our app is going to raise an error.
Remember, because we’ve declared that a user
has_many :posts, users now have a
posts method, which you can use to build related records with the
1 2 3 4 5 6 7 8 9 10 11 12 13
build() works a lot like
new in that it instantiates the object in memory without trying to save to the database right away. Then, we can check whether the record saves and redirect to the appropriate page if it does, otherwise, re-render the new post page.
An interesting difference between
new() is that
build() will automatically add the new object to the collection of its related record, even though it hasn’t yet been persisted.
Worried you might end up duplicating records because you expect users to submit data you’ve already got in your database? Use
first_or_create. You can use this method in conjunction with
.where() and if it exists, it will return the first record. Otherwise it will create a new record.
Our tags situation is a perfect use case. When a user creates a post, they might add a tag we already have in our database. We’ll end up with a bunch of tags with the same name, or if we have uniqueness validations on our tags, the tags will fail to save altogether. We can avoid this with
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
Pluck out the attributes you need
When you’ve saved enough records in your database and you want to examine certain attributes, sometimes you’re only interested in a few. Running calling
.pluck() will return only the attributes you’re interested in.
Because the tags in our blog app only have one attribute we’ll ever be interested in, their name, there’s no need to loop over the tag records in full – we can just grab the names.
1 2 3 4 5 6
Make data dependent on the existence of other data
This is a common oversight among beginners. If certain records should only exist when their related records are present, then you need to enforce that constraint.
Fortunately, Active Record makes this incredibly easy. One simply needs to add
dependent: :destroy after a
1 2 3
Now, if a user deletes his or her account, all the user’s post records will also be destroyed, and we don’t run the risk of causing our app to fail because of bad data.
Learn Active Record Queries Because They Probably Write Better SQL Than You
I learned this the hard way recently, as you can read about in this post. At the same time, if you don’t know right off the bat how to write a query in Active Record, you can write your own raw SQL commands using
.find_by_sql(). For example, let’s say we want to order users of our app by who has the most posts, and we didn’t know how to write it with an Active Record query. We could simply do:
1 2 3
For our blog application, we probably want the default order of how posts appear to be in descending order (ordered by most recently published). However, the default ordering is ascending order. It’s going to get annoying fast if we have to write
Post.order('created_at DESC') every time we want to pass all the posts to a view. So we can easily create a default scope in the model:
1 2 3 4 5
We can create custom scopes on the fly. For example, let’s say our users love Godzilla and are constantly writing about the 350-ft scaly monster. We wouldn’t want to have to write the query for that over and over; we could simply create a scope for all posts tagged ‘godzilla’:
1 2 3 4 5 6 7
I could then find all these posts with
If our blog app took off and acquired thousands of users, all creating lots of posts and tags, we’d want to implement eager loading of data to cut down on the number of queries made to our database.
Right now, I’ve got 10 posts showing up on the home page of the blog (via pagination). I decided to also show all the posts for each blog.
To accomplish this, I loop through the posts and their associated tags:
1 2 3 4 5 6 7 8
The code will execute 1 query to grab 10 posts, and then several additional queries for each post based on their associated tags. This is referred to as the N + 1 queries problem. If each post has three tags, that’s 30 + 1 queries.
It’s quite easy to handle this – we simply need to let Active Record know to
includes() the related records we want to load (in this case, tags), which will reduce the number of queries to two.
I can include this eager loading in our default scope I defined earlier:
1 2 3 4 5 6 7
Now, when I load the index page, I see I get a much smaller number of queries: