Filter and map by grouping data - ruby-on-rails

I have an array of objects, where I have the following fields: title, name e url
My data:
#array = [
{ id: 1, title: '123', name: 123 },
{ id: 2, title: '123', name: 321 },
{ id: 3, title: '1234', name: 123 },
]
I need to group my data by title and then make the map with the names
<%= #array.map do |x| %>
<div class='col-md-12'>
<h3><%= x.title %></h3>
<div class="row">
<%= #array.filter do |y| %>
<div class="col-4">
<% if y.title === x.title %>
<p><%= t.name %></p>
<% end %>
</div>
<% end.join.html_safe %>
</div>
</div>
<% end.join.html_safe %>
The return is:
title = '123'
name = '123'
name = '321'
title = '123'
name = '123'
name = '321'
title = '123'
name = '123'
That is, my array comes duplicated ... how to proceed?
Remembering that I am inside the rails view

You're getting duplicate entries since you're mapping over all the elements and filtering the array for every element inside the block.
You can use Enumerable#group_by to group the array elements by title. You can then iterate over the groups and display the title and names of each group. Note that I'm using item[:name] to access the values from the hash since your question mentions an array of hashes. You should update it to item.name (and other places using hash access) if you're working with objects instead.
#groups = #array.group_by {|i| i[:title] }
<% #groups.each do |title, items| %>
<h3><%= title %></h3>
<% items.each do |item| %>
<p><%= item[:name] %></p>
<% end %>
<% end %>

Related

Filterrific gem for two tables

I am using filterrific gem to add filters in my app. I have parents table and children table. On the children_list page which displays list of all the children with their firstname and their parent's firstname. The issue I am facing is in the search query I want to add the parent.firstname search as well for filterrific. I tried adding a join as below:-
num_or_conds = 2
joins(child: :parent).where(
terms.map { |term|
"(LOWER(children.firstname) LIKE ?) OR (LOWER(parents.firstname) LIKE ?) "
But this didnt do the job. Any idea how this can be achieved.
parent.rb
has_many :children
child.rb
belongs_to :parent
filterrific(
available_filters: [
:search_query,
:with_clinic_date
]
)
scope :search_query, lambda { |query|
return nil if query.blank?
terms = query.downcase.split(/\s+/)
terms = terms.map { |e|
(e.gsub('*', '%') + '%').gsub(/%+/, '%')
}
num_or_conds = 2
where(
terms.map { |term|
"(LOWER(children.firstname) LIKE ?) OR (LOWER(parents.firstname) LIKE ?)"
}.join(' AND '),
*terms.map { |e| [e] * num_or_conds }.flatten
)
}
scope :with_clinic_date, lambda { |ref_date|
where('children.clinic_date = ?', ref_date)
}
end
_children.html.erb
<h1>Children</h1>
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>Parent First Name</th>
<th>Child firstname</th>
</tr>
</thead>
<tbody>
<% #children.each do |child| %>
<tr>
<td><%=child.parent.firstname %></td>
<td><%=child.firstname %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
children-list.html.erb
<%= form_for_filterrific #filterrific do |f| %>
<div class="row">
<div class="col-sm-3">
Search
<%= f.text_field(
:search_query,
class: 'filterrific-periodically-observed form-control'
) %>
</div>
<div class="col-sm-3">
Request Date
<%= f.date_field(:with_clinic_date, class: 'js-datepicker form-control') %>
</div>
<div class="col-sm-3">
<br>
<%= link_to(
'Reset filters',
reset_filterrific_url,
) %>
</div>
</div>
<%= render_filterrific_spinner %>
<% end %>
<%= render( partial: 'children/children_list') %>
children.js.erb
<% js = escape_javascript(
render(partial: 'children/children_list')
) %>
$("#filterrific_results").html("<%= js %>");
AFAIK, you can't filter two separate classes on the same page. It will use the last defined filterrific instance. When I ran into this problem, I used remote forms with custom action/routes
# routes.rb
resources :parent do
get :filter_parents
resources: children do
get :filter_children
end
end
And then the controllers..
# parents_controller.rb
def index
parents_filter # this would be a helper method running your filter queries
end
def filter_parents
parents_filter # this would be a helper method running your filter queries
end
The children's controller would look similar, just different named helper method/custom action.
And then use a partial for the table. Target the table's container, and use a filter_parents.js.erb and filter_childrens.js.erb file
$('#parents-table').html('<%= escape_javascript render 'path/to/partial'%>')
// same in childrens.js.erb, just target the appropriate table

How do you Display Avarage Rating in index page?

I'm trying to display average ratings for jobs on my index page, it works perfectly fine on my show page
but on my index page the stars are there but are blank, how do i get them to display on my index page?
My Show Page:
<h4>Average Rating</h4>
<div class="average-review-rating" data-score=<%= #average_review %>></div>
<span class="number-of-reviews">Based on <%= #job.reviews.count %> reviews</span>
</div>
</div>
<script>
$('.review-rating').raty({
readOnly: true,
score: function() {
return $(this).attr('data-score');
},
path: '/assets/'
});
</script>
<script>
$('.average-review-rating').raty({
readOnly: true,
path: '/assets/',
score: function() {
return $(this).attr('data-score')
}
});
</script>
Jobs Show controller
def show
if #job.reviews.blank?
#average_review = 0
else
#average_review = #job.reviews.average(:rating).round(2)
end
end
My Index Page:
<% #jobs.each do |job| %>
<%= link_to job.title, job_path(job) %></h4>
<%= job.category %></p>
<%= job.city %>
<div class="average-review-rating" data-score=<%= average_review %>></div>
<span class="number-of-reviews">Based on <%= job.reviews.count %> reviews</span>
</div>
</div>
<% end %>
<script>
$('.average-review-rating').raty({
readOnly: true,
path: '/assets/',
score: function() {
return $(this).attr('data-score')
}
});
</script>
In your show page, you have #average_review defined. I'm guessing this was done in your jobs controller in the show action.
In your index page, you will need to calculate the average rating for each job as you are iterating through them. You can do this the same way you defined #average_rating. If you are defining your #average_rating in the show action as:
#average_rating = job.reviews.sum('score') / job.reviews.count
You will need to either define this method in the model (the better option), so something like:
app/models/job.rb
def average_review
reviews.sum('score') / reviews.count
end
Then in your index page:
<div class="average-review-rating" data-score=<%= job.average_review %>></div>
The other option is to set a variable for each object on the index page itself, less work but probably not as neat:
<% #jobs.each do |job| %>
<%= link_to job.title, job_path(job) %></h4>
<%= job.category %></p>
<%= job.city %>
<% average_review = job.reviews.sum('score') / job.reviews.count %>
<div class="average-review-rating" data-score=<%= average_review %>></div>
<span class="number-of-reviews">Based on <%= job.reviews.count %> reviews</span>
</div>
</div>
<% end %>
EDIT:
In your app/models/job.rb
def average_review
reviews.blank? ? 0 : reviews.average(:rating).round(2)
end
And index.html.erb
<div class="average-review-rating" data-score=<%= job.average_review %>></div>
You can fetch these results like so:
def index
#jobs = Job.all
#average_reviews = Review.where(job_id: #jobs)
.group(:job_id)
.average(:rating)
.transform_values { |rating| rating.round(2) }
end
This will return a hash with job ids as keys and the average rating as value (rounded to 2 decimals). To access these average ratings you can do the following in your index view:
#jobs.each do |job|
average_review = #average_reviews[job.id]
end
The above code only makes 2 SQL calls, 1 for fetching your jobs and 1 for fetching all average reviews for all the fetched jobs. Keep in mind that if a job has no reviews. When fetching the average rating from the hash the value nil is returned.

Rails: How to iterate over products from hash? (Amazon API / Vacuum)

Expanding on Rails: How to extract values from hash? (Amazon API / Vacuum) -- how do I make these newly dug up values available to my views?
Currently getting:
undefined method `image_url' for #<Hash:0x848402e0>
Extracted source (around line #5):
<%= link_to image_tag(product.image_url), product.url %>
main_controller.rb:
class MainController < ApplicationController
def index
request = Vacuum.new('GB')
request.configure(
aws_access_key_id: 'ABCDEFGHIJKLMNOPQRST',
aws_secret_access_key: '<long messy key>',
associate_tag: 'lipsum-20'
)
params = {
'SearchIndex' => 'Books',
'Keywords'=> 'Ruby on Rails',
'ResponseGroup' => "ItemAttributes,Images"
}
raw_products = request.item_search(query: params)
hashed_products = raw_products.to_h
# puts hashed_products['ItemSearchResponse']['Items']['Item'].collect{ |i| i['ItemAttributes']['Title'] }
# puts hashed_products['ItemSearchResponse']['Items']['Item'].collect{ |i| i['DetailPageURL'] }
# puts hashed_products['ItemSearchResponse']['Items']['Item'].collect{ |i| i['LargeImage']['URL'] }
#products = []
#products = hashed_products['ItemSearchResponse']['Items']['Item'].each do |item|
product = OpenStruct.new
product.name = item['ItemAttributes']['Title']
product.url = item['DetailPageURL']
product.image_url = item['LargeImage']['URL']
#products << product
end
end
end
index.html.erb:
<h1>Products from Amazon Product Advertising API</h1>
<% if #products.any? %>
<% #products.each do |product| %>
<div class="product">
<%= link_to image_tag(product.image_url), product.url %>
<%= link_to product.name, product.url %>
</div>
<% end %>
<% end %>
I wouldn't turn the entire Amazon hash into an OpenStruct. And since product.name is nil you can't do a find on it.
Instead, just loop through the Amazon items, assign them to your product, and then add to the #products array:
#products = []
hashed_products['ItemSearchResponse']['Items']['Item'].each do |item|
product = OpenStruct.new
product.name = item['ItemAttributes']['Title']
#products << product
end

How to group some values rails

I'm using rails 4. And i'm trying to output menu navigation. I got tble PlaceType with category and title columns. There are 6 categories and i want to output Category only 1 time and then output all titles, that brlong to this ategory. How can i achieve that?
<% #types = PlaceType.all %>
<% #types.each do |type| %>
<ul><%= type.category %>
<li><%= type.title %></li>
</ul>
<% end %>
That's my way of sloving this problem:
<% #types = PlaceType.all %>
<% #types.group_by(&:category).each do |category, type| %>
<ul><%= category %>
<% type.each do |t|%>
<li><%= t.title %></li>
<% end %>
</ul>
<% end %>
Use the group_by method available in Array.
#types.group_by {|type| type.category}
Will give you a hash that looks something like this:
{
"Category A" => ["Title 1", "Title 2"],
"Category B" => ["Title 3"],
"Category C" => ["Title 4", "Title 5"]
}
Now you can loop through the resulting hash instead, to display titles under each category.

Accesing hash keys and attributes from the view

in an attempt to create a model with an array as an atribute, i ended up creating an array of hashes like so:
data1 = {}
data1[:name] = "Virtual Memory"
data1[:data] = #jobs.total_virtual_memory
data2 = {}
data2[:name] = "Memory"
data2[:data] = #jobs.total_memory
#data = []
#data << data1
#data << data2
which populates #data like this:
[{:data=>[#<Job day: "2010-08-02">, #<Job day: "2010-08-04">], :name=>"Virtual Memory"}, {:data=>[#<Job day: "2010-08-02">, #<Job day: "2010-08-04">], :name=>"Memory"}]
However i do not know how to acces these variables in the view. So as tu run somethig like:
for series in #data
series:name
for d in series:data
data:[Date, Value]
end
end
which would return something along the lines of:
Name1
Date1, Value1
Date2, Value 2,
Date3, Value 3,
Date4, Value 4,
Name2
Date1, Value 1,
Date2, Value 2,
Date3, Value 3,
Date4, Value 4,
This should work:
<% for series in #data %>
<%= series[:name] %>
<% for d in series[:data] %>
<%= d.date %>, <%= d.value %>
<% end %>
<% end %>
However you might consider using a more suitable datastructure instead of hashs. A Struct for example. This could look like this:
# in lib/JobData.rb:
JobData = Struct.new(:name, :data)
# in the controller:
data1 = JobData.new("Virtual Memory", #jobs.total_virtual_memory)
data2 = JobData.new("Memory", #jobs.total_memory)
#data = [data1, data2]
# in the view:
<% for series in #data %>
<%= series.name %>
<% for d in series.data %>
<%= d.date %>, <%= d.value %>
<% end %>
<% end %>
As a style point: I used for because you used for, but in general it's considered more rubyish to use each instead.
Here is the view:
<% for d in #data %>
{ pointInterval: <%= 1.day * 1000 %>,
name:<%= "'#{d[:name]}'"%>,
pointStart: <%= 2.weeks.ago.at_midnight.to_i * 1000 %>,
data: [
<% for chart in d[:data] %>
<%= "'[#{chart.day.to_time(:utc).to_i * 1000}, #{chart.data_attribute}],'" %>
<% end %>
]
},
<% end %>
Use #{d[:name]} to access the value of the "name" key and use d[:data] to access the array, then just loop throughthe array as if it were any normal array

Resources