Rails show total of nested values in view - ruby-on-rails

I am building a feature in my app to create purchase orders. I have classic tables purchase_orders and purchase_order_lists.
purchase_order has many purchase_order_lists and purchase_order_lists belongs_to purchase_order. Foreign key is on purchase_orders table, column PURCHASE_ORDER. Database table names are capital.
In my purchase_orders controller I have:
def show
#purchase_order = PurchaseOrder.find(params[:ID])
#purchase_order_list = PurchaseOrderList.find(params[:ID])
end
In my model purchase_oder_list.rb
def total
(self.UNITS * self.PRICEBUY)
end
In my view purchase_orders/show
<td><%= purchase_order_list.total %></td>
which return the correct value for each purchase order list row.
Then in the same view to show the total:
#purchase_order_list.to_a.sum(&:total)
which should show the total of method total but it raises the error
Couldn't find PurchaseOrderList with 'ID'=POID-00001
as it is looking for a Purchase Order ID among Purchase Order List IDs.
How can I fix it to return the sum of method totalfor a certain purchase order?

The problem was that I was using find while there are many purchase_order_lists for one purchase order.
To fix I used where instead:
#purchase_order_list = PurchaseOrderList.where(PURCHASE_ORDER: params[:ID])

Related

Calculate SUM based on a column from associated model

I have a table called vehicles, which has a column called vehicle_id and price.
I have a table called sales, which references the vehicles table. It has the columns vehicle_id (references the vehicle table) and sale_status which can equal to 'sold' or 'loan'.
I am trying to calculate the total price of vehicles which equal to 'sold' in the sales table. Help is much appreciated!
This is what I have so far but it returns the wrong number.
vehicle.rb:
def self.vehicles_price_sum
vehicles_sold.sum(:price).to_f
end
def self.vehicles_sold
Vehicle.where(id: Sale.where(sale_status: 'Sold'))
end
You can try with a subquery (which is close to you solution, yet you need to provide a column name explicitly for in clause with select, otherwise sales' id column is going to be provided):
Vehicle.where(id: Sale.where(status: "Sold").select(:vehicle_id)).sum(:price)
# SELECT SUM(`vehicles`.`price`) FROM `vehicles` WHERE `vehicles`.`id` IN (SELECT `sales`.`vehicle_id` FROM `sales` WHERE `sales`.`sale_status` = 'Sold')

Rails create multiple records at once from nested attributes on a different model

In my app I have model PurchaseOrder and PurchaseOrderList. When I create a purchase order it updates both tables purchase_orders and purchase_order_lists with a nested form.
Now I want to be able to add a button update stock on purchase_orders/show. When I click this button a new record will be created on table stockdiaries with same products, units and prices as on the purchase order.
The logic is that first I create a purchase order and then I click the button to update stock when goods are received.
The best I came up with is to create this method on PurchaseOrders controller:
def update_stock
#purchase_order_list = PurchaseOrderList.where(PURCHASE_ORDER: params[:ID])
#stockdiary = Stockdiary.create(PRODUCT: #purchase_order_list.PRODUCT, UNITS: #purchase_order_list.UNITS, PRICEBUY: #purchase_order_list.PRICEBUY)
flash[:notice] = "Stock updated successfully."
redirect_to(:action => 'show', :ID => #purchase_order.ID)
end
and in my purchase_orders/show:
<%= link_to "Update Stock", { controller: :purchase_orders, action: :update_stock, ID: #purchase_order.ID} %>
but it raises error
undefined method `PRODUCT' for
#<PurchaseOrderList::ActiveRecord_Relation:0x007f1efeff4898>
But there is a column PRODUCT on purchase_order_lists table.
Note that I included in the method only columns that are common to purchase_order_lists and stockdiaries as others (as id or status) are not concerned on this question. Columns names are capital as I'm building the app on existing db.
What is the correct way to create a stockdiary from a purchase order?
Updated
Rails is returning an an ActiveRecord Relation from this query:
#purchase_order_list = PurchaseOrderList.where(PURCHASE_ORDER: params[:ID])
This is because you used where, so ActiveRecord returns a relation that could potentially have multiple records in it. If you only want one record, or even if you know that there will only be one record then you should use find_by
So you can either do:
# change where to find_by
#purchase_order_list = PurchaseOrderList.find_by(PURCHASE_ORDER: params[:ID])
# and then this will work
#stockdiary = Stockdiary.create(PRODUCT: #purchase_order_list.PRODUCT, UNITS: #purchase_order_list.UNITS, PRICEBUY: #purchase_order_list.PRICEBUY)
OR... If you want where or there might be multiple records, then you can loop:
#purchase_order_list = PurchaseOrderList.where(PURCHASE_ORDER: params[:ID])
# and then loop through you potentially multiple records in #purchase_order_list
#purchase_order_list.each do |po|
#stockdiary = Stockdiary.create(PRODUCT: po.PRODUCT, UNITS: po.UNITS, PRICEBUY: po.PRICEBUY)
end
or use first:
#stockdiary = Stockdiary.create(PRODUCT: #purchase_order_list.first.PRODUCT, UNITS: #purchase_order_list.first.UNITS, PRICEBUY: #purchase_order_list.first.PRICEBUY)
Usually this means you didn't place accepts_nested_attributes_for in the PurchaseOrder model.
PurchaseOrder
class PurchaseOrder
accepts_nested_attributes_for :purchase_order_list
end

Rails sort users by method

I'm trying to rank my user's in order of an integer. The integer I'm getting is in my User Model.
def rating_number
Impression.where("impressionable_id = ?", self).count
end
This gives each User on the site a number (in integer form). Now, on the homepage, I want to show an ordered list that places these user's in order with the user with the highest number first and lowest number second. How can I accomplish this in the controller???
#users = User....???
Any help would be appreciated!
UPDATE
Using this in the controller
#users = User.all.map(&:rating_number)
and this for the view
<% #users.each do |user| %>
<li><%= user %></li>
<% end %>
shows the user's count. Unfortunately, the variable user is acting as the integer not the user, so attaching user.name doesn't work. Also, the list isn't in order based on the integer..
The advice here is still all kinds of wrong; all other answers will perform terribly. Trying to do this via a nested select count(*) is almost as bad an idea as using User.all and sorting in memory.
The correct way to do this if you want it to work on a reasonably large data set is to use counter caches and stop trying to order by the count of a related record.
Add a rating_number column to the users table, and make sure it has an index defined on it
Add a counter cache to your belongs_to:
class Impression < ActiveRecord::Base
belongs_to :user, counter_cache: :rating_number
end
Now creating/deleting impressions will modify the associated user's rating_number.
Order your results by rating_number, dead simple:
User.order(:rating_number)
The advice here is just all kinds of wrong. First of model your associations correctly. Secondly you dont ever want to do User.all and then sort it in-memory based on anything. How do you think it will perform with lots of records?
What you want to do is query your user rows and sort them based on a subquery that counts impressions for that user.
User.order("(SELECT COUNT(impressions.id) FROM impressions WHERE impressionable_id = users.id) DESC")
While this is not terribly efficient, it is still much more efficient than operating with data sets in memory. The next step is to cache the impressions count on the user itself (a la counter cache), and then use that for sorting.
It just pains me that doing User.all is the first suggestion...
If impressions is a column in your users table, you can do
User.order('impressions desc')
Edit
Since it's not a column in your users table, you can do this:
User.all.each(&:rating_number).sort {|x,y| y <=> x }
Edit
Sorry, you want this:
User.all.sort { |x, y| y.rating_number <=> x.rating_number }

How to sort through an associated attribute on two levels at once?

This is a simple ruby question I believe. In my app, I have Product model that has_many Reviews. Each Review has an attribute of an "overall" rating which is an integer.
What I want to do is display the top ten Products based on the average of their overall ratings. I've already gotten this to work, BUT, I also want to sort Products that have the SAME overall rating by a secondary aggregate attribute, which would be how MANY reviews that Product has. Right now, if I have 3 products with the same average overall rating, they seem to be displayed in random order.
So far my code is:
Controller
#best = Product.has_reviews.get_best_products(10)
Product Model
scope :has_reviews, joins{reviews.outer}.where{reviews.id != nil}
def self.get_best_products(number)
sorted = self.uniq
sorted = sorted.sort { |x, y| y.reviews.average("overall").to_f <=> x.reviews.average("overall").to_f }
sorted.first(number)
end
I've tried this for my model code:
def self.get_best_products(number)
sorted = self.uniq.sort! { |x, y| x.reviews.count.to_f <=> y.reviews.count.to_f }
sorted = sorted.sort { |x, y| y.reviews.average("overall").to_f <=> x.reviews.average("overall").to_f }
sorted.first(number)
end
...but it does not do what I want it to do. I am just iterating through the #best array using each in my view.
---UPDATE
OK now I am trying this:
Controller:
#best = Product.get_best_products(6)
Model:
def self.get_best_products(number)
self.joins{reviews}.order{'AVG(reviews.overall), COUNT(reviews)'}.limit(number)
end
But I am getting this error:
PGError: ERROR: column "products.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT "products".* FROM "products" INNER JOIN "reviews" ...
I am using the Squeel gem btw to avoid having direct SQL code in the model.
----UPDATE 2
Now I added the 'group' part to my method but I am still getting an error:
def self.get_best_products(number)
self.joins{reviews}.group('product.id').order{'AVG(reviews.overall), COUNT(reviews)'}.limit(number)
end
I get this error:
PGError: ERROR: missing FROM-clause entry for table "product"
LINE 1: ...eviews"."product_id" = "products"."id" GROUP BY product.i...
product.rb
scope :best_products, (lambda do |number|
joins(:reviews).order('AVG(reviews.overall), COUNT(reviews)').limit(number)
)
products_controller.rb
Product.best_products(10)
This makes sure everything happens in the database, so you won't get records you don't need.
If I got it right here is my idea of how I would do it:
As products has many reviews and reviews has an overall attribute I would add a reviews_counter column to the products table that will increment with each added review, this way you'll be able to gain a little more db performance as you don't have to count all the products reviews to get the most reviewed one.
Now you'll get the products ordered by reviews_counter:
#best_products = Products.order("reviews_counter desc")
and next you'll get the reviews for each product ordered by overall:
<% for prod in #best_products %>
<%= prod.reviews.order("overall desc") %> # can do all this or more in helper
<% end %>
also ordering this way, if you have 3 reviews with the same overall you can one more order() statement and sort it by name or id or whatever you like so they don't display in random order.
This is just my idea of how I would do it, I worked recently on an app that required something similar and we just added a counter_field to our model, it's not illegal to do so :)
p.s. it's not very clear for me how many records you would want to display for each so you'll just need to add .limit(5) for exemple to get only the first 5 reviews of a product.

How to print all elements that belongs_to this table

Ok, I'm not sure that my title was clear enough, but I will try to explain
I have two tables: orders that has_many items and items that belongs_to orders.
I just started to learn RoR and stuck with a simple task. All I want is to
display orders and related items like this:
Order 1:
Item 1
Item 2
Order 2:
Item 1
Item 2
...
I know how to display orders or items separately, I know how to display items' orders (item.order.id), but how to display orders and items in the table like above? In template where I display orders I could go through each item every iteration and compare it foreign order_id with order.id, but that would be awkward. I'm supposing that I should get items into some kind of multidimensional hash where key would be order_id and then I could just refer to this hash by order id and get all items in it, but I'm not sure it's correct.
I hope what I have written here is understandable.
When you define a has_many relation, you automatically get the methods to query those objects. In this case, the method order.items.
So you can do:
Order.find_each do |order|
puts "Order #{order.id}:"
order.items.each do |item|
puts "Item #{item.id}"
end
end
(I used find_each method, which is available from Rails 2.3+. You could use a simple Order.all.each though.

Resources