
Question:
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.
Answer1: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 :)
<h2>before_action (in controller)</h2>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
<h2>before_action (in application_controller.rb)</h2>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
<h2>One more thing: model scopes</h2>
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)
Answer2: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
Answer3: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.