Ruby - how to join output from a Rails command? - ruby-on-rails

There are multiple calculations I'd like to be able to perform on my database, all centred around a user id, but I don't know how to recombine them after.
Let's say I have three tables for User, Purchases, and DiscountUsage. I want to find the last date a user made a purchase and the total number of discounts used. I would run two separate commands:
User.joins(:purchases).group("users.id").select("users.id", "MAX(purchases.purchase_date)")
User.joins(:discount_usages).group("users.id").select("users.id", "COUNT(*)")
I want my final output to be one table though, joined on users.id, but the output from select isn't the right data type to work on with Rails functions. How can I represent that the users.id values from both calls are the same and thus join them based on those columns?

I assume a User may not have any purchases and not all purchases use discount codes.
However, you want a full listing of each user with their last purchase date and total discount usages over all purchases. You may need to use a right join.
Something like:
query = User.select('users.id AS user_id', 'MAX(purchases.purchase_date) AS last_purchase_date', 'COUNT(discount_usages.id) AS total_discount_usages')
.joins('LEFT JOIN purchases ON purchases.user_id = users.id')
.joins('LEFT JOIN discount_usages ON discount_usages.user_id = purchases.user_id')
.group('users.id')
.to_sql
Then in order to grab the fields you could use:
rows = ApplicationRecord.connection.select_all(query).to_hash
https://guides.rubyonrails.org/active_record_querying.html#select-all
This will give you an array of hashes with keys: 'user_id', 'last_purchase_date', 'total_discount_usages'.
...
<% rows.each do |row| %>
<% row.symbolize_keys! %>
<tr>
<td><%= row[:user_id] %></td>
<td><%= row[:last_purchase_date] %></td>
<td><%= row[:total_discount_usages] %></td>
<tr>
...

You can select aggregates from joined tables and access them in the model by using aliases:
#users = User.joins(:purchases, :discount_usages)
.select(
"users.id",
"MAX(purchases.purchase_date) AS latest_purchase",
"COUNT(discount_usages.*) AS discount_usages_count"
)
.group("users.id")
If you want to hydrate the other attributes of the records select users.* instead of just users.id.
<table>
<thead>
<tr>
<th>Id</th>
<th>Latest Purchase</th>
<th>Discount usages</th>
</tr>
</thead>
<tbody>
<% #users.each do |user| %>
<tr>
<td><%= user.id %></td>
<td><%= user.latest_purchase %></td>
<td><%= user.discount_usages_count %></td>
</tr>
<% end %>
</tbody>
</table>
I want my final output to be one table though, joined on users.id, but
the output from select isn't the right data type to work on with Rails
functions.
Not sure quite what you mean here. This example will return a collection of user records just like a normal query.
If you want a "raw" result with just an array of arrays use .pluck instead of .select.

Related

Rails 5 has_many_through include pivot table in result

I have a Rails 5 app with restaurants and products. One product has_many restaurant and one restaurant has many products. I created a pivot table and created and has_many_through relation since I want to work with the pivot table.
Product:
has_many :restaurant_products, dependent: :destroy
has_many :restaurants, through: :restaurant_products
Restaurant:
has_many :restaurant_products, dependent: :destroy
has_many :products, through: :restaurant_products
Since every restaurant can modify the price for each product I have added a custom_price column to my restaurant_products table.
Now I can get all products for a certain restaurant:
#restaurant.products
But this way, when I list the products, I don't have access to the custom price. How can I somehow include or access the correct pivot record to get the custom price?
You can use the following:
#restaurant.restaurant_products.joins(:products).select('restaurant_products.custom_price, products.*')
That will give you access to instances including all of a product's columns, as well as the join table's custom price.
The SQL generated will look similar to:
SELECT restaurant_products.custom_price, products.*
FROM `restaurant_products`
INNER JOIN `products` ON `products`.`id` = `restaurant_products`.`product_id`
WHERE `restaurant_products`.`restaurant_id` = 1
Quick tip: what's returned might look a little strange, something like:
[#<RestaurantProduct id: 1, custom_price: 10>]
...though if you call attributes, or a product attribute on an instance here, you'll have access to everything you want.
The above gives quick and efficient access to the records' data, though if you need to access methods on the product itself, you might prefer the following:
#product_details = #restaurant.restaurant_products.includes(:products)
And then to loop through the data:
#product_details.each do |product_detail|
product_detail.custom_price
product_detail.product.column_data
product_detail.product.a_method
end
Hope these help - let me know how you get on or if you have any questions.
Just provide a custom select that includes the row from the join table:
#products = #restaurant.products
.select('products.*, restaurant_products.custom_price')
This returns restaurant_products.custom_price as if it where a column on products. You can us AS to provide a alias if you want to call it something else than custom_price.
So you can do:
<table>
# ...
<% #products.each do |product| %>
<tr>
<td><%= product.name %></td>
<td><%= product.custom_price %></td>
</tr>
<% end %>
</table>
In such cases, it always better to query on pivot table as bellow
#restaurant_products = RestaurantProduct.includes(:product)
.where(restaurant_id: #restaurant.id)
You can display it in the view simply as follows.
<table>
<% #restaurant_products.each do |resta_product| %>
<tr>
<td><%= resta_product.product.name %></td>
<td><%= resta_product.custom_price %></td>
</tr>
<% end %>
</table>
You can even delegate all product methods to work directly on restaurant_product.

Rails 5 Multiple Sums and Join

This question is related to this previously asked question.
My DB columns for model Taxline: ID, RECEIPT, TAXID, BASE, AMOUNT
With entries:
1,1,001,30$,3$
2,1,001,50$,5$
3,2,001,20$,2$
And then a second table with columns: TICKETID, TICKETNUMBER
My controller
class TaxlinesController < ApplicationController
def index
#taxlines = Taxline.group(:RECEIPT).sum(:AMOUNT)
end
end
My view
<% #taxlines.each do |receipt, amount| %>
<td><%= receipt %></td>
<td><%= amount %></td>
<% end %>
This works great to show a ticket for each row with corresponding total amount.
Question 1. What is the proper way to also show in view sum of BASE? I tried .sum(:AMOUNT, :BASE) and .sum(:AMOUNT).sum(:BASE) but they both don't work.
Question 2. If now I call in view for instance <%= taxline.TAXID %> I get an error.
To solve this I tried to add in view <% #taxlines.each do |receipt, amount, taxid| %> and <td><%= taxid %></td>. And in controller #taxlines = Taxline.group(:RECEIPT).sum(:AMOUNT).select(:TAXID). But it shows a blank column.
Question 3. I want to show TICKETNAME value from TICKETS table.I have already set in Ticketline Model belongs_to :ticket. I assume that after solving question 1 I will be able to do ticketline.ticket.TICKETNAME.Right?
Question 1:
Try this
#taxlines = Taxline.group(:RECEIPT).select("SUM(AMOUNT) AS AMOUNT, SUM(BASE) AS BASE")
Question2:
To access TAXID you need to add this in group column.
#taxlines = Taxline.group(:RECEIPT, :TAXID).select("SUM(AMOUNT) AS AMOUNT, SUM(BASE) AS BASE, TAXID")
Question 3:
To access the other table variables, you need to add join statement to the query and then you should group.
#taxlines = Taxline.joins(:ticket).group(:RECEIPT, :TAXID).select("SUM(AMOUNT) AS AMOUNT, SUM(BASE) AS BASE, TAXID, TICKETNAME")
In the view side, try the below one.
<% #taxlines.each do |taxline| %>
<td><%= taxline.AMOUNT %></td>
<td><%= taxline.BASE %></td>
<td><%= taxline.TAXID %></td>
<td><%= taxline.TICKETNAME %></td>
<% end %>
P.S: Not tried this.

List data from two models

I have two models, Items and Calibrations. Items has many calibrations, meaning that every year the instruments have to be calibrated. Fields, date_calibration and date_expired, are located in the "calibrations" table. (Items: has_many :calibrations, calibration: belongs_to item)
I need list/show all the items that are expiring. I can list all the items without problem of course but, I don't know how to add date_expired to the list.
In the Items controller:
#items = Item.all.order("created_at DESC")
In the Index:
<% #items.each do |item| %>
<tr>
<td><%= item.cod %></td>
<td><%= item.number %></td>
<td><%= item.den_cont %></td>
<td><%= item.branch %></td>
<td><%= item.model %></td>
</tr>
<% end %>
I'm using Aptana and PostgreSQL version 9.1, Ruby 2.1 and Rails 4.1.
Can anyone of you suggest any solution or point me to the right direction?
UPDATE
What should I change to show the item using the sentence below..
Item_controller
Item.includes(:calibrations).where('calibrations.date_expired <= ?' , 2014/07/12)
Index
<% #items.each do |item| %>
Return undefined method each.
ALSO
Any idea on how to show a traffic light depending on how many days left to calibration_date ?? Tks again!
As long you have your relations properly defined in your models I do believe something similar to the following should do the trick!
Item.joins(:calibrations).where(date_expired <whatever condition>)
The equivalent SQL being:
SELECT Items.*
FROM Items
LEFT OUTER JOIN Calibrations
ON Calibrations.item_id = Items.item_id
WHERE date_expired <whatever condition>
With the equivalent SQL of the above statement being (using the includes method):
SELECT *
FROM Items
LEFT OUTER JOIN Calibrations
ON Calibrations.item_id = Items.item_id
WHERE date_expired <whatever condition>
Hope this helps!
Also, if you're not wanting to return any data related to the calibrations table (which it looks like this is the case), I would go with the joins method, however if you are, the includes method would be the way to go (http://tomdallimore.com/blog/includes-vs-joins-in-rails-when-and-where/)
Also, the following may be of interest: http://guides.rubyonrails.org/active_record_querying.html (particularly section 12)!
#items = Item.includes(:calibrations)
.where('calibrations.date_expired <= ?', some_date)
include will join the two tables and allow you to specify conditions on items based on columns from the calibrations table. I think that's what you wanted isn't it?

Group by name, count and total, and display it in a view using Rails

I thought I wasn't a rookie anymore, until I stumbled upon this.
I'm trying to load an html list, grouping shops per user and displaying per shop, the number of receipts and total amount.
In Sql I would do this easily with a group by, so I was trying to load the code below, within my Shop Model, into my Rails Console.
def self.group_receipts(user, search_hash=nil)
shop_ids = Shop.pluck(:id) & Receipt.from_companies(user, search_hash).pluck(:shop_id)
#greceipt means Grouped Receipt
greceipt = Struct.new(:name, :number_of_receipts, :total)
query = Shop.joins(:receipts).where('shops.id in (?)',shop_ids).select('shops.name,count(receipts.id) as number_of_receipts,sum(receipts.total) as total').group('shops.id')
query
end
Here is my output
>> Shop.group_receipts(302).all
(2.0ms) SELECT id FROM "shops"
(3.0ms) SELECT shop_id FROM "receipts" WHERE (receipts.id IN (SELECT receipts.id
FROM receipts
INNER JOIN shops on shops.id=receipts.shop_id
INNER JOIN companies on companies.id = shops.company_id
INNER JOIN user_companies on user_companies.company_id = companies.id
WHERE user_companies.user_id = 302)) AND (receipts.is_manual is null or receipts.is_manual=false) ORDER BY receipts.created_at DESC
Shop Load (2.0ms) SELECT shops.name,count(receipts.id) as number_of_receipts,sum(receipts.total) as total FROM "shops" INNER JOIN "receipts" ON "receipts"."shop_id" = "shops"."id" WHERE (shops.id in (16)) GROUP BY shops.id
[#<Shop name: "Kirlin Osinski and Dooley">]
If my query seems to be all right, why is that that my output is not something like name, 10, 1000 ?
That greceipt struct you find on the method definition intends to create a structure so that, we can later access gs.name, gs.number_of_receipts, gs.total, but i cannot understand how to load a list of objects of this struct type from the output presented above :-S.
Anyone to the rescue?
With the help of this forum, I understood that the problem was within rails console.
I ended up with
model:
def self.group_receipts(user, search_hash=nil)
shop_ids = Shop.pluck(:id) & Receipt.from_companies(user, search_hash).pluck(:shop_id)
query = Shop.joins(:receipts).where('shops.id in (?)',shop_ids).select('shops.name,count(receipts.id) as number_of_receipts,sum(receipts.total) as total').group('shops.id')
query
end
controller:
#receipts_per_shop = Shop.group_receipts(current_user)
partial view _receipts_per_shop:
<table class="table table-striped">
<thead>
<th><%=t 'activerecord.attributes.shop.name'%></th>
<th>#</th>
<th>Total</th>
</thead>
<% if #shops.present? %>
<%= render partial: '/shops/receipt_per_shop', collection: #receipts_per_shop %>
<% else %>
<tbody><tr><td colspan="3"><%= t('no_records') %></td></tr></tbody>
<% end %>
</table>
partial view _receipt_per_shop
<% #receipts_per_shop.each do |rs| %>
<tr>
<td><%= rs.name %></td>
<td><%= rs.number_of_receipts %></td>
<td><%= rs.total %></td>
</tr>
<% end %>

Average values by group

I have two tables in my web app: one is for Donors (called "donors") and the other is for Donation Amounts (called "donations). When you click on a donor, you can see all of their donations.
I'm trying to average values associated with a particular date, for a particular charity. For example, if these records exist for Donor A:
*Donor A*
Date Donation Amount
05/04/2013 30
05/04/2013 40
05/05/2013 15
05/05/2013 75
I'd like the system to also calculate and display that the average donation amount for 05/04/2013 was 35 and the average donation amount for 05/05/2013 was 45.
Currently I've tried using the group attribute in my donor model:
def self.average_donateperdate
Donation.average(:donateamount, conditions: ["donor_id = ?", #donor], group: "donatedate")
end
This doesn't seem to work. Since I'm new to Rails, I'm not entirely sure whether this is the right way to do it or not. There are a few other posts that touch on this topic, but none that have helped me solve the issue. Any help would be greatly appreciated!
The simplest syntax to do this is:
#donor.donations.group(:donatedate).average(:donateamount)
This will return a hash in the format { 'donatedate' => 'average donateamount' }.
Naturally, this assumes you have a has_many :donations association on your Donor model. A reusable method would look like:
def average_donation_by_date
donations.group(:donatedate).average(:donateamount)
end
And you can now simply call:
#donor.average_donation_by_date
To see this visually, you can call this in your controller:
#average_donations = #donor.average_donation_by_date.to_a.sort_by(&:first)
And your view might contain something like this:
<table>
<thead>
<tr>
<th>Date</th>
<th>Average Donation</th>
</tr>
</thead>
<tbody>
<% #average_donations.each do |date, amount| %>
<tr>
<td><%= date.strftime('MM/dd/yyyy') %></td>
<td><%= amount %></td>
</tr>
<% end %>
</tbody>
</table>
Reference
Rails api - calculate grouped values

Resources