In a Rails index view you iterate the collection and per item in the collection data is presented. In most cases I end up with a complex view, because there is a need to do additional calculations for a specific item in the collection. Here is a typical example:
- if #projects.blank?
= render "shared/placeholder", colspan: "10"
- else
- #hours_total = 0
- #turnover_total = 0
- #projectusers_total = 0
- #projects.each do |project|
- decorate project do |decorates|
- project_calc = ProjectCalculator.new(project)
- hours = project_calc.get_project_data("hours")
- turnover = project_calc.get_project_data("turnover")
- #hours_total += hours
- #turnover_total += turnover
- #projectusers_total += project.projectusers.size
%tr
%td= link_to decorates.data_field(project.name), admin_project_path(project)
%td= decorates.data_field(project.customer.name) unless project.customer.blank?
%td= decorates.data_field(project.company.name_short)
%td= decorates.status
%td= decorates.data_field(project.projectusers.size)
%td= decorates.decimal(hours)
%td= decorates.amount(turnover)
%td= decorates.fixed_price
%td= decorates.show_progress("hours", "compact", hours)
%td= decorates.show_progress("turnover", "compact", turnover)
An improvement I already implemented is to use a decorator for view specific stuff and another one is to use service objects for the actual calculations.
But what bothers me here are all the calculations. What is a solution to make this simpler and ideally not having this in the view?
What about using a Presenter?
This link helped me a lot: http://robertomurray.co.uk/blog/2014/decorators-presenters-delegators-rails/
Here is another Short overview
In your case you could implement it like this
# app/presenter/project_presenter.rb
class ProjectPresenter
attr_accessor :project, :project_calculator
def initialize project
#project = project
#project_calculator = ProjectCalculator.new
end
def hours
project_calculator.get_project_data("hours")
end
def show_progress *args
# calculate progress
end
end
In your controller you can initialize the presenter for every project
#projects = Project.all.collect { |project| ProjectPresenter.new(project) }
So, your view can access the presenter with
- #projects.each do |project|
%tr
%td= project.show_progress('hours')
Hope it will help
ciao boris
Related
It could be a stupid question, but that is why I prefer to ask here.
I have this code below in HAML:
- #calendar[:array_number_day].each.with_index do |num_d, index|
%tr
- (0..6).each do |i|
- if (#calendar[:array_name_day][index] == i)
%td
%span #{num_d}
- else
%td
How can I break the each loop in this case ? I tried #{break} and break after %span #{num_d} but without success.
Thank's in advance for the help,
- break should work:
- #calendar[:array_number_day].each.with_index do |num_d, index|
%tr
- (0..6).each do |i|
- if (#calendar[:array_name_day][index] == i)
%td
%span #{num_d}
- break
- else
%td
Make sure you have - and use the proper indentation (it should be one indentation level deeper than the if statement.
I have a students collection result set and I need following.
Display names should be resolved following these rules: If there are no other students in the collection with the same first name, their display
name should be just their first name.
If there are multiple students in the collection with the same first name, their display name should be their first name followed by a space and their last initial(e.g. “John Smith” would resolve to “John S”)..
Try this
#results.each do |object|
displayname = (#results.select { |filter_object| filter_object.first_name == object.first_name }.count > 0) ? object.first_name : object.first_name + " " + object.last_name.initial
end
here's an example, this might not specifically be what you need (this kinda sounds like homework), but hopefully it gives you an idea.
# in Student model
attr_accessor :display_name
# in controller
students = Student.all
students.each do |student|
if students.count { |s| s.first_name == student.first_name } > 1
student.display_name = s.first_name
else
student.display_name = "#{student.first_name} #{student.last_name[0].upcase}"
end
end
# in view
<% students.each do |student| %>
<%= student.display_name %>
<% end %>
First find out duplicate first names.
dup_first_names = Student.select(:first_name).group(:first_name).group(:first_name).having("COUNT(*) > 1").uniq.pluck(:first_name)
Then for each student check whether the first name is in the dup_first_names array.
Student.all.each do |s|
if dup_first_names.include?(s.first_name)
puts "#{s.first_name} #{s.last_name.first}"
else
puts "#{s.first_name}"
end
end
I currently have the following code:
- #alpha = Glossary.find(:all, :order =>"title ASC").group_by{|u| u.title[0]}
- #glossary = Glossary.find(:all, :order =>"title ASC")
- #alpha.each do|a|
%h1= a[0]
- #glossary.each do |g|
%p display stuff
This displays all of the glossary terms under each letter rather than only the ones that begin with the letter.. I've tried a few things but I'm not sure how to select the right thing.
You should be able to do everything with your #alpha instance variable, since you're using group_by:
- #alpha = Glossary.find(:all, :order =>"title ASC").group_by{|u| u.title[0]}
- #alpha.each do |alpha, glossary_array|
%h1= alpha
- glossary_array.each do |item|
%p= item
You're close. I think you just want to do
- #alpha = Glossary.order("title ASC").group_by{|u| u.title[0]}
- #alpha.each do |letter, items|
%h1= letter
- items.each do |item|
%p= item
I have the following method in my model:
def self.set_bad_recommedation_size(rating_set)
bad = Rating.where(rating_set: rating_set).where(label: 'Bad').count
total = Rating.where(rating_set: rating_set).count
percentage_bad = (bad.to_f/total.to_f * 100)
return bad, total, percentage_bad
end
How do I call the variable bad, total, percentage_bad in my view.
What I want:
<%= "#{Model.set_bad_recommedation_size(rating_set).bad}/#{Model.set_bad_recommedation_size(rating_set).total"%>
You're better off doing:
<% bad, total, percentage_bad = Model.set_bad_recommedation_size(rating_set) %>
<%= "#{bad}/#{total}" %>
That way you're not calling the method multiple times.
I would add an intermediate helper so that your view reads better
<%= bad_recommendation_ratio(result_set) %>
Application.helper
def bad_recommendation_ratio(result_set)
bad, total = Model.set_bad_recommedation_size(rating_set)
"#{bad}/#{total}"
end
I have created a loop, to calculate a total rating of a record. To do this I am first looping through all the child records (ratings), extracting the rating from each row, adding it to the total and then outputting the total.
<% total = 0 %>
<% for ratings in #post.ratings %>
<% total = (total + ratings.rating) %>
<% end %>
<%= total %>
My question is, simply, Is this the rails way?
It achieves the desired result, although needs 5 lines to do so. I am worried I am bring old habits from other languages into my rails project, and I am hoping someone could clarify if there is an easier way.
The following, preferably in the controller, will do it succinctly:
#rating = #post.ratings.sum { &:rating }
If that seems cryptic, you might prefer
#rating = #post.ratings.inject(0) { |sum, p| sum + p.rating }
Note, however, that this will fail if any of the ratings are null, so you might want:
#rating = #post.ratings.inject(0) { |sum, p| sum + (p.rating || 0) }
You should generally keep logic out of your views. I would put that code in a helper or a controller, and the call a method to calculate the total
Put the following in your controller, then you just need to use #rating in your view:
total = 0
#rating = #post.ratings.each { |r| total += r.rating }
Or you could move it into the Post model and do something like:
def self.total_rating
total = 0
ratings.each { |r| total += r.rating }
total
end
and then simply call #post.total_rating