8

Using Rails 4

I am wondering (and having a hard time finding an answer) if it is OK to call an ActiveRecord method directly from the view, such as:

<%= Article.where(approved: true).count %>

or

<%= Article.where("short_answer is NOT NULL and short_answer != ''").count %>

I realize the normal practice would be to store these in an instance variable inside of the controller, but since I am using a partial, I cannot do that.

Is doing this ok? Can it hurt? Is there a better way to go about this (such as a helper method)? Thank you!

halfer
  • 19,824
  • 17
  • 99
  • 186
Kathan
  • 1,428
  • 2
  • 15
  • 31

3 Answers3

7

Is doing this ok? Can it hurt?

It's definitely okay, but the problem is that you'll be calling another db query - which is the most "expensive" part of a Rails app.

@instance_variables are set once, and can be used throughout the view:

#app/views/articles/show.html.erb
#Referencing @article references stored data, not a new DB query
<%= @article.title %>
<%= @article.description %>
<%= @article.created_at %>

Because the above all uses the stored @article data, the database is only hit once (when @article is created in the controller).

If you call AR methods in the view, you're basically invoking a new db call every time:

#app/views/articles/show.html.erb
#Bad practice
<%= Article.select(:name).find(params[:id]) %>
<%= Article.select(:description).find(params[:id]) %>
<%= Article.select(:created_at).find(params[:id]) %>

To answer your question directly, you would be okay to call that data IF you were only counting database-specific data.

IE if you were trying to count the number of @articles, you'd be able to call @articles.size (ActiveRecord: size vs count)

The prudent developer will determine which data they have in their controller, and which they need to pull from the db... doing all their db work in the controller itself:

#app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
   def index
      @articles = Article.where(approved: true) #-> could use a scope here if you wanted
   end
end

#app/views/articles/index.html.erb
<%= @articles.size %>

Nithin's answer is great but won't get past the consideration that you have to determine whether you need to call the db explicitly, or use already-invoked data.

Finally, in regards to using a partial, if you have to pass that data every time, you may wish to use some sort of conditional data to determine whether you need to call the db or not:

#app/views/shared/_partial.html.erb
<% approved ||= Article.approved_articles.size %>
<% short    ||= Article.short_answer_presence.size %>

This will allow you to set locals IF you want, and also have "defaults" set if they aren't set.

Community
  • 1
  • 1
Richard Peck
  • 76,116
  • 9
  • 93
  • 147
3

You should mostly do

class Article < ActiveRecord::Base
....

  scope :approved_articles, where(approved: true)
  scope :short_answer_presence, where("short_answer is NOT NULL and short_answer != ''")
end

In your controller method

@approved_articles_count     = Article.approved_articles.count
@short_answer_presence_count = Article.short_answer_presence.count

and use those variables in view.

In case of partials, as said my Raman you can do that.

<%= render partial: "form", locals: {approved_articles_count: @approved_articles_count, short_answer_presence_count: @short_answer_presence_count} %>
Nithin
  • 3,679
  • 3
  • 30
  • 55
  • Awesome, thanks for the detailed answer. I will go this route. Just curious though, can it hurt to call it directly from the view like I was? @Nithin – Kathan Dec 20 '15 at 04:08
  • It wouldn't be a problem, but it wouldn't be following rails DRY concept. Not a good way for lot of things though. – Nithin Dec 20 '15 at 04:50
1

You can always pass these variables inside a partial using locals:

<%= render partial: "form", locals: {zone: @zone} %>

Its always a good practice to define the instance variables in controller, it does not hurt but you don't end up doing business logic inside a view.

Raman
  • 1,221
  • 13
  • 20