Second week on RoR (with no programming background). And I have a bit of an issue, I'm doing a Metacritic type of a website. And there are going to be ratings everywhere. I decided on 0 to 33 = red 34 to 66 = orange 67 to 100 = green which looks like that
index (controller:show)
<td><% if show.reviews.count == 0 %>0
<% elsif show.reviews.average("rating").between?(33, 66) %>
<table class="orange">
<tr>
<td><b><%= number_with_precision(show.reviews.average("rating"), :precision => 0) %></b></td>
</tr>
</table>
<% elsif show.reviews.average("rating").between?(66, 100) %>
<table class="green">
<tr>
<td><%= number_with_precision(show.reviews.average("rating"), :precision => 0) %></td>
</tr>
</table>
<% elsif show.reviews.average("rating").between?(00, 33) %>
<table class="red">
<tr>
<td><%= number_with_precision(show.reviews.average("rating"), :precision => 0) %></td>
</tr>
</table>
<% end %>
</td>
My issue is that I'm gonna need to repeat that code, a lot, see (I'm only getting started:
show (controller show)
<p>
Note: <% if #ratings == 0 %>0
<% elsif #ratings.between?(33, 66) %>
<table class="orange">
<tr>
<td><b><%= number_with_precision(#ratings, :precision => 0) %></b></td>
</tr>
</table>
<% elsif #ratings.between?(66, 100) %>
<table class="green">
<tr>
<td><%= number_with_precision(#ratings, :precision => 0) %></td>
</tr>
</table>
<% elsif #ratings.between?(00, 33) %>
<table class="red">
<tr>
<td><%= number_with_precision(#ratings, :precision => 0) %></td>
</tr>
</table>
<% end %>
</p>
Somebody told me this should be a model but I don't really know how to write it. Any help ?
First of all you should add an instance method to your Show model that retrieves and caches the average rating for a show. This prevents querying the database multiple times for the same data:
def average_rating
#average_rating ||= self.reviews.average('rating')
end
The code that returns the appropriate css class for a Show can go into a helper (e.g. the ShowHelper):
module ShowHelper
def average_rating_class_for(show)
if show.average_rating < 34
'red'
elsif show.average_rating > 66
'green'
else
'orange'
end
end
end
With this, your views become much cleaner:
<td>
<% if show.reviews.count == 0 %>
0
<% else %>
<table class="<%= average_rating_class_for(show) %>">
<tr>
<td><%= number_with_precision(show.average_rating, :precision => 0) %></td>
</tr>
</table>
<% end %>
</td>
And:
<p>
Note:
<% if #show.reviews.count == 0 %>
0
<% else %>
<table class="<%= average_rating_class_for(#show) %>">
<tr>
<td><%= number_with_precision(#show.average_rating, :precision => 0) %></td>
</tr>
</table>
<% end %>
</p>
You could even move the generation of the entire table into a model. (Although you shouldn't be using a table here, but that's a different matter.)
module ShowHelper
def average_rating_class_for(show)
if show.average_rating < 34
'red'
elsif show.average_rating > 66
'green'
else
'orange'
end
end
def average_rating_table_for(show)
if show.reviews.count == 0
'0'
else
content_tag :table do
content_tag :tr do
contect_tag :td, :class => average_rating_class_for(show) do
number_with_precision(show.average_rating, :precision => 0)
end
end
end
end
end
end
With this you view becomes:
<td>
<%= average_rating_table_for(show) %>
</td>
What Andre suggests is possible too, but it may be a bit difficult to comprehend for a beginner like yourself. This is simpler.
You will need to make a new folder in your app directory called presenters.
Then you will want to create a file called rating_presenter.rb
This will be your presenter file
class RatingPresenter
def initialize(rating, template)
#rating = rating
#template = template
end
def get_ratings
# here you will house the logic to display your tables as needed
# I would probably determine the output to return the class to set the table accordingly
# keep in mind that view helpers are available
# ie. h.link_to or h.form_tag
end
private
def h # we don't want to be saying #template.link_to etc everywhere, so this is a shortcut
#template
end
end
And in the application_helper.rb file
We need to determine the class in order to present the class
def present(object, klass = nil)
klass ||= "#{object.class}Presenter".constantize # assign object or nil
presenter = klass.new(object, self) # assign presenter to object instance
yield presenter if block_given? # yield if block is given
presenter # return presenter
end
So in your view
You call the helper method that is then
<% present #rating do |rating_presenter| %>
<p>
<%= rating_presenter.get_ratings %>
</p>
<% end %>
So this is a quick and dirty example from a couple resources I've learned. You will need to experiment/break stuff a bit to have it suit your needs. You can expand on the get_ratings method and use more than one method to build your table ( this is recommended ) instead of having one method be responsible for the whole thing. It will help you to isolate problems. Hope this points you in the right direction
You should also search on google for "presenters +rails", you may find some more articles that will further help you understand this concept. Finally rails has gems for handling complex view logic, check out Draper. https://github.com/drapergem/draper
I think for somebody "Second week on RoR (with no programming background)" using a presenter is a little out of scope. Also one should try and simplyfy step by step and only to the level one is comfortable with (no use in using constructs one does not understand)
My advise would be to simply write a small helper to decide which colour the div should be. As a first shot simply put the following method into app/helpers/application_helper.rb
def color_for_rating(rating)
if show.average_rating < 34
'red'
elsif show.average_rating > 66
'green'
else
'orange'
end
end
you could then clean up your index view as following by using the helper to give the correct colour for your table class
<td><% if show.reviews.count == 0 %>0
<% else %>
<table class="<%= color_for_rating(show.reviews.average("rating")) %>" >
<tr>
<td><b><%= number_with_precision(show.reviews.average("rating"), :precision => 0) %></b></td>
</tr>
</table>
<% end %>
</td>
As soon as you feel comfortable with this easier approach and have used it a little you can check back to investigate the more advanced solutions given in the other answers.
Related
I'm looking to produce a looped table that has 3 results for each treatment. I want to one row to contain the date, degrees then list each individual treatment.
Controller
#trial = Trial.find(params[:trial_id])
#max_length = [#trial.treatment_selections].map(&:size).max
Model
has_many :treatment_selections, primary_key: 'trial_id'
has_many :assessments, primary_key: 'trial_id'
has_many :methods, through: :assessments
So far I have this:
<table class="table table-bordered">
<th>Date</th>
<th>Degree</th>
<% #max_length.times do |data| %>
<th><%= #trial.treatment_selections[data].try(:treatment).try(:name) %></th>
<% end %>
<% #max_length.times do |data| %>
<% #trial.methods.order(:treatment_selection_id).order("assessment_date ASC").in_groups_of(3)[data].each_with_index do |e, index| %>
<tr>
<td></td>
<td><%= Time.at(e.try(:assessment).try(:assessment_date)/1000).strftime("%d/%m/%Y") rescue 0 %></td>
<td><%= e.try(:assessment).try(:degrees) rescue 0 %></td>
<td><%= e.try(:total).round(1) rescue 0 %></td>
</tr>
<% end %>
<% end %>
</table>
Which produces this:
But i'd like it to produce this:
I would say there is at least two problems in your code:
1_ Your one liner seems to not work as you think, test the result in console to be sure it's really what you expect. We can't help you more without more details about your models.
<% #trial.methods.order(:treatment_selection_id).order("assessment_date ASC").in_groups_of(3)[data].each_with_index do |e, index| %>
2_ You can't fill columns 4 (Treatment 2), 5(Treatment 3), 6(Treatment 4) if you only have one <td>...</td> after your <td>...degre...</td>.
You probably need to put a loop here as you did for your <th>treatment x</th>
I guess each row in your last table is an assessment, and each column (treatment 1 to 4) for each assessment is a method. So you would need nested loops:
<% #trial.assessments.order("assessment_date ASC").each do |a| %>
<tr>
<td></td>
<td><%= Time.at(a.try(:assessment_date)/1000).strftime("%d/%m/%Y") rescue 0 %></td>
<td><%= a.try(:degrees) rescue 0 %></td>
<% a.methods.each do |m| %>
<td><%= m.try(:total).round(1) rescue 0 %></td>
<% end %>
</tr>
<% end %>
I'm trying to store FIFA Games, and set a scoreboard with a ranking system.
I shouldn't use logic in the view, but if I calculate them in the controller, it renders an error that the method user is not specified. When I put it in the loop, however, it recognizes it because the user is the looped item.
The app can already save games and calculate the winner. The app adds winner_id and loser_id to each game. Later in the scoreboard, I count how many current user_id's from the loop match all games' winner_id's and loser_id's. This keeps the database clean. I don't want to keep the wins and losses in the db because when a game is deleted, it shouldn't count as a win or loss anymore.
Controller:
class ScoreboardController < ApplicationController
def index
#users = User.all
end
end
VIEW:
<div class="panel panel-default" style="margin-left: 10px; margin-right:10px">
<!-- Default panel contents -->
<div class="panel-heading">Scoreboard</div>
<!-- Table -->
<table class="table">
<thead>
<th>#</th>
<th>Username</th>
<th>Ratio</th>
<th>Wins</th>
<th>Losses</th>
</thead>
<% #users.each do |user|%>
<tbody>
<td>
1
</td>
<td>
<%= user.username %>
</td>
<% if (Game.where(:winner_id => user.id).count) == 0 %>
<td>Unvalid</td>
<% elsif (Game.where(:loser_id => user.id).count) == 0 %>
<td>Unvalid</td>
<% else %>
<% #ratio = (number_with_precision((((Game.where(:winner_id => user.id).count).to_f) / (Game.where(:loser_id => user.id).count).to_f), precision: 2)) %>
<td><%= #ratio %></td>
<% end %>
<td>
<%= Game.where(:winner_id => user.id).count %>
</td>
<td>
<%= Game.where(:loser_id => user.id).count %>
</td>
<% end %>
</tbody>
</table>
</div>
I'd like to put this list in the right order. The list should be ordered by ratio. => the #ratio from the view. Can I do this directly?
In the first td, the current position is shown. It shows 1 for every user. How can I make this 1, 2, 3, ...?
You should add those methods in your User model.
class User < ActiveRecord::Base
has_many :wins, class_name: 'Game', foreign_key: 'winner_id'
has_many :losses, class_name: 'Game', foreign_key: 'loser_id'
def ratio
wins.count / losses.count.to_f * 100
end
end
then in the controller :
def index
#users = User.all.sort_by(&:ratio)
end
and in the view, use the user instance methods directly :
<%= user.wins.count %>
You should be doing it the way #ThomasHaratyk has suggested above.
Additional question: in the first td the current position is shown, for now it shows a 1 for every user, how can I make this 1, 2 , 3 , ... ?
<% #users.each_with_index do |user, index|%>
<tbody>
<td>
<%= index + 1 %>
</td>
<% end %>
For a current project, I have duplicate code between views, and I'm not sure of the best route to refactor it.
I appear to be in a position where I can have duplicate code across various .html.erb files, or I could put identical code into a partial and use conditionals. I've always heard logic should stay out of views. Neither option seems ideal, and I don't currently know of alternatives.
To illustrate my question, I created a simple rails app called animals. I scaffolded for two models: one for cat and one for dog. Images display their corresponding attributes:
Displaying #cats and #dogs is pretty much the same. Cats just have a column for meows while Dogs have a column for barks, and a dog has the additional attribute column of plays_catch.
Lets say we choose to reduce the duplicate code for displaying cats and dogs by making a shared view partial:
#views/shared/_animal.html.erb
<tr>
<td><%= animal.name %></td>
<td><%= animal.age %> </td>
<% if animal.class == Cat %>
<td><%= animal.meows %> </td>
<% end %>
<% if animal.class == Dog %>
<td><%= animal.barks %> </td>
<td><%= animal.plays_catch %> </td>
<% end %>
</tr>
Then to render #cats = Cat.all:
<%= render partial: "shared/animal", collection: #cats %>
Then to render #dogs = Dog.all:
<%= render partial: "shared/animal", collection: #dogs %>
Obviously it would be overkill to do something like this for this specific example, but the real world project I'm applying it to would not be overkill.
The overall question is: how do you remove nearly identical code that iterates over collections, where the only difference is adding/removing a column of information? It just doesn't feel right to put that logic in the view itself, and leaving the duplication feels wrong.
You could use decorators and add methods that return the extra column(s):
class DogDecorator < Draper::Decorator
def extra_columns
[:barks, plays_catch]
end
end
class CatDecorator < Draper::Decorator
def extra_columns
[:meows]
end
end
...
<% animal.extra_columns.each do |column| %>
<td><%= animal.attributes[column.to_s] %>
<% end %>
...
<% #cats = CatDecorator.decorate_collection(Cat.all)
<%= render partial: "shared/animal", collection: #cats %>
You can use respond_to? to solve the problem more generically. The view logic doesn't feel so wrong when it's more generic.
<% [:meows, :barks, :plays_catch].each do |method| %>
<% if animal.respond_to?(method) %>
<td><%= animal.send(method) %> </td>
<% end %>
<% end %>
You can add a method of the same name to both Cat and Dog classes which would return the specific instance attributes names and values. I'd recommend returning two arrays (one with the names of the fields, other with the fields' values, or vice-versa) since hashes are not exactly ordered. This way you can control the order in which they'll appear in the view.
For example:
#models/cat.rb
def fields_and_attributes
fields = ["Name","Age","Meows"]
attributes = [self.name, self.age]
if self.meows
attributes.push("Yes")
else
attributes.push("No")
end
[fields,attributes] # make sure each attribute is positioned in the same index of its corresponding field
end
#models/dog.rb
def fields_and_attributes
fields = ["Name","Age","Plays catch"]
attributes = [self.name, self.age]
if self.plays_catch
attributes.push("Yes")
else
attributes.push("No")
end
[fields,attributes] # make sure each attribute is positioned in the same index of its corresponding field
end
#controllers/animals_controller.rb
def display_animals
#animals = Cat.all + Dog.all # an array containing the different animals
end
#views/display_animals.html.erb
for i in (0...#animals.size)
fields_and_attributes = #animals[i].fields_and_attributes
for f in (0...fields_and_attributes[0].size)
<p><%= fields_and_attributes[0][f] %> : <%= fields_and_attributes[1][f] %></p>
end
end
Here, we first iterate over all of the animals and call the .fields_and_attributes method of that specific record; we then iterate over the results of calling that method, displaying fields and attributes in the same order as the one defined within the method and also guaranteeing that the code will display every field and every attribute regardless of the difference in the total number of fields for each different animal.
I don't know of any canonical way to accomplish this, but I would use one partial for this in the following way:
<tr>
<% animal.attributes.each do |_, value| %>
<td><%= value %></td>
<% end %>
</tr>
You can get rid of repeated attributes calls by providing in the partial a local variable with pre-obtained model attributes.
EDIT: if you only want to display some attributes.
# Declare whitelist of attributes
# (you can also declare a blacklist and just calculate the difference between two array: all_attributes - blacklist_attributes):
<% whitelist = [:name, :age, :barks] %>
<%= render partial: 'shared/animal',
collection: #dogs,
locals: {attrs: (#dogs.first.attributes.keys.map(&:to_sym) & whitelist)} %>
views/shared/_animal.html.erb:
<tr>
<% attrs.each do |attr| %>
<td><%= animal[attr] %></td>
<% end %>
</tr>
Below is my answer after reviewing posted answers. Basically:
I left the differences within each scaffold model's index page
I made shared partials for common table headers and table data
code below:
#app/views/cats/index.html.erb
<h1>Listing Cats</h1>
<table>
<thead>
<tr>
<%= render partial: "shared/cat_dog_table_headers" %>
<th>Meows</th>
</tr>
</thead>
<tbody>
<% #cats.each do |cat| %>
<tr>
<%= render partial: "shared/cat_dog_table_data", locals: {animal: cat} %>
<td><%= cat.meows %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Cat', new_cat_path %>
And for the dogs:
#app/views/dogs/index.html.erb
<h1>Listing Dogs</h1>
<table>
<thead>
<tr>
<%= render partial: "shared/cat_dog_table_headers" %>
<th>Barks</th>
<th>Plays catch</th>
</tr>
</thead>
<tbody>
<% #dogs.each do |dog| %>
<tr>
<%= render partial: "shared/cat_dog_table_data", locals: {animal: dog} %>
<td><%= dog.barks %></td>
<td><%= dog.plays_catch %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Dog', new_dog_path %>
The shared table headers for cats and dogs:
#app/views/shared/_cat_dog_table_headers
<td><%= Name %></td>
<td><%= Age %></td>
The shared table data for cats and dogs:
#app/views/shared/_cat_dog_table_data_headers
<td><%= animal.name %></td>
<td><%= animal.age %></td>
I want to print in tabular form the entries given by user. The table will contain 3 columns for name,registration number and classes attended, however the output is not as expected. Its first printing all the names and then all the registration numbers and so on. I want to print a name and the registration number according to name and classes attended.
</head><table width="100%">
<tr>
<th>NAME</th>
<th>REGISTRATION NUMBER</th>
<th>CLASSES ATTENDED</th>
</tr>
<tr>
<% #name_students.each do |t| %>
<th><%= t.name_student %></th><br>
<% end %>
<% #reg_nos.each do |t| %>
<th><%= t.reg_no %></th><br>
<% end %>
<% #classes_ats.each do |t| %>
<th><%= t.classes_at %></th><br>
<% end %>
</tr>
</table>
Here is my controller action.
class PagesController < ApplicationController
def home
#name_students = Page.all
#reg_nos = Page.all
#classes_ats = Page.all
end
def list
#name_students = Page.all
#reg_nos = Page.all
#classes_ats = Page.all
end
def add
Page.create(:name_student => params[:nam])
Page.create(:reg_no => params[:reg])
Page.create(:classes_at => params[:cls])
redirect_to :action => 'home'
end
end
If I understand what you're doing, you should probably have PagesController#home return something like #pages = Page.all object and display the data kind of like this:
<thead>
<tr>
<th>Name</th>
<th>Registration Number</th>
<th>Classes Attended</th>
</tr>
</thead>
<tbody>
<% #pages.each do |p| %>
<tr>
<td><%= p.name %></td>
<td><%= p.registration_number %></td>
<td><%= classes_attended(p.classes_attended) %></td>
</tr>
<% end %>
</tbody>
Above, classes_attended(p) is a call to a helper method that you would use to map the class names of the classes that student attended into an display-able array. Really, that kind of display logic might be better in a decorator, but a helper method should be fine for now.
Let me know if I've totally misunderstood what you're doing, and I'll delete my answer.
Edit to add:
Example index method:
def home
#pages = Page.all
end
Also, looking at your question again, is there a reason you're creating three Page objects with one attribute each instead of one Page object with all three attributes? It should probably look something like Page.create(:name_student => params[:nam], :reg_no => params[:reg], :classes_at => params[:cls]). That's pretty much the only way the solution I posted will work. Again, though, I might be totally misunderstanding what you're going for.
I have following line code in my view:
<td> Model.some_instance_method(args) </td>
I would like to clear this. Below i paste whole code.
https://gist.github.com/3039144
You don't make it super clear as to what your objective is. Assuming you just want it to be more idiomatic rails code, something like this would be much cleaner:
class PinnedContent < ActiveRecord::Base
def reports
PinnedContentReport.where("pinned_content_id = ?", self.id).count
end
end
In your view:
<% #reported_pinned_contents.each do |reported_pinned_content| %>
<tr>
<td><%= reported_pinned_content.id %></td>
<td><%= reported_pinned_content.reports %></td>
<td></td>
</tr>
<% end %>