Rails Model, View, Controller, and Helper: what goes where?
MVC
Controller: Put code here that has to do with working out what a user wants, and deciding what to give them, working out whether they are logged in, whether they should see certain data, etc. In the end, the controller looks at requests and works out what data (Models) to show and what Views to render. If you are in doubt about whether code should go in the controller, then it probably shouldn't. Keep your controllers skinny.
View: The view should only contain the minimum code to display your data (Model), it shouldn't do lots of processing or calculating, it should be displaying data calculated (or summarized) by the Model, or generated from the Controller. If your View really needs to do processing that can't be done by the Model or Controller, put the code in a Helper. Lots of Ruby code in a View makes the pages markup hard to read.
Model: Your model should be where all your code that relates to your data (the entities that make up your site e.g. Users, Post, Accounts, Friends etc.) lives. If code needs to save, update or summarise data related to your entities, put it here. It will be re-usable across your Views and Controllers.
Where to place Rails code that is not a model, view, controller or helper?
If the code is something like modules with utility methods in these could be placed in the lib
folder. Alternatively you could create a common superclass for some of your controllers if they share behaviour.
Please post an example of the kind of code you're thinking of.
Where should I put this, in the model, a helper, or a controller?
Calculating the average belongs in the model:
class School < ActiveRecord::Base
...
def average_review_rating
return nil if reviews.blank?
reviews.average(:rating)
end
end
Rounding the average belongs in the view, because it is formatting. Put calculation in the model, and formatting in the view (or a helper).
<%= @school.average_review_rating.round(1) if @school.average_review_rating %>
This can be shorted considerably using the andand gem.
<%= @school.average_review_rating.andand.round(1) %>
You may wish to push the rounding into the helper, where it can be independently tested:
class SchoolHelper
def format_rating(n)
n.andand.round(1)
end
end
which is used like this:
<%= format_rating(@school.average_review_rating) %>
Helper method or Model or Controller?
The general answer depends on how often you're going to use it:
- helper: used often but only in views or controllers
- model: used often anywhere the model can be used (other models)
- controller: used rarely and only for specific actions.
However in your case, the answer is none of the above, and stop trying to reinvent the wheel. About 95% of the things people try to do with Rails are tasks that others have already done. There's a very good chance that it's either in the Rails Core or exist in either gem or plugin form.
What you're trying to do has already been done, and is built into the Rails core. It's a ActionView::Helpers::FormOpitionsHelper method called collection_select
collection_select does exactly what you want to do, it's also much more robust than a single purpose method.
It has the form of
collection_select(object, method, collection, value_method,
text_method, select_options = {}, html_options)
value_method and text_method are sent to each item in collection to get the select value and the display text for each select option. It is not required that either are column names.
Use it like this:
<% form_for @whatever do |form| %>
<%= form.collection_select :user_id, User.all, :id,
:full_name, :prompt => "Select a User" %>
<% end %>
Using View page, Controller and Helper
You no need to connect views and helper as by default all the helper modules are included in the views.
And do include the helper in you controller. Helper is a module and controller is a class. Just include the module in the class.
To get the clear picture please post exactly your structure.
Rails : dynamically add helper to controller and access it in view
Create your own render controller.
module Core
class RenderController < ActionController::Base
include Core::ApplicationHelper
include ::YourCustomHelper
end
end
then use it for render_to_string
::Core::RenderController.new.render_to_string('agreement_files/shared/a.html.erb', locals: { a: @a , b: @b })
Rails - helper method in controller available in the model?
You can do something like this to access the instance variable declared in controller from model
class User < ActiveRecord::Base
cattr_accessor :current_user
end
class ApplicationController < ActionController::Base
before_filter :current_user
def current_user
@current_user ||= User.find(session[:user_id])
User.current_user = @current_user
end
end
end
Rails: Helpers and Models - where to organize code
Your specific example is including a business rule in the sense that if the instance of the model is both purchased and confirmed then the proper status is "purchased" not "confirmed"
So in your example, I'd definitely put the method in the model since it is coding one of your applications business rules.
A different example:
def status_string
case status
when 0: "Purchased"
when 1: "Confirmed"
else
"Pending"
end
end
In this case, the status_string method could be reasonably defined either in a View Helper or Model--it has nothing to do with any business rules, it is changing the representation of the value. I'd put it in the model since I tend to only put html-related sw into the View Helpers. But depending on your internationalization scheme, a similar method might be better placed in the View Helper.
A good example of a View Helper is an application-wide method to transform date time values into the standard representation for your app. Eg
# application_helper.rb
def date_long_s(d)
d.strftime("%A, %b *%d, %Y *%I:%M %p")
end
Get data from Helper or use Model in Rails
I would go with (1) your helper method for now. It's simple and straightforward. As I said in the comments, you could use a decorator around your model (using draper for e.g.) to add what I'd consider quite view-specific logic if you are tempted towards option (2).
One note on your helper methods - use pluck
instead of collect
so you don't select columns or instantiate a bunch of objects you don't need.
Also, order
defaults to asc
, so you can shorten the whole thing to:
def users_for_select
User.order(:email).pluck(:email, :id)
end
Related Topics
How to Use Active Support Core Extensions
Really Cheap Command-Line Option Parsing in Ruby
How to Check a Checkbox in Capybara
Convert Array of 2-Element Arrays into a Hash, Where Duplicate Keys Append Additional Values
Iterate Over Ruby Time Object With Delta
How to Create a Private Class Method
Libmysqlclient.So.15: Cannot Open Shared Object File: No Such File or Directory
How to Install Ruby-Debug When Needing Necessary Libraries And/Or Headers
Ruby: How to Install a Specific Version of a Ruby Gem
How to Wrap Link_To Around Some HTML Ruby Code
Undefined Method Raise_In_Transactional_Callbacks=' for Activerecord::Base:Class (Nomethoderror)
How to Remove Blank Elements from an Array
Undefined Method 'Visit' When Using Rspec and Capybara in Rails
Ruby Gem For Finding Timezone of Location
Ruby: Problem Installing Eventmachine Under Windows 7
Difference Between Gemfile and Gemfile.Lock in Ruby on Rails