'Tell, Don't Ask' whilst maintaining Separation of Concerns - ruby-on-rails

In my Rails app, I have the following association:
Video belongs to Genre (Video does not HAVE to have a genre)
Genre has many Videos (Genre can have no videos)
In the Video model, I have the following method.
# models/video.rb
def genre_name
genre.present? ? genre.name : ''
end
This is to avoid something like this in the view (which just seems messy):
# views/videos/show.html.erb
<% if #video.genre.present? %>
<%= #video.genre.name %>
<% else %>
No Genre Present
<% end %>
Instead, I can just do this (which looks much tidier)
# views/videos/show.html.erb
<%= #video.genre_name %>
However, it doesn't feel right asking for information about the genre in the Video model. What's the best way to organise this code? Should I be using helpers instead?

If you find yourself doing this kind of thing a lot, it may be worth looking into a decorator pattern, which can house view logic like this. (I quite enjoy using Draper for this purpose, but it's not very difficult to roll your own naive implementation.)
Then, your decorator logic can look like this:
class VideoDecorator
def genre_name
object.genre.try(:name).presence || "Fallback"
end
end
You can wrap up the model in a decorator as you render in the controller:
#video = Video.find(params[:id])
respond_with #video.decorate
And your view 'logic' (or lack thereof) can look like this application-wide:
<%= #video.genre_name %>
Thoughtbot has an excellent explanation of the decorator pattern here

You could write in your view
<%= #video.genre.try(:name) || 'No Genre Present' %>
If you don't need the fallback text, just
<%= #video.genre.try(:name) %>
Read more about Object#try here.
If you want the fallback also when name is an empty string (not just nil) you can use Object#presence
<%= #video.genre.try(:name).presence || 'No Genre Present' %>

Related

Render a partial based on model attribute

Just wondering if there is a way to make the following code more concise
<% #users.each do |user| %>
<%= render partial: "#{user.state}_user", locals: { user: user } %>
<% end %>
Each user has a state (either active or inactive) and, for each user, there correct partial (_active_user.hmtl.erb or _inactive_user.html.erb needs to be rendered).
Looking for using collection but I can't find any sample.
You could override ActiveModel::Conversion#to_partial_path which is how render looks up the partial implicitly:
class User < ApplicationRecord
# ...
def to_partial_path
"users/#{state}_user"
end
end
But that will override how its rendered implicitly everywhere which may not be desirable. Otherwise I would question if you really need to make this more succinct - what you are doing is off the rails and its better to have code that clearly shows its intent instead of something overly clever / force DRYied.
The reason you haven't found any examples using collection is that its not going to work. It iterates through the collection and calls #to_partial_path on each member.

Move logic like this to the controller or model rather than the view?

I have this logic currently in my view
<% tools_count = #job.tools.count - 1 %>
<% count = 0 %>
<% #job.tools.each do |u|%>
<%= u.name %>
<% if count != tools_count %>
<% count += 1 %>
<%= "," %>
<%end%>
<% end %>
Which just loops through some users relations and puts in a , unless it is the end of the list.
My question: This kind of logic looks really messy and clogs up my views I know there must be a better way of doing this by moving it into the controller or maybe model, does anyone know the correct way to do this kind of logic?
You can add a method like this to your Job model:
def tool_names
tools.map(&:name).join(',')
end
And use it in your view like this:
<%= #job.tool_names %>
There are couple of ways to avoid putting this kind of logic in the view layer:
Create an instance method in the model class (as spickermann suggested)
This will work for simple logic and simple projects. However, when you will want to use some helpers from ActionView::Helpers such as jobs_path or number_to_currency, a model is not a good place for it.
Create a helper method in helper modules eq. JobHelpers
Generally you can put any helper methods related to view layer in helpers. For example to share common methods for building a view components.
Use the decorator/presenter pattern and put there the view logic so model won't be polluted. Here is some more explanation about the pattern and sample implementation using draper gem: http://johnotander.com/rails/2014/03/07/decorators-on-rails/
You can do it in a single line like
<%= #job.tools.map(&:name).join(',') %>

Rails - moving out calculations from my views?

Currently I'm performing some calculations in my views, which is a bad thing, of course:
<% categories.each do |c| %>
....
<%= c.transactions.sum("amount_cents") %>
....
<% end %>
I am researching ways that will help me to refactor the above issue.
One thing is to move the calculation to my controller
#category_sum = #transaction.sum("amount_cents")
Which is probably a better solution, but you know. Not perfect.
Since I have many users, I do not see how can I move the calculator logic into my Model. So I guess I might need to use a new Class, create a bunch of methods (sum, average, etc.) and use them in the views? Am I on the right track? Will be thankful for any advice on how to restructure my code and design and implement this Class.
One mean to isolate view logic is to use presenters.
A presenter allows you to do something like that :
<% categories.each do |c| %>
....
<% present c do |category| %>
<%= category.transaction_sum %>
<% end %>
....
<% end %>
You then have a presenter class in app/presenters/category_presenter.rb :
class CategoryPresenter < BasePresenter
presents :category
def transaction_sum
category.transactions.sum("amount_cents")
end
end
Of course, it is best used if you have many methods in that presenter (but once you begins to reduce view logic, it's quick to fill presenters).
The implementation used here rely on what is describe in this pro railscast. The basic idea is simply to have a #present helper that infers a class name based on object class, load and initialize the proper presenter class.
An other popular alternative is to use drapper, which use the concept of decorator, but a presenter is basically a decorator.
The main code smell you're seeing is called the law of Demeter (which, like many programming "laws", you should think of it more like a "guideline of Demeter").
What you could do is move the actual calculation step into a method on the category, e.g.
class Category < ActiveRecord::Base
def transaction_amount
transactions.sum("amount_cents")
end
end
<% categories.each do |c| %>
....
<%= c.transaction_amount %>
....
<% end %>
Technically speaking the calculation is still performed while rendering the view, but the logic for how that summed amount gets calculated is no longer inside the view itself. All the view now cares about is that it can send the message transaction_amount to the category objects. This also leaves room for you to add a cache for the sums, or for you to stop passing actual records, but instead pass static objects (that aren't ActiveRecord models) that come out of some piece of code that performs the sum in a more efficient manner.

How to display a tag cloud from Acts as Taggable On on an index page in Ruby on Rails 3.2.7?

I'm very new to Ruby on Rails, so there's probably a simple solution I'm missing.
The tldr version - how do I display an Acts As Taggable On tag cloud of distinct (i.e. no repeating) tags assigned to all instances of a particular model on that model's index page?
The longer version - I have a model called Video in which I have successfully managed to implement a tagging feature using Acts as Taggable On and this fantastic tutorial.
What I'd like to do now is, on the Video's index page (index.html.erb), to display a summary of all the individual tags that a user has assigned to individual videos. For example, lets say I have three videos, each tagged as follows:
Video 1: great, banana, book
Video 2: small, great, apple
Video 3: rubbish, small, banana
I'd like the index page to display the following list of tags:
great, banana, book, small, apple, rubbish.
The code for my model (elided) is as follows:
class Video < ActiveRecord::Base
attr_accessible :tag_list # Lots of other fields in here as well, but not relevant
acts_as_taggable_on :tags
end
The code in my Video helper is as follows:
module VideosHelper
include ActsAsTaggableOn::TagsHelper
end
Finally, as per the gem's documentation, I've added the following code to my controller:
class VideosController < ApplicationController
def tag_cloud
#tags = Video.tag_counts_on(:tags)
end
end
So, what code should I be adding to the index page of my view? I tried the following, again as per the documentation:
<% tag_cloud(#tags, %w(css1 css2 css3 css4)) do |tag, css_class| %>
<%= link_to tag.name, { :action => :tag, :id => tag.name }, :class => css_class %>
<% end %>
But this returns the following error when I go to the Video index page:
undefined method `empty?' for nil:NilClass
As I say, I'm obviously missing something simple, but I'm completely new to Rails (and Ruby) so I'm still finding my feet.
OK, after hacking about a bit, I think I've found a solution, in case anyone else wondering how to do this happens to stumble across this question.
However, please be aware that I am very much a beginner at RoR, so this is probably not the best solution - if I'm doing anything wrong, or if you have a better solution, feel free to let me know!
Add this code in your view to display the list of tags for a particular model in order:
#tags = ActsAsTaggableOn::Tag.all(:order=>'name')
<% if #tags.count > 0 %>
<ul>
<% #tags.each do |tag| %>
<li><%= link_to tag.name, tagged_url(:tag => tag.name) %></li>
<% end %>
</ul>
<% else %>
<p>There are no tags on the system.</p>
<% end %>
This results in a very basic display and, due to my inexperience I advise using this approach with caution - I'm sure it's not the best, or even the "safest", method, so beware!

Rails Check if User Id is in Array

I'm trying to build a condition based on wether or not a "user" is a "member". Basically I need a way of checking if the current_user.id matches any of the user_id of any members. The non-working code I have right now is:
<% if current_user = #page.members %>
you can view this content.
<% end %>
I'm looking for something along the lines of: "If current_user.id exists in the "user_id" of any members."
Something like this, based on the field names in your question:
<% if #page.members.map(&:user_id).include? current_user.id %>
You can view this content
<% end %>
Assuming your #page.members variable contains an array, you can use the include? method:
<% if #page.members.include? current_user %>
you can view this content.
<% end %>
If you're using an array of ids, you will of course need to change the test slightly to look for the current user's id:
<% if #page.members.include? current_user.id %>
you can view this content.
<% end %>
#member_ids = #page.members.map{|m| m.id()}
then check for the condition as below
#memeber_ids.include? current_user.id()
Has said before include? should do the thing.
I'm just answering to tell you about a gem called CanCan, that gives you easy access for authorization "helpers".
Why you should use CanCan instead of doing what you are actually doing?
Don't reinventing the weel most of the times it's a goob practice.
You are placing business logic on the view (bad practice).
CanCan most likely has been developed thinking on security, and all the best practices in mind.
You save some developing hours.
Sorry if I repeated myself.

Resources