Adding values in a group_by in Rails - ruby

I'm working on generating quarterly royalty reports for authors. I have a Report model (not backed by a database table) that I'm using to display the report for specific authors based on the year and quarter. I'm routing that to author/:author_id/:reports/:year/:quarter
I've got some pretty ugly code in my controller right now, for the sake of getting things working. I can refactor later:
def show
#author = Author.find(params[:author_id])
#find the orders that took place in the current quarter and year for the report
#orders = Order.get_current_orders(quarter_range, params[:year])
#find only the products that apply for the current author
#author_products = #author.products
#find only the line_items that match the current quarter's orders and the author's products
#line_items = LineItem.find_all_by_order_id_and_product_id(#orders, #author_products)
#group the line items by product
#line_items_by_product = #line_items.group_by { |l| l.product_id }
end
That let's me do this in my view:
<%= #line_items_by_product.each do |product, line_item | %>
<h3><%= product %></h3>
<% line_item.each do |line_item| %>
<%= line_item.quantity %> <br />
<% end %>
<% end %>
Two things I need to fix. Right now, product just returns the product id, not the title (it's stored in the products table, not the line_items table). I can't access product.title here obviously, but I need to get the title in the grouping.
My second issue is that instead of just looping over the quantity of every single line item, I want to total the quantity of each line item and just display that. So in stead of getting , 1, 10, 55 ... I just want 66. I tried array#inject, but I'm not grasping the syntax.
And, that all be said ... I'm sure I'm doing more work than I need to. I started with a lot of the controller code in the model but was having a lot of undefined method errors. Any advice or help would be appreciated.

I agree that you need to refactor your code, and I'm only offering these answers to your specific questions - these are not my overall recommendations.
First Problem
Remember this is Ruby, a Product instance is just as valid to group by as the Integer that you're currently using. So, you can change your group_by call to:
#line_items_by_product = #line_items.group_by { |l| l.product }
...and then change your view to...
<h3><%= product.title %></h3>
Second Problem
<%= line_item.sum {|li| li.quantity } %>

Related

Linking to search results with Ruby

I'm a complete novice in Ruby and Nanoc, but a project has been put in my lap. Basically, the code for the page brings back individual URLs for each item linking them to the manual. I'm trying to create a URL that will list all of the manuals in one search. Any help is appreciated.
Here's the code:
<div>
<%
manuals = #items.find_all('/manuals/autos/*')
.select {|item| item[:tag] == 'suv' }
.sort_by {|item| item[:search] }
manuals.each_slice((manuals.size / 4.0).ceil).each do |manuals_column|
%>
<div>
<% manual_column.each do |manual| %>
<div>
<a href="<%= app_url "/SearchManual/\"#{manual[:search]}\"" %>">
<%= manual[:search] %>
</a>
</div>
<% end %>
</div>
<% end %>
</div>
As you didn't specify what items is returning, I did an general example:
require 'uri'
# let suppose that your items query has the follow output
manuals = ["Chevy", "GMC", "BMW"]
# build the url base
url = "www.mycars.com/search/?list_of_cars="
# build the parameter that will be passed by the url
manuals.each do |car|
url += car + ","
end
# remove the last added comma
url.slice!(-1)
your_new_url = URI::encode(url)
# www.mycars.com/?list_of_cars=Chevy,GMC,BMW
# In your controller, you will be able to get the parameter with
# URI::decode(params[:list_of_cars]) and it will be a string:
# "Chevy,GMC,BMW".split(',') method to get each value.
Some considerations:
I don't know if you are gonna use this on view or controller, if will be in view, than wrap the code with the <% %> syntax.
About the URL format, you can find more choices of how to build it in:
Passing array through URLs
When writing question on SO, please, put more work on that. You will help us find a quick answer to your question, and you, for wait less for an answer.
If you need something more specific, just ask and I can see if I can answer.

ruby (for rails) sort_by instantiated variable

In a rails 3.2.17 application, with ruby 1.9.3, a method filters a previously-found set of products based on a condition. If it passes the condition, it creates an instance variable for the record (rate), which itself is the product of another method (shown_price).
#products = #inventories.map(&:product).uniq
def validarray
validarray = Array.new
#products.each do |product|
#inventories_for_product = #inventories.select{ |i| i.product_id == product.id }
if #inventories_for_product.count == #threshhold
product.rate = #inventories_for_product.map(&#shown_price).inject(0, :+)
validarray << product
end
end
validarray.sort_by{|p| p[:rate]}
end
#validarray = validarray
All elements in this method generate proper data which can be viewed via the browser. In fact (even re-calling sort_by)
<% #validarray.sort_by{|p| p[:rate]}.each do |vp| %>
<%= vp.id %> <%= vp.rate %><br />
<% end %>
will show the proper data.
Issue when I ask for descending order
#validarray.sort_by{|p| -p[:rate]}
undefined method `-#' for nil:NilClass
whereas for ascending order
no complaints, but no sorting either.
I assume that the symbol
:rate is wrong
and ruby lets the stuff unsorted as ASC is invoked by default and has nothing to sort with, but complains otherwise because it has no tools in its hands.
Most likely yourate attribute is not a column in products table, but it is defined as a method within Product model. This means that [] method on the model will always return nil, as it only reads given database column and perform a typecast. In short, do:
#validarray.sort_by &:rate
or (in reverse order)
#validarray.sort_by{|p| -p.rate }
Possibly not all products have a :rate value. You could either:
#validarray.sort_by{|p| p[:rate]}.reverse
or:
#validarray.sort_by{|p| -(p[:rate] || 0) }

How to generate pages for each tag in nanoc

I am new to nanoc and I am still finding my around it. I am able to get my site ready, it looks good and functions good, too. But I need to have a tags area. I am able to achieve that with
<%= tags_for(post, params = {:base_url => "http://example.com/tag/"}) %>
But how do I generate pages for tag? So for instance there is a tag called "NFL", so every time a user clicks on it, he/she should be directed to http://example.com/tag/nfl with a list of articles that correspond with NFL.
I can setup a layout which will do that. But then what kind of logic should be I using? And also do I need to have a helper for this?
You can use a preprocess block in your Rules file in order to generate new items dynamically. Here’s an example of a preprocess block where a single new item is added:
preprocess do
items << Nanoc::Item.new(
"some content here",
{ :attributes => 'here', :awesomeness => 5000 },
"/identifier/of/this/item")
end
If you want pages for each tag, you need to collect all tags first. I’m doing this with a set because I do not want duplicates:
require 'set'
tags = Set.new
items.each do |item|
item[:tags].each { |t| tags.add(t.downcase) }
end
Lastly, loop over all tags and generate items for them:
tags.each do |tag|
items << Nanoc::Item.new(
"",
{ :tag => tag },
"/tags/#{tag}/")
end
Now, you can create a specific compilation rule for /tags/*/, so that it is rendered using a "tags" layout, which will take the value of the :tag attribute, find all items with this tag and show them in a list. That layout will look somewhat like this:
<h1><%= #item[:tag] %></h1>
<ul>
<% items_with_tag(#item[:tag]).each do |i| %>
<li><%= link_to i[:title], i %></li>
<% end %>
</ul>
And that, in broad strokes, should be what you want!

Rails 3.1 - Correct approach to remove a query from a view and process it in the controller or model

In a view I have the following:
<% #top_posts.each do |post| %>
<li>
<%= post.title %><br />
<%= link_to "Most popular comment", comment_path( post.comments.order("vote_cnt DESC").first )
</li>
<% end %>
I know it is considered poor form to have the post.comments.order("vote_cnt DESC").first query in a view. However, since I'm combining both post and comment data to create a single list item, I'm having a hard time understanding how to get this "combo-pack" of data built in the controller. Should I be constructing some sort of #hash in the controller and then iterate on #hash.each in the view? Is that the right approach?
Or is the job for a scope on my Post model? Is there some ActiveRecord magic that I'm missing that makes this easy? I'm still pretty rookie at RoR, and am just beginning to see just how much I don't understand.
What I'd do is add a method in my Post model to get the first comment according to your criterias. The view would then look like comment_path(post.relevant_comment).

Ruby / Rails - Advanced grouping with group_by

I have a model called event_recurrences it contains 2 important columns event_id and cached_schedule
cached_schedule contains an array of dates
event_id is used to reference the event
I need to
Get all the event_recurrence objects #something.event_recurrences - Done
Go through each recurrence object and get the event_id and all the
dates from cached_schedule
Iterate through each month, and spit out a list like the following
Jan
event_id date
event_id date
event_id date
Feb
event_id date
... and so on
To recap the event_id is located event_recurrence.event_id the dates that the event_id will happen on are located in an array inside event_recurrence.cached_schedule
Some I have some incomplete code to work with...
This code works successfully to show each event_recurrence object by month using the created_at field.
in my controller
#schedule_months = #something.event_recurrences.order("created_at DESC").group_by { |e| e.created_at.beginning_of_month }
in my view
<% #schedule_months.keys.sort.each do |month| %>
<div class="month">
<%= month.strftime("%B %Y") %>
</div>
<% for event in #schedule_months[month] %>
<li><%= event %></li>
<% end %>
<% end %>
Thanks in advance.
Sincerely, jBeas
There are some details missing from your question... for example, can the dates in cached_schedule span multiple months, or are they all guaranteed to be in the same month?
If you just want to use core Ruby:
dates = []
#something.event_recurrences.each do |er|
er.cached_schedule.each { |date| dates << [date, er.event_id] }
end
#event_dates = dates.group_by { |(date,event_id)| date.mon }
Then in the view:
<% #event_dates.keys.sort.each do |month| %>
<div class="month"> <%= month.strftime("%B %Y") %></div>
<% #event_dates[month].each do |(date,event_id)| %>
<li><%= date %> <%= event_id %></li>
<% end %>
<% end %>
You may have to adjust the code a little depending on the specifics, but this should give you the idea. Note the use of destructuring assignment: #event_dates[month].each do |(date,event_id)|. This saves a line of code and expresses what the code is doing more clearly.
If you don't mind adding your own extensions to the core Ruby classes, you could make this code even cleaner and more consise. I often use a method which I call mappend:
module Enumerable
def mappend
result = []
each { |a| enum = yield a; enum.each { |b| result << b } if enum }
result
end
end
The name is a mix of map and append -- it is like map, but it expects the return value of the mapping block to also be Enumerable, and it "appends" all the returned Enumerables into a single Array. With this, you could write:
#event_dates = #something.event_recurrences.mappend { |er| er.cached_schedule.map { |date| [date, er.event_id] }}.group_by { |(date,event_id)| date.mon }
OK, that might be a lot for one line, but you get the idea: it saves you from using an intermediate variable to accumulate results.
UPDATE: Something like mappend is now part of the Ruby core library! It's called flat_map.

Resources