Dynamically display table based on conditions - ruby-on-rails

Hello my fellow programmers, I am a ruby on rails noob and need some help:
Based on today's date I am querying the db and getting the results as an array.
#cr = [#<CRate id: 1, currency: "AUD", rate: 2.0, datetime: "2013-10-09 22:59:59">,
#<CRate id: 7, currency: "USD", rate: 10.0, datetime: "2013-10-09 29:50:50">,
#<CRate id: 9, currency: "EUR", rate: 20.0, datetime: "2013-10-09 22:59:59">,
#<CRate id: 12, currency: "RUB", rate: 12.0, datetime: "2013-10-09 22:59:59">,
#<CRate id: 14, currency: "AUD", rate: 18.0, datetime: "2013-10-09 29:50:50">]
Question: I need to dynamically display the table based on currency(header), depending on the time, if rate exist then I display it else output is nil. Like this table below:
I have tried to do this:
<table>
<thead>
<th>Time</th>
<% #currency = #cr.collect(&:currency) %>
<% #currency.each do |cur| %>
<th><%= cur %></th>
<% end %>
</thead>
<tbody>
<tr class="<%= cycle('odd', 'even') %>">
<td><%= #datetime = #cr.collect(&:datetime) %></td>
<% #rate = #cr.collect(&:rate) %>
<% #rate.each do |r| %>
<td><%= r %></td>
<% end %>
</tr>
</tbody>
</table>
This doesn't work because the thead is updated with 2 AUD's and I don't know how to proceed. Please help.
Thanks a lot.

First, you need unique currency types, so line no. 4 will be:
<% #currency = #cr.collect(&:currency).uniq %>
Secondly, you need to get rates based on datetime and currency type (since you have not specified Rails version, I will try and write a working solution which might not be best solution):
<tbody>
<% #datetime = #cr.collect(&:datetime).uniq %>
<% #datetime.each do |dt| %>
<tr class="<%= cycle('odd', 'even') %>">
<td><%= dt %></td>
<% #currency.each do |curr| %>
<td><%= #cr.select {|o| o.currency == curr && o.datetime == dt}.rate %></td>
<% end %>
</tr>
<% end %>
</tbody>
HTH
Edit: This seems to be an inefficient way to solve this problem because you are relying too much on arrays in a way that they will always come in order you are expecting. I would like rather create a Hash (better known as key-value pair) of #cr like {'first_datetime' => {'AUD' => '10', 'RUB' => '20'}, 'second_datetime' => {'AUD' => '50', 'RUB' => ''}} and so forth.
Edit 2: In your controller, create a hash and 2 sets like:
#currencies = Set.new
#datetimes = Set.new
#currency_rate_datewise = Hash.new {|h, k| h[k] = {}}
#cr.each do |cr|
#currencies.add cr.currency
#datetimes.add cr.datetime
#currency_rate_datewise[cr.datetime][cr.currency] = cr.rate
end
Now your view could be simplified to:
<table>
<thead>
<th>Time</th>
<% #currencies.each do |cur| %>
<th><%= cur %></th>
<% end %>
</thead>
<tbody>
<% #datetimes.each do |datetime| %>
<tr class="<%= cycle('odd', 'even') %>">
<td><%= datetime %></td>
<% #currencies.each do |currency| %>
<td><%= #currency_rate_datewise[datetime][currency] || 'nil' %></td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
Although, I would have used group_by selectively, but I guess, it is better if you stick with learning such things at beginner's stage. I have written this from top of my head, please post a comment if something's wrong.
HTH

you can use the group_by rails helper for that. see the documentation:
group_by() Collect an enumerable into sets, grouped by the result of a
block. Useful, for example, for grouping records by date.
Example:
latest_transcripts.group_by(&:day).each do |day, transcripts|
p "#{day} -> #{transcripts.map(&:class).join(', ')}"
end
"2006-03-01 -> Transcript"
"2006-02-28 -> Transcript"
"2006-02-27 -> Transcript, Transcript"
"2006-02-26 -> Transcript, Transcript"
"2006-02-25 -> Transcript"
"2006-02-24 -> Transcript, Transcript"
"2006-02-23 -> Transcript"

Related

undefined method `each' for nil:NilClass on an erb array iteration

Im currently working in an Rails 5 application where you can search for a first name or last name and records of the customers of that account would be displayed. However I am getting a Nil object return from search algorithm.
customers_controller:
class CustomersController < ApplicationController
def index
if params[:keywords].present?
#keywords = params[:keywords]
customer_search_term = CustomerSearchTerm.new(#keywords)
#customer = Customer.where(
customer_search_term.where_clause,
customer_search_term.where_args).
order(customer_search_term.order)
else
#customers = []
end
end
end
As you can see if there is no records found is suppose to return an empty array but is returning a Nil object.
customers/index.html.erb
[![<header>
<h1 class="h2">Customer Search</h1>
</header>
<section class="search-form">
<%= form_for :customers, method: :get do |f| %>
<div class="input-group input-group-lg">
<%= label_tag :keywords, nil, class: "sr-only" %>
<%= text_field_tag :keywords, nil,
placeholder: "First Name, Last Name or Email Address",
class: "form-control input-lg" %>
<span class="input-group-btn">
<%= submit_tag "Find Customers", class: "btn btn-primary btn-lg" %>
</span>
</div>
<% end %>
</section>
<section class="search-results">
<header>
<h1 class="h3">Results</h1>
</header>
<table class="table table-striped">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Joined</th>
</tr>
</thead>
<tbody>
<% #customers.each do |customer| %>
<tr>
<td><%= customer.first_name %></td>
<td><%= customer.last_name %></td>
<td><%= customer.email %></td>
<td><%= l customer.created_at.to_date %></td>
</tr>
<% end %>
</tbody>
</table>
</section>][1]][1]
The first thing you should understand is that instance variables return nil if they haven't been set. If you say #fake_var == nil it will be true if you never defined #fake_var before this. You can contrast this with regular local variables, which will raise a NoMethodError if you try and use them before they're defined. For example, puts(fake_var) will raise a NoMethodError for fake_var.
Now look at your template. No matter what it will loop over #customers. If #customers has not been set, you'll see a NoMethodError because you can't call each on nil.
Finally, look at your controller action:
def index
if params[:keywords].present?
#keywords = params[:keywords]
customer_search_term = CustomerSearchTerm.new(#keywords)
#customer = Customer.where(
customer_search_term.where_clause,
customer_search_term.where_args).
order(customer_search_term.order)
else
#customers = []
end
end
Specifically the case when params[:keywords].present?. You never set #customers in this case so it will be nil when the template tries to access it.
I think if you simply replaced #customer = with #customers = it would solve your problem.
you can force it to return array using #to_a which converts nil to empty array
def index
return [] unless params[:keywords]
#keywords = params[:keywords]
customer_search_term = CustomerSearchTerm.new(#keywords)
#customer = Customer.where(
customer_search_term.where_clause,
customer_search_term.where_args).
order(customer_search_term.order
).to_a
end
https://apidock.com/ruby/Array/to_a

Using multiple content_tag in one method in Rails

I am trying to update a Rails 2.3 application to a newer Rails version(4/5).
I have there a method that prints a html table using a list as input, and the caller can customize the display of the rows. I also searched some existing gems that do something similar, but they don't have all the requirements I need. So I have to make this work. The code is
def model_table_2(collection, headers, options = {}, &proc)
options.reverse_merge!({
:id => nil,
:class => nil,
:style => nil,
:placeholder => 'Empty',
:caption => nil,
:summary => nil,
:footer => nil
})
placeholder_unless !collection.empty?, options[:placeholder] do
html_opt = options.slice(:id, :class, :style, :summary)
content_tag(:table, html_opt) do
table_sections = []
table_sections << content_tag(:caption, options[:caption]).to_s if options[:caption]
table_sections << content_tag(:thead,
content_tag(:tr,
headers.collect { |h|
concat(content_tag(:th, h))
}
)
)
if options[:footer]
table_sections << content_tag(:tfoot,
content_tag(:tr, content_tag(:th, concat(options[:footer]), :colspan => headers.size))
)
end
table_sections << content_tag(:tbody,
collection.each_with_index.collect do |row, row_index|
concat(
proc.call(row, cycle('odd', 'even'), row_index)
)
end.join
)
table_sections.join
end
end
end
def placeholder(message = t('general.empty'), options = {}, &proc)
# set default options
o = { :class => 'placeholder', :tag => 'p' }.merge(options)
# wrap the results of the supplied block, or just print out the message
if proc
t = o.delete(:tag)
concat tag(t, o, true), proc.binding
yield
concat "</#{t}>", proc.binding
else
content_tag o.delete(:tag), message, o
end
end
def placeholder_unless(condition, *args, &proc)
condition ? proc.call : concat(placeholder(args), proc.binding)
end
In the view file I call it like this:
<% table_cols = ["No.", "Name"] %>
<% obj_list = [{active: true, name: 'First'}, {active: true, name: 'Second'}, {active: false, name: 'Last'}, nil] %>
<%= model_table_2(obj_list, table_cols, {:class=>'table table-bordered', :caption=>'Model Table Test', :footer=>'The Footer'}) do |record, klass, row_index| -%>
<% if !record.nil? then %>
<% content_tag :tr, :class => klass + (record[:active] ? '' : ' text-muted') do -%>
<td><%= row_index+1 -%></td>
<td><%= record[:name] %></td>
<% end %>
<% else %>
<% content_tag :tr, :class => klass do -%>
<td style="text-align:center;">*</td>
<td>render form</td>
<% end %>
<% end %>
<% end %>
But the output is not how I would expect:
<table class="table table-bordered">
<th>No.</th>
<th>Name</th>
The Footer
<tr class="even">
<td>1</td>
<td>First</td>
</tr>
<tr class="odd">
<td>2</td>
<td>Second</td>
</tr>
<tr class="even text-muted">
<td>3</td>
<td>Last</td>
</tr>
<tr class="odd">
<td>*</td>
<td>render form</td>
</tr>
</table>
As you can see, some of the tags are missing, like caption, thead, tbody, tfoot. I guess it's because the content_tag calls are nested. I tried before without the table_sections array, but it didn't work either.
Also, I have an error when the list is empty, and the code goes to the placeholder... methods.
It's a weird quirk of content_tag but if you nest them you need to use a concat on each return in the inner tags. Otherwise, you just get the last returned string and the inner tags just disappear into the ether. Sadly, in my experience, I've found complex nesting isn't worth the effort of moving into a helper method.
Perhaps, a better approach would be to DRY up the html with a decorator pattern, rails partials, or using something like the cells gem.

Rails - Redirect to specific record page

I'm pretty new to Ruby on Rails and Ruby in general but I'm trying to make a small website with simple database in Ruby on Rails.
At the moment I have the html.erb pages to show, add and edit records.
The next thing i wanted to do is the action that redirects user to a page with more info about the record he clicked in the record table.
I can't really think of any way to do this.
Any help would be really appriciated.
p.s. Sorry for any mistakes in my English - it's not my first language and im still learning!
Here is my html code:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="tablecontainer">
<table class="table table-bordered table-condensed">
<tr class="success">
<td><b>Nazwa</b></td>
<td><b>Obrażenia</b></td>
<td><b>Typ</b></td>
<td><b>Waga</b></td>
<td><b>Zasięg</b></td>
<td><b>Szybkość</b></td>
<td><b>Rzadkość</b></td>
<td><b>Opcje</b></td>
</tr>
<% #biala.each do |b| %>
<tr>
<td><%= b.nazwa %></td>
<td><%= b.obrazenia %>%</td>
<td><%= b.typ %></td>
<td><%= b.waga %></td>
<td><%= b.zasieg %></td>
<td><%= b.szybkosc %></td>
<td><%= b.rzadkosc %></td>
<td><%= link_to '', {id: b.id, action: 'db_wiecejbiala'}, class: "glyphicon glyphicon-info-sign" %><%= link_to '', {id: b.id, action: 'db_edytujbiala'}, class: "glyphicon glyphicon-pencil" %> <%= link_to '', {id: b.id, action: 'usunbiala'}, data: {confirm: 'Jesteś tego pewien?'}, class: "glyphicon glyphicon-remove" %></td>
</tr>
<% end %>
</table>
And here is the controller:
class BazaController < ApplicationController
def db_bronbiala
#biala = BronBiala.all
#iloscbiala = BronBiala.count
end
def db_dodajbiala
#nowybiala = BronBiala.new
end
def utworzbiala
#nowybiala = BronBiala.new(parametrybiala)
if #nowybiala.save
redirect_to(action: 'db_bronbiala')
else
render('db_dodajbiala')
end
end
def parametrybiala
params.require(:bron_biala).permit(:nazwa, :obrazenia, :typ, :waga, :zasieg, :szybkosc, :rzadkosc, :zalety, :wady, :ciekawostki, :opis)
end
def usunbiala
usuwaniebiala = BronBiala.find(params[:id]).destroy
#biala = BronBiala.all
render('db_bronbiala')
end
def db_edytujbiala
#biala = BronBiala.all
#edytowanabiala = BronBiala.find(params[:id])
end
def aktualizujbiala
#biala = BronBiala.all
#edytowanabiala = BronBiala.find(params[:id])
if #edytowanabiala.update_attributes(parametrybiala)
redirect_to(action: 'db_bronbiala')
else
render('db_edytujbiala')
end
end
def db_wiecejbiala
#biala = BronBiala.all
#bialawiecej = BronBiala.find(params[:id])
end
end
And the db_bialawiecej code:
<div class="content">
<h2>Lista:</h2>
<div class="tablecontainer">
<table class="table table-bordered table-condensed">
<tr class="success">
<td><b>Nazwa</b></td>
<td><b>Obrażenia</b></td>
<td><b>Typ</b></td>
<td><b>Waga</b></td>
<td><b>Zasięg</b></td>
<td><b>Szybkość</b></td>
<td><b>Rzadkość</b></td>
</tr>
<% #bialawiecej.id do |b| %>
<tr>
<td><%= b.nazwa %></td>
<td><%= b.obrazenia %>%</td>
<td><%= b.typ %></td>
<td><%= b.waga %></td>
<td><%= b.zasieg %></td>
<td><%= b.szybkosc %></td>
<td><%= b.rzadkosc %></td>
</tr>
<% end %>
</div>
</div>
On click send id of clicked item (GET). you will have link similar to : localhost:3000/desired_model/5
then in action do #desired_model = DesiredModel.find(params[:id])
redirect user to desired show page.
Show data.
Next time please provide some code :)

Add link to methods to array in controller

I have a index method in my rails 4 application controller that looks like:
def index
#products = Product.all
#headers = #products.map(&:data).flat_map(&:keys).uniq
#product_data = #products.map{ |product| product[ :data ].values }
end
So #product_data ends up with something like:
[["Table", "$199.99", "blue"], ["Book", "$9.99", "green"]]
In my view, I put all of this in a unordered list. But now I'd like to have a link_to an edit and delete page for each product. How can I include this in my array, so I can display a link for each product on the view page?
You could add product_id to the result of product[:data].values array. Then use that product_id as parameter to your product url_helpers.
#product_data = products.map{ |product| product[ :data ].values.unshift(product.id) }
This should give you something similar to:
[[1, "Table", "$199.99", "blue"], [2, "Book", "$9.99", "green"]]
I see there is no use of #product_data there.Why can't You display the data in a table in your index.html.erb and you can loop through every product,so that the edit and delete links appear to every product.Assuming that you have name,price and color attributes for your product model,just do like this
In your index.html.erb:
<table border=1>
<tr>
<th>Product Name</th>
<th>Product Price</th>
<th>Product Color</th>
<th></th>
<th></th>
</tr>
<% #products.each do |p| %>
<tr>
<td><%=p.name %></td>
<td><%=p.price %></td>
<td><%=p.color %></td>
<td><%=link_to 'Edit', :action => "edit", :id => p.id %></td>
<td><%=link_to 'Delete', :action => "delete", :id => p.id, :confirm => "Are you sure?" %></td>
</tr>
<% end %>
</table>
Note:
Its just an another approach.

What is the recommended pattern for extend html class in view?

I have following <tr> tag in my table
<% if user.company.nil? %>
<tr class="error">
<% else %>
<tr>
<% end %>
<td><%= user.name %></td>
</tr>
I would like to add another if statement
<% if user.disabled? %>
<tr class="disabled">
<% end %>
So when two of this statements are true I would like to receive:
<tr class="error disabled">
I know I should move that to helper but how to write good case statment for extending class depends of this statements?
def tr_classes(user)
classes = []
classes << "error" if user.company.nil?
classes << "disabled" if user.disabled?
if classes.any?
" class=\"#{classes.join(" ")}\""
end
end
<tr<%= tr_classes(user) %>>
<td><%= user.name %></td>
</tr>
But the good style is:
def tr_classes(user)
classes = []
classes << "error" if user.company.nil?
classes << "disabled" if user.disabled?
if classes.any? # method return nil unless
classes.join(" ")
end
end
<%= content_tag :tr, :class => tr_classes(user) do -%> # if tr_classes.nil? blank <tr>
<td><%= user.name %></td>
<% end -%>
you could try a helper method, something like
def user_table_row(user)
css = ""
css = "#{css} error" if user.company.nil?
css = "#{css} disabled" if user.disabled?
content_tag :tr, class: css
end
not sure how well this will work in the case of a table row, as you will want to nest td inside it
UPDATE: here is updated version yielding the block of td code
def user_table_row(user)
css = # derive css, using string or array join style
options = {}
options[:class] = css if css.length > 0
content_tag :tr, options do
yield
end
end
then in the view
<%= user_table_row(user) do %>
<td><%= user.name %></td>
<% end %>

Resources