Jason Charnes

ActiveRecord Calls in Your Views? Get Them Out

Ruby on Rails gives developers tools to build web applications rapidly. “Convention over configuration” means that a lot of decisions are made for you. Take, for example, ActiveRecord.

You don’t have to concern yourself with how you’ll interact with data. Ruby on Rails implements the Active Record pattern through ActiveRecord, included in every Rails application by default.

I don’t mind ActiveRecord. In fact, I believe it helped me excel as a developer. I had a very limited SQL background from college. When I started learning Rails, I could use that limited knowledge by relying on ActiveRecord.

Consider the following snippet of SQL:

SELECT * FROM posts;

Due to my lack of experience, I would litter that throughout my application. When I started learning Rails, I discovered it gave me a better way to do that using MVC (Model-View-Controller) with the model backed by ActiveRecord. The previous line of SQL with ActiveRecord is:

Post.all

A Simple Example

Let’s start with the basics.

“Great! I don’t have to write SQL in my views,” I thought.

# app/views/posts/index.html.erb
<h1>All Posts</h1>
<% Post.all.each do |post| %%>
<%= post.title %%>
<% end %%>

Quickly I learned about MVC in Ruby on Rails.

Model-View-Controller is a separation of concerns.

  • Your Model shouldn’t care about your View
  • Your View shouldn’t care about your Controller
  • View Controller shouldn’t care about either

Let’s consider the Controller. It’s responsible for getting data to the View.

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def index
@posts = Post.all
end
end
# app/views/posts/index.html.erb
<h1>All Posts</h1>
<% @posts.each do |post| %%>
<%= post.title %%>
<% end %%>

Benefit #1

If you need to use @posts multiple times, it only calls the database once.

<% @posts.each do |post| %%>
...
<% end %%>
<hr/>
<% @posts.each do |post| %%>
...
<% end %%>
Processing by PostsController#index as HTML
Rendering posts/index.html.erb within layouts/application
Post Load (0.2ms) SELECT "posts".* FROM "posts"
Rendered posts/index.html.erb within layouts/application (3.3ms)

Explicitly calling Post.all in your view would attempt to call the database multiple times. However, Rails is smart enough to cache the first call and return those results for the subsequent calls to Post.all.

Benefit #2

If the call to Post needs to change, you only have to update it once.

Example: You introduce a published attribute on a post. If it’s false, you don’t want that post to show in the list of all posts.

# app/controllers/posts_controller.rb
class PostsController < AppplicationController
def index
@posts = Post.where(published: trued)
end
end

Before you would have needed to change that multiple places in your view. By honoring the separation of concerns, you’re able to reduce the amount of work and cognitive load to make the change.

A More Intermediate Example

When learning Rails, you likely already learned the last examples.

“I already know this!” you exclaimed!

Have you considered dropdowns? Say you need to fill a drop down with a list of sizes.

<%= select_tag :size, options_for_select([["Small", "sm"], ["Medium", "md"], ["Large", "lg"]]) %%>

Eventually, requirements change as they always do. The client has requested the ability to control sizes. No problem! You’re a web developer; you were hoping they would ask for that. You create a model for sizes and populate your dropdown from that list.

<%= select_tag :size, options_from_collection_for_select(Size.all, 'id', 'name') %%>

That works as expected and “DRYs” up the size drop down. There’s a problem, though. We just violated MVC. Our view explicitly knows about our model.

Luckily, it’s not too difficult to clean this up. Make that call in your controller, just like before.

# app/controllers/products_controller.rb
class ProductsController < ApplicationController
def new
@product = Product.all
@sizes = Size.all
end
end
# app/views/
<%= select_tag :size, options_from_collection_for_select(@sizes, 'id', 'name') %%>

“With Great Power ActiveRecord Comes Great Responsibility”

There isn’t anything preventing you from calling ActiveRecord throughout your views. Though DHH was talking about deeper functionality, this ability reminds me of an article about “Sharp Knives.”

Ruby includes a lot of sharp knives in its drawer of features. … There’s nothing programmatically in Ruby to stop you using its sharp knives to cut ties with reason. … That brings us to Rails. The knives provided by the framework are not nearly as sharp as those offered with the language, but some are still plenty keen to cut.

Next time you start to make that ActiveRecord call in your view, think of another place it could go.

Follow me on Twitter.