I have the following requirement.
Ex: There is a transaction table where it has columns say, transaction_name and amount. I want to loop through the transactions and display their details (transaction_name and amount) and finally I want to display the total amount (sum of all the amounts) in the head (before the loop) section of my page. (Think about it as a summary display)
Example page structure would be like
Sum of all the transactions - 200
transaction amount
trn1 100
trn2 50
trn3 50
And I tried to use yield and content_for tag but no luck.
my code is as follows (i'm calling inside my erb file.)
<%= yield :transaction_summary %>
<table>
<% total_amount = 0%>
<%for transaction in #transactions%>
<tr>
<td><%= transaction.transaction_name %></td>
<td><%= transaction.amount %></td>
<% total_amount += transaction.amount %>
</tr>
<%end%>
</table>
<% content_for :transaction_summary do %>
<h1>
Sum of all the transactions - <%= total_amount %>
</h1>
<% end %>
And
I'm using with inside a view (not inside a layout)
I'm using rails 2.2.2
Please help me and let me know if there is a better way
thanks in advance
cheers
sameera
EDIT:
Actually what I want to do is , Display some details before a particular loop where those details can be collected after the loop
Ex: If i have an array of transaction objects, I want to show a count of pass and failed transactions before the transactions loop in my view
thanks
In other cases where you really need to reuse content_for, the following may be useful.
In Rails 4, you can pass :flush => true as an option to content_for:
<% content_for :example, flush: true do %>
<h1>Deletes previous content</h1>
<% end %>
In Rails 3.1.1 (approx) you can delete the content from the view_flow when you yield by defining and using the following (eg in application_helper):
def yield_and_flush!(content_key)
view_flow.content.delete(content_key)
end
I think you have the wrong idea about content_for and yield. :) http://guides.rubyonrails.org/layouts_and_rendering.html
<h1>
<%= #transactions.collect(&:amount).sum -%>
</h1>
<table>
<%for transaction in #transactions%>
<tr>
<td><%= transaction.transaction_name %></td>
<td><%= transaction.amount %></td>
</tr>
<%end%>
</table>
edit -
Regarding collecting data, I suggest you put them in helper methods:
#transactions_helper.rb
def transactions_total transactions
#transactions_total ||= #transactions.collect(&:amount).sum
end
def passed_transactions transactions
#passed_transactions ||= #transactions.collect{|transaction| transaction.passed == true}
end
def failed_transactions transactions
#failed_transactions ||= transactions - passed_transactions(transactions)
end
Just noticed your comment to theTRON. The whole dry principle doesn't really apply to executing tiny logic such as looping through a array.
I would write a helper method which calculates the total separately, perhaps something along the lines of:
# app/helpers/transactions_helper.rb
def calculate_total(transactions)
total = 0
transactions.each {|transaction| total += transaction.amount}
total
end
Then you can display it in your view wherever you like, with:
<%= calculate_total(#transactions) %>
In Rails 3 you can yield after content_for; so your code will become:
<% content_for :transaction_table do %>
<table>
<% total_amount = 0%>
<% for transaction in #transactions do %>
<tr>
<td><%= transaction.transaction_name %></td>
<td><%= transaction.amount %></td>
<% total_amount += transaction.amount %>
</tr>
<%end%>
</table>
<% content_for :transaction_summary do %>
<h1>
Sum of all the transactions - <%= total_amount %>
</h1>
<% end %>
<% end %> <!- End content_for :transaction_table -->
<%= yield :transaction_summary %>
<%= yield :transaction_table %>
Note:
<% content_for :transaction_summary do %>
doesn't have to be inside of
<% content_for :transaction_table do %>
, but for some more complex cases it could.
Related
i want to achieve a nested loop without duplicates in a have and belongs to many relationship
i have a model 'campaign' and for each campaign i also have campaign data.
i want to display each campaign with its campaign data in a table. (nested)
#campaigns = current_user.campaigns
<% #campaigns.each do |item| %>
<% i = item.campaign_data %>
<% i.each do |cdata| %>
<%= cdata.date %>
<tr>
<td>
<%= item.name %>
</td>
<td>
<%= cdata.date %>
</td>
<td>
</td>
</tr>
<% end %>
<% end %>
my problem is that my campaigns get duplicated.
I want to achieve something like this:
Each campaign is listed in the table with its corresponding campaign_data directly below it, and if no campaign_data is left the next loop begins with the next campaign - is this possible?
best regard
You might be getting duplicated campaigns as you are using <%= item.name %> inside the <% i.each do |cdata| %> loop. So, if one campaign has 4 campaign_datas you will see the campaign name 4 times.
You should use naming conventions properly, if the campaign has many data campaign_data then you should specify so in association i.e. has_many :campaign_datas
Also, the Following code should be in the controller
#campaigns = current_user.campaigns.include(:campaign_datas)
Note:- I used include to avoid n + 1, please read here.
In view
<% for campaign in #campaigns %>
<% next if #campaigns.campaign_datas.blank? %>
<tr>
<td><%= item.name %></td>
</tr>
<% for campaign_data in #campaigns.campaign_datas %>
<tr>
<td><%= campaign_data.date %></td>
</tr>
<% end %>
<% end %>
Note:-
<% next if #campaigns.campaign_datas.blank? %> line is used to skip the campaign if it has no campaign data.
So i have this in my view
<% #events.each do |event| %>
<% if event.eventcomplete %>
<% else %>
<tr>
<td colspan="7">
<p>
No Events could be found.
</p>
</td>
</tr>
<% end %>
This is on my search results page,
However what im wanting if there is no eventcomplete, Just display one No events found,
At the moment i'm gettting around 100 events found but, None of them are complete. So i'm getting around 100 "No Events could be found"
Thanks for any help
Sam
I don't know what your code looks like, but something smells wrong to me that you're filtering by eventcomplete in your view both for determining which rows to display and for whether or not to show your "No results" message. Presumably you will later want to do other things using this collection (like pagination), so I'd suggest filtering the collection in the controller:
# controller code
#in_progress_events = #events.where(eventcomplete: false)
Once the collection is being properly filtered before it hits the view, check out the points in this answer for tips on display: Rails: An elegant way to display a message when there are no elements in database
Your code is missing an <% end %> tag.
<% #events.each do |event| %>
<% if event.eventcomplete %>
[insert view code to show if event.eventcomplete is true]
<% else %>
<tr>
<td colspan="7">
<p>
No Events could be found.
</p>
</td>
</tr>
<% end %>
<% end %>
Actually the code is doing exactly what it should do,
your html Block was defined in your each block, this is why it got raised 100 times.
I just fixed it with Helper Variables, but it is just a workaround
You should make use of more static methods in your models, just define it
Please make sure your business logic holds place in your models:
Fat models, Thin Controllers/Views
this should work
<% #there_is_no_eventcomplete = false %>
<% #events.each do |event| %>
<% if event.eventcomplete %>
#there_is_no_eventcomplete = true
<% end %>
<% end %>
<% if #there_is_no_eventcomplete == false %>
<tr>
<td colspan="7">
<p>
No Events could be found.
</p>
</td>
</tr>
<% end %>
You want to use a scope on your model which you can then call in the controller. I'm not sure how you've set up your model or how you determine if an event is completed, so lets assume you have a boolean attribute completed on your model:
event.rb
class Event
def self.completed
where(complete: false)
end
end
controller
#events = Event.completed
view
<% unless #events.nil?
<% #events.each do |event|%>
// your code
<% end %>
<% else %>
// your code
<% 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 am using pagination for my index page where I list all the users information like name, email etc. In the table I want to display the serial number in the order [1,2,3...]. If I user the user_id and if I delete the user the number will be missing out of sequence. I use the following code in my view
<% #user.each_with_index do |d, i| %>
<tr>
<td><%= i+1 %></td>
<% if d.profile.present? %>
<td><%= link_to d.profile.first_name+ " "+d.profile.last_name, posts_individualpostlink_path(:id => d.id) %> </td>
<% else %>
<td><%= "No Profile" %></td>
<% end %>
<td><%= d.email %></td>
<% if d.profile.present? %>
<td><%= d.profile.date_of_birth %> </td>
<% else %>
<td><%= "No Profile" %></td>
<% end %>
</tr>
<% end %>
</table>
<%= will_paginate #user %>
when I am going to the second page again the serial number starts with [1,2,....]. Per Page if i am giving 10 users, the second page should show [11, 12, 13,..... in the table.
Can anyone help me to do this. Thanks
Try with
<%
count = ((params[:page] || 1).to_i - 1) * 10
#user.each_with_index do |d, i| %>
<tr>
<td><%= count + i %></td>
Before answering question, small emotional note: stop using single letter variables in Your code. It makes it completely unreadable.
Why not use <% #user.each_with_index do |user, idx| %> ? Now in You code block it's easy to understand that You always refer to user.
Now the answer. Will paginate add page parameter to the paging links. So in You controller You should be able to do this:
#page = params[:page] || 1
After that use it to calculate correct number in Your view:
<td><%= (#page - 1) * number_of_items_on_page + i+1 %></td>
I am making some printable tables for a client with a Ruby on Rails 3.1 app and need to repeat table headers on each page. Unfortunately, at the moment, WebKit browsers do not support a CSS-based solution.
To solve this issue, I thought I would use the will_paginate gem.
Controller
def
#books = current_library.books.order('books.title ASC')
end
Current View Code
<% #books.each do |b| %>
<table>
<thead><th><%= b.title %></th></thead>
<tbody>
<% b.chapters.each do |chap| %
<td><%= chap.number %> ... <%= chap.name %></td>
<% end %>
</tbody>
</table>
<% end %>
How do I setup the pages and step through each one? In other words, how do I get all the pages of the pagination on one view page?
Alternatively, is there a better approach I should pursue?
You might be better off using Enumerable#each_slice here. It allows you to split a large enumerable object into a series of smaller slices, and then iterate on those slices. It's quite nice for this sort of thing, doesn't require any extra math in your loops, and doesn't require a gem.
Here's an example for a collection with 10 items on a page:
<% #books.each_slice(10) do |slice| %>
<h1>Header Information</h1>
<h2>Awesome</h2>
<% slice.each do |book| %>
<table>
<thead><tr><th><%= book.title %></th></tr></thead>
<tbody>
<% book.chapters.each do |chap| %
<tr><td><%= chap.number %> ... <%= chap.name %></td></tr>
<% end %>
</tbody>
</table>
<% end %>
<p>Some footer information</p>
<% end %>
This approach will only work if you assume that each book record takes about the same amount of space (so you don't end up with oversized or undersized pages), but that would be a problem with will_paginate as well.