3

I have a RESTful controller that is responsible for building many different models. This means that any given view would requires a handful of variables to be set before it can be rendered correctly. If I set those variables in the controller, then the code would have to be duplicated across different actions that might render that view. For example, rendering Page 1 requires 5 variables. If show, create, and update all render that view, then the code to set those 5 variables is duplicated across those controller actions. The alternative is to just put all of that code inside the view. But that can get really ugly:

<% variable1 = Model1.where(some conditions) %>
<% variable2 = Model2.where(some other conditions) %>
<% variable3 = Model3.where(some third conditions) %>

I'm hesitant about this solution because of how much code goes into the views. I've always followed the principle that the code in views shouldn't touch the database. Another method I'm entertaining is creating private methods that focus on setting variables and rendering a view. This method could be called by all the actions that require rendering.

revo_wolf
  • 73
  • 1
  • 4

3 Answers3

4

In my point of view, all your logic should be placed in your controller, models, helpers or libraries. In your case, setting variables should be done in your controller and not in your views.

It's really interresting to place your logic at the good place, because it will be more easy to debug, maintain or refactore your application if your code is at the good place.

So, here are some ideas to put your variables declarations in your controller without duplicate your code :)

before_action (in controller)

You can use before_action in your controller. It will reduce the duplicated code.

For example, you can do:

before_action :set_variables

def set_variables
  @var1 = some_code
  @var2 = other_code
  ...
end

You can restrict the before_action to only specific actions by using only or except

before_action :set_variables, only: [:index, :edit]

This will call set_variables only before index and edit

before_action (in application_controller.rb)

If you want to add a before_action for all index actions in every controllers for examples, you just have to do a before_action in your application_controller.rb

And if you want to skip this type of before_action in a specific controller, you can use the skip_before_action method.

# application_controller.rb
before_action :set_variables, only: :index

# specific_controller.rb
skip_before_action :set_variables

One more thing: model scopes

Then, a last thing before the end: Model1.where(some conditions). What about model scopes?

Your code will be more readable and less duplicated:

class MyModel < ActiveRecord::Base
  scope :active, -> { where(is_active: true) } 
end

MyModel.active # equivalent to MyModel.where(is_active: true)
Simon Ninon
  • 2,371
  • 26
  • 43
0

You can add private methods to your controllers and call them within your action methods:

class MyController < ApplicationController
  # snip ...
  def my_action
    @variable1 = get_variable1()
    @variable2 = get_variable2()
    @variable3 = get_variable3()
    @action_specific_variable = Model4.where(my_condition)
  end

  def my_other_action
    @variable1 = get_variable1()
    @variable2 = get_variable2()
    @variable3 = get_variable3()
    @action_specific_variable = Model5.where(my_other_condition)
  end

  private
  def get_variable1()
    return Model1.where(some conditions)
  end

  def get_variable2()
    return Model2.where(some other conditions)
  end

  def get_variable3()
    return Model3.where(some third conditions)
  end
end

If you need the logic to get these variables to be available across controllers, create a new utility module in you lib folder. For example, you might make a file lib/utilities.rb that contains

module Utilities
  def self.get_variable1()
    return Model1.where(some conditions)
  end

  def self.get_variable2()
    return Model2.where(some other conditions)
  end

  def self.get_variable3()
    return Model3.where(some third conditions)
  end
end

and then your controller would look like

class MyController < ApplicationController
  # snip ...
  def my_action
    @variable1 = Utilities::get_variable1()
    @variable2 = Utilities::get_variable2()
    @variable3 = Utilities::get_variable3()
    @action_specific_variable = Model4.where(my_condition)
  end

  def my_other_action
    @variable1 = Utilities::get_variable1()
    @variable2 = Utilities::get_variable2()
    @variable3 = Utilities::get_variable3()
    @action_specific_variable = Model5.where(my_other_condition)
  end
end
Kevin
  • 14,655
  • 24
  • 74
  • 124
  • For the private method part I was thinking of creating a method called `render_page_1` and have it instantialize all the required variables and then calling render. But the question then becomes, why not just set those inside the template? – revo_wolf Nov 12 '13 at 23:33
  • See my second answer. Based on your comment, it looks like it might be more of what you want to do. – Kevin Nov 12 '13 at 23:36
0

If you're looking for a way to associate complex logic with a particular view so that the logic is executed every time that view is created, even if it's generated with methods that don't exist yet, you can use Rails helpers. Every controller has a helper associated with it. For example, if you have a file app/controllers/my_controller.rb, Rails will automatically look for a file called app/helpers/my_helper.rb.

Any methods defined in a helper are available to views that are created by the associated controller. So, for example, let's say you have this controller:

def MyController < ApplicationController
  # snip ...
  def my_action
    @var = "some value"
  end
end

and a view app/views/my/my_action.html.erb:

<% variable1 = Model1.where(some conditions) %>
<% variable2 = Model2.where(some other conditions) %>
<% variable3 = Model3.where(some third conditions) %>
<%= "#{variable1} #{variable2} #{variable3} #{@var} %>

You can refactor the code that accesses the models into app/helpers/my_helper.rb:

module MyHelper
  def get_variable1()
    return Model1.where(some conditions)
  end

  def get_variable2()
    return Model2.where(some other conditions)
  end

  def get_variable3()
    return Model3.where(some third conditions)
  end
end

And refactor your view like this:

<% variable1 = get_variable1() %>
<% variable2 = get_variable2() %>
<% variable3 = get_variable3() %>
<%= "#{variable1} #{variable2} #{variable3} #{@var} %>

Without having to modify your controller.

Kevin
  • 14,655
  • 24
  • 74
  • 124
  • I asked this question and got a similar answer: http://stackoverflow.com/questions/17152262/call-a-controller-method-automatically-when-rendering-a-partial – Kevin Nov 12 '13 at 23:37
  • Helpers are really supposed to be for generating snippets of HTML, not for data access. Consider if you have to call the same helper more than once; now you are making the same call to the database twice. I would prefer seeing all data access at the controller level. – Doug R Nov 12 '13 at 23:55