Rader on Rails

Dispatches from my web development journey.

Active Record Fun: Methods I Commonly Use (and Maybe You Should Too)

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.

Dynamic finders

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 ActiveRecord::RecordNotFound error.

For example:

posts_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
get '/posts/:id' do
  # This will raise a RecordNotFoundError and cause Sinatra to freak out
  # @post = Post.find(params[:id])

  # This returns a record or nil, so we can check whether we have a post
  @post = Post.find_by_id(params[:id])

  # We can then use some conditional logic to render a page if we get a valid record
  # Otherwise, we can redirect the user with a notification that the record doesn't exist
  if @post
    erb :"posts/show"
  else
    flash[:warning] = "That post doesn't exist."
    redirect "/"
  end

end

Use build when building relations

I get sad when I see code that looks like this:

posts_controller.rb
1
2
3
4
5
6
7
post '/posts' do
  @user = User.find(session[:user_id])

  @post = Post.create(user_id: @user.id, title: params[:post][:title], body: params[:post][:body])

   erb :"posts/show"
end

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 build() method:

posts_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
post '/posts' do
  # Build the association
  @post = current_user.posts.build(params[:post])

  # Ensure the record saves
  if @post.save
    flash[:success] = "Post successfully created!"
    redirect "/posts/#{@post.id}"
  else
    flash[:danger] = "There was a problem creating your post. Please try again."
    erb :"posts/new"
  end
end

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 build() and 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.

first_or_create

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 first_or_create.

posts_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
post '/posts' do
  # Build the association
  @post = current_user.posts.build(params[:post])

  # Ensure the record saves
  if @post.save
    params[:tags].split(", ").each do |param_tag|

    # Make sure we don't create tags with the same name
    tag = Tag.where(name: param_tag).first_or_create
      # Add the tag to the posts collection
      @post.tags << tag
    end

    flash[:success] = "Post successfully created!"
    redirect "/posts/#{@post.id}"
  else
    flash[:danger] = "There was a problem creating your post. Please try again."
    erb :"posts/new"
  end
end

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.

post.rb
1
2
3
4
5
6
def show_tags
  tag_string = ""
  # pluck(:name) allows me to return just the name attributes in all my tag records
  tags.pluck(:name).each { |tag| tag_string << "#{tag}, " }
  tag_string
end

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 has_many or has_one declaration.

user.rb
1
2
3
class User < ActiveRecord::Base
  has_many :posts, dependent: :destroy
end

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
User.find_by_sql("SELECT users.*, COUNT(DISTINCT posts) AS num_posts FROM users
                    INNER JOIN posts ON posts.user_id = users.id
                  GROUP BY users.id ORDER BY num_posts DESC")

Scopes

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:

post.rb
1
2
3
4
5
class Post < ActiveRecord::Base

  default_scope { order('created_at DESC') }

end

Custom Scopes

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’:

post.rb
1
2
3
4
5
6
7
class Post < ActiveRecord::Base

  default_scope { order('created_at DESC') }

  scope :godzillaed, -> { joins(:tags).where(tags: { name: 'godzilla' } )}

end

I could then find all these posts with Post.godzillaed.

Eager loading

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:

posts/index.erb
1
2
3
4
5
6
7
8
<div class="posts">
<% @posts.each do |post| %>
  <h2><a class="post" href="/posts/<%= post.id %>"><%= post.title %></a></h2>
  <% post.tags.each do |tag| %>
    <a href="#"> -- <%= tag.name %></a>
  <% end %>
<% end %>
</div>

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:

post.rb
1
2
3
4
5
6
7
class Post < ActiveRecord::Base

  default_scope { includes(:tags).order('created_at DESC') }

  scope :godzillaed, -> { joins(:tags).where(tags: { name: 'godzilla' } )}

end

Now, when I load the index page, I see I get a much smaller number of queries:

1
2
Post Load (3.2ms)  SELECT  "posts".* FROM "posts"   ORDER BY created_at DESC LIMIT 10 OFFSET 0
HABTM_Tags Load (0.9ms)  SELECT "posts_tags".* FROM "posts_tags"  WHERE "posts_tags"."post_id" IN (54, 53, 52, 50, 48, 47, 46, 45, 44, 43)

Much better.

Comments