When to use Helpers vs Model - ruby-on-rails

I'm new to Rails and just wondering when I should put code into a Helper as opposed to putting the code into the Model.
Is there a 'rule of thumb' so to speak for this?

Use helpers if you're working in a view (template) and you need to build a complex bit of HTML such as a <table>. Or, if you want to change some presentation data that's not connected to the database.
def truncate_html( html, options = {} )
options[:length] = 35 unless options[:length]
truncate( strip_tags( html ), options )
end
Use models when you're working with database objects, and you want to simplify the business logic.
def one_day?
start_date.to_s[0,9] == end_date.to_s[0,9]
end
Here's Helpers in the guides: http://guides.rubyonrails.org/form_helpers.html
And here's Models: http://guides.rubyonrails.org/active_record_querying.html

It's best to use helpers when the code that the helper is creating is meant to be displayed in the view only. For example if you want to have methods that help create HTML links, they should go in the helper:
def easy_link user
link_to(user.name, user)
end
If your code is business logic it should go in your models. You should also aim to put as much business logic in your models, you don't want this code in your views and controllers. For example, if you want to process an order, that code should go in the model:
def process
raise NotReadyToProcess unless ready_to_process?
raise NotValidPaymentDetails unless valid_payment_details?
process_payment
end

Helpers should only contain logic for the view
Models should contain only logic related to the object modeled, never related with the transaction performed neither the view rendered

Related

Testing an association model helper method rails rspec

I have two models, User and Account.
# account.rb
belongs_to :user
# user.rb
has_one :account
Account has an attribute name. And in my views, I was calling current_user.account.name multiple times, and I heard that's not the great of a way to do it. So I was incredibly swift, and I created the following method in my user.rb
def account_name
self.account.name
end
So now in my view, I can simply call current_user.account_name, and if the association changes, I only update it in one place. BUT my question is, do I test this method? If I do, how do I test it without any mystery guests?
I agree there is nothing wrong with current_user.account.name - while Sandi Metz would tell us "User knows too much about Account" this is kind of the thing you can't really avoid w/ Active Record.
If you found you were doing a lot of these methods all over the User model you could use the rails delegate method:
delegate :name, :to => :account, :prefix => true
using the :prefix => true option will prefix the method in the User model so it is account_name. In this case I would assume you could write a very simple unit test on the method that it returns something just incase the attribute in account would ever change your test would fail so you would know you need to update the delegate method.
There's nothing wrong with current_user.account.name
There's no difference between calling it as current_user.account.name, or making current_user.account_name call it for you
You're probably not calling current_user in the model, like you say
You should have a spec for it if you use it
Personally I see no good reason for any of this. Just use current_user.account.name.
If you are worrying about efficiency, have current_user return a user that joins account.
This is going to be a bit off-topic. So, apologies in advance if it's not interesting or helpful.
TL;DR: Don't put knowledge of your models in your views. Keep your controllers skinny. Here's how I've been doing it.
In my current project, I've been working to make sure my views have absolutely no knowledge of anything about the rest of the system (to reduce coupling). This way, if you decide to change how you implement something (say, current_user.account.name versus current_user.account_name), then you don't have to go into your views and make changes.
Every controller action provides a #results hash that contains everything the view needs to render correctly. The structure of the #results hash is essentially a contract between the view and the controller.
So, in my controller, #results might look something like {current_user: {account: {name: 'foo'}}}. And in my view, I'd do something like #results[:current_user][:account][:name]. I like using a HashWithIndifferentAccess so I could also do #results['current_user']['account']['name'] and not have things blow up or misbehave.
Also, I've been moving as much logic as I can out of controllers into service objects (I call them 'managers'). I find my managers (which are POROs) a lot easier to test than controllers. So, I might have:
# app/controllers/some_controller.rb
class SomeController
def create
#results = SomeManager.create(params)
if #results[:success]
# happy routing
else
# sad routing
end
end
end
Now, my controllers are super skinny and contain no logic other than routing. They don't know anything about my models. (In fact, almost all of my controller actions look exactly the same with essentially the same six lines of code.) Again, I like this because it creates separation.
Naturally, I need the manager:
#app/managers/some_manager.rb
class SomeManager
class << self
def create(params)
# do stuff that ends up creating the #results hash
# if things went well, the return will include success: true
# if things did not go well, the return will not include a :success key
end
end
end
So, in truth, the structure of #results is a contract between the view and the manager, not between the view and the controller.

Ruby on Rails: proper way to return data from methods

How do I follow OOP standards within RoR controllers?
The setup: submitting data to a form & then manipulating it for display. This is a simplified example.
app/controllers/discounts_controller.rb
...
def show
#discount = Discount.find(params[:id])
formatted = calc_discounts(#discount)
end
...
private
calc_discounts
half_off = #discount.orig_price * .5
quarter_off = #discount.orig_price * .25
return {:half => half_off, :quarter => quarter_off}
end
...
Or is it better to place this in a library with attr_accessor and then create new instances of the library class within the controller? Or is there an even better way of accomplishing this?
The question to ask yourself is "is this logic useful for the view, model, or both?"
If the answer is that it's only useful for display purposes, I would put that logic in a view helper. If it's also beneficial to the model, put it there. Maybe something like this:
class Discount
def options
{half: (self.orig_price * .5), quarter: (self.orig_price * .25)}
end
end
Then in your controller you can just locate the record in question:
def show
#discount = Discount.find(params[:id])
end
And display it in the view:
<h1>half: <%= #discount.options[:half] %> </h1>
<h1>quarter: <%= #discount.options[:quarter] %> </h1>
Well, you can can add half_off and quarter_off as methods to your model:
class Discount < ActiveRecord::Base
def half_off
orig_price * 0.5
end
def quarter_off
orig_price * 0.25
end
end
.. and then do the following:
def show
#discount = Discount.find(params[:id])
end
Now you can call #discount.half_off and #discount.quarter_off in your view..
First off, you've got some syntax issues there. When you define methods you need to use a def keyword, and since Ruby 1.9 you can use a shortcut when defining hashes that avoids hashrockets, so it's:
def calc_discounts
half_off = #discount.orig_price * .5
quarter_off = #discount.orig_price * .25
return {half: half_off, quarter: quarter_off}
end
Also, you defined a local variable formatter inside of your controller's show method. This doesn't actually do anything but assign some values to a variable that only exists within that method. Only the controller's instance variables (variables with an #) can be passed to the view.
That being said, the best practice in RoR is to keep controllers "skinny", which means only using controllers to authenticate, authorize, load a model, assign an instance variable for you view, handle errors with any of the former, and then render the view according to the format requested.
It's another best practice not to include much logic in your views. This way, your logic can be shared with and reused by other views instead of having to be re-written for each new view you make. It also makes your views more readable, as they will read like simple lists of what is to be shown instead of making people try to decipher embedded ruby all over the place.
If the code is something that one of your other models could benefit from being able to use, put it inside your model code (or make a new plain old Ruby object if the logic is complex or not really cohesive with the existing model).
If the logic is something that is just for making a view prettier or in a better format, but won't actually be used by the models, then it should go in some type of view helper or decorator.

Organizing site navigation actions in Rails

I'm new to Rails (I've worked in MVC but not that much) and I'm trying to do things the "right" way but I'm a little confused here.
I have a site navigation with filters Items by different criteria, meaning:
Items.popular
Items.recommended
User.items
Brand.items # by the parent brand
Category.items # by a category
The problem is that I don't know how to deal with this in the controller, where each action does a similar logic for each collection of items (for example, store in session and respond to js)
Either I have an action in ItemsController for every filter (big controller) or I put it in ItemsController BrandsController, CategoriesController (repeated logic), but neither provides a "clean" controller.
But I don't know witch one is better or if I should do something else.
Thanks in advance!
You're asking two separate questions. Items.popular and Items.recommended are best achieved in your Item model as a named scope This abstracts what Xavier recommended into the model. Then in your ItemsController, you'd have something like
def popular
#items = Item.popular
end
def recommended
#items = Item.recommended
end
This isn't functionally different than what Xavier recommended, but to me, it is more understandable. (I always try to write my code for the version of me that will come to it in six months to not wonder what the guy clacking on the keyboard was thinking.)
The second thing you're asking is about nested resources. Assuming your code reads something like:
class User
has_many :items
end
then you can route through a user to that user's items by including
resources :users do
resources :items
end
in your routes.rb file. Repeat for the other nested resources.
The last thing you said is
The problem is that I don't know how to deal with this in the controller, where each action does a similar logic for each collection of items (for example, store in session and respond to js)
If what I've said above doesn't solve this for you (I think it would unless there's a piece you've left out.) this sounds like a case for subclassing. Put the common code in the superclass, do the specific stuff in the subclass and call super.
There's a pretty convenient way to handle this, actually - you just have to be careful and sanitize things, as it involves getting input from the browser pretty close to your database. Basically, in ItemsController, you have a function that looks a lot like this:
def search
#items = Item.where(params[:item_criteria])
end
Scary, no? But effective! For security, I recommend something like:
def search
searchable_attrs = [...] #Possibly load this straight from the model
conditions = params[:item_criteria].keep_if do |k, v|
searchable_attrs.contains? k
end
conditions[:must_be_false] = false
#items = Item.where(conditions)
end
Those first four lines used to be doable with ActiveSupport's Hash#slice method, but that's been deprecated. I assume there's a new version somewhere, since it's so useful, but I'm not sure what it is.
Hope that helps!
I think both answers(#Xaviers and #jxpx777's) is good but should be used in different situations. If your view is exactly the same for popular and recommended items then i think you should use the same action for them both. Especially if this is only a way to filter your index page, and you want a way to filter for both recommended and popular items at the same time. Or maybe popular items belonging to a specific users? However if the views are different then you should use different actions too.
The same applies to the nested resource (user's, brand's and category's items). So a complete index action could look something like this:
# Items controller
before_filter :parent_resource
def index
if #parent
#items = #parent.items
else
#items = Item.scoped
end
if params[:item_criteria]
#items = #items.where(params[:item_criteria])
end
end
private
def parent_resource
#parent = if params[:user_id]
User.find(params[:user_id])
elsif params[:brand_id]
Brand.find(params[:brand_id])
elsif params[:category_id]
Category.find(params[:category_id])
end
end

Best way to create a "link" method in Rails (model or helper)?

I've done a lot of research on this topic and there seems to be some dispute, so I wanted to get your opinions. Here is my basic situation - I have a User model:
class User < ActiveRecord::Base
# User consists of first_name, last_name, and other fields
has_one :profile # 1-1 mapping between User and Profile
# Profile is a nested resource of User
# this is the method up for debate:
# this obviously doesn't work unless I include
# the necessary modules in this class
def link(*args)
link_to self.first_name, users_profile_path(self), args
end
end
My reasoning for this kind of behavior is that, in my views, I'd like to do something like:
<%= #user.link %>
instead of:
<%= link_to #user.name, users_profile_path(#user) ... %>
every time. This link will be used thousands of times, in many different views. I want to centralize this "method" so that, when I need to make a change, I can make it once.
However, this practice absolutely violates the MVC architecture. Others suggest using a helper:
module UsersHelper
def profile_link(user, *args)
link_to user.name, users_profile_path(user), args
end
end
Now, I have to wrap the user in the method instead of calling it as a method ON user:
<%= profile_link(#user) %>
Which, in my opinion, is uglier than the latter example.
So my question is - which is better?? Or is there a way to accomplish this that I'm completely unaware of?
Rails is all about coding by convention. As you've pointed out, using a method in the model breaks the conventions of MVC. Unless there's a compelling reason to do so, you're better off going with the flow, and using the helper approach.
One practical issue is testing: you'll find it easier to test the helper method than the model method. Helper tests will include the link_to and users_profile_path methods for you -- model tests won't.
Finally, think of other developers reading your code. Where would they expect to find this method? If you follow MVC, you'll make their lives easier.
Use the helper. Because this is a method that creates a view object (the anchor tag), it's best to put it in a helper module.
Take a look at ActiveRecord's to_param method: http://api.rubyonrails.org/classes/ActiveRecord/Base.html#method-i-to_param

What's the correct way to run one controller action from another controller action without an HTTP redirect?

I'd like to be able to dispatch from one controller action to another conditionally, based on a combination of query parameters and data in the database.
What I have right now is something like:
class OldController < ApplicationController
def old_controller_action
if should_use_new_controller
new_params = params.dup
new_params[:controller] = "new_controller_action"
redirect_to new_params
return
end
# rest of old and busted
end
end
class NewController < ApplicationController
def new_controller_action
# new hotness
end
end
This works just fine, but it issues an HTTP redirect, which is slow. I'd like to be able to do this same thing, but within the same HTTP request.
Is there a clean way to do this?
Edit: The bounty will go to someone who can show me a clean way to do this that leaves the controllers and their actions relatively untouched (other than the redirect code itself).
Instead of calling code across actions, extract the code to lib/ or something, and call that code from both controllers.
# lib/foo.rb
module Foo
def self.bar
# ...
end
end
# posts_controller
def index
Foo.bar
end
# things_controller
def index
Foo.bar
end
Create an instance of the controller class:
#my_other_controller = MyOtherController.new
Then call methods on it:
#my_other_controller.some_method(params[:id])
I prefer the module idea, but this should do the trick.
You can also pass parameters as a whole from another controller:
#my_other_controller.params = params
I suspect you want option 3, but lets go through the some alternatives first
Option 1 - Push the controller selection logic into a helper that inserts the right link into your view. Benifits - controllers remain clean, Cons - if decision logic depending on submitted values this approach won't work. If URL is being called by external websites then this won't work.
Option 2 - Push the logic back into your model. Pro's - keeps controller clean. Cons - doesn't work well if you've got lots of sesson, params or render / redirect_to interaction.
Option 3 - Stay within the same controller. I suspect you are trying to replace some existing functionality with some new functionality, but only in some cases. Pro's - Simple and have access to everything you need. Cons - only works if it makes sense to use the same controller i.e. you're working with the same entity such as user, place or company.
Lets look an an example for option 3. My links controller has totally diferent behavour for admins than other users ...
class LinksController < ApplicationController
#...
def new
#Check params and db values to make a choice here
admin? ? new_admin : new_user
end
#...
private
def new_admin
#All of the good stuff - can use params, flash, etc
render :action => 'new_admin'
end
def new_user
#All of the good stuff - can use params, flash, etc
render :action => 'new_user'
end
end
If two controllers are trying to do the same thing, there's a very good chance this should be in a model. Take a good look at your design and -- I'm sorry I don't know your experience level with MVC -- read up on thin controller techniques:
http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model
http://www.robbyonrails.com/articles/2007/06/19/put-your-controllers-on-a-diet-already
http://andrzejonsoftware.blogspot.com/2008/07/mvc-how-to-write-controllers.html
If the problem is that you need the other controller to do the render, then maybe the route should have pointed there to begin with, and still the skinny controller technique should save the day.
If extracting the common code between controllers into a module doesn't work for you, I would use Rack middleware. I haven't seen code that uses ActiveRecord within middleware but I don't know of any reason why it shouldn't be possible since people have used Redis and the like.
Otherwise I think your only option would be to restart processing of the request with something like (untested, pseudo example):
env['REQUEST_URI'] = new_controller_uri_with_your_params
call(env)
This is similar to how integration tests are implemented. But I don't know if everything from call until you hit a controller is idempotent and safe to rerun like this. You could trace through the source and see. But even if it's ok now, it might break in any future version of rails or rack.
Using middleware would avoid this by letting you intercept the request before it's been run. You should still be able to share code with your rails application by extracting it out into common modules included in both places.
Honestly I think just doing the simple thing of factoring the common controller code is likely cleaner, but it's hard to know without the details of your situation so I thought I'd go ahead and suggest this.
Do this:
class OldController < ApplicationController
def old_controller_action
if should_use_new_controller
new_controller_action
end
# rest of old and busted
end
end
and the new controller
class NewController < OldController
def new_controller_action
# new hotness
end
end

Resources