Stuck - Joining 3 tables, performing function on - ruby-on-rails

Forgive me as newbie. I have a multi-level join table around the following...
Category
has_many :products
Products
has_many :sales
Sales
belongs_to :product
Within the category I am looking to display a list of each of the best-selling products (as determined by the number of sales, which are individually logged).
I can print a total sum of sale value on the product page OK (which is column sales.sum - my column is called sum), but cannot print a full list of all products fitting into a category, whilst performing a sum function on the sales.sum column for each product.
I currently have this code in category controller;
def show
#all = Category.joins(products: :sales)
end
I think this is right but I cannot get the right view. Appreciate this may not be the best way to present the question on SO, but tried many different ways to print it in my view file, but really stuck.
Could anyone point me in the right direction?
Thank you in advance! :-)
EDIT - to make it a bit clearer what I'm trying to achieve...
Category -> Products -> Sales
Want to display a list of each Product that belongs to a Category (by category_id), but put them in list of total Sales (which belongs to Products by product_id. Column is called 'sum' within the Sales table and shows value such as $100 Product 1, $50 Product 2, $40 Product 1, et cetera).
So, multi level join.

For those interested, finally worked out the answer;
<ol>
<%= #category.products.each do |su| %>
<li><%= su.name %> <%= su.sales.sum(:sum) %></li>
<% end %>
</ol>
Hope this helps someone else!

Related

Check if value in one table is present in another

So this is probably easy but I haven't been able to find the right method for it. I have 2 models. One called monitor, and one called follower.
In 'monitor' I have a column called owner_id.
In 'follower' I have a column called follower_id.
What I would like to do is check if any of these match up (I'd like to get a count, not a boolean output). Both belongs_to Users.
How do I go about doing this?
Situation
I am trying to calculate a conversion rate from Twitter. Where the follower and the follower ids are the users who is following your account.
On the monitor I let you monitor keyword and interactions, where I save the owner_id (the person you're communicating with).
Now I count all the conversations you have had. Then I want to see how many of those that have turned into following your company.
I have a model called campaigns where you can monitor certain keywords.
<% #campaigns.each do |campaign| %>
<%= campaign.keyword %>
<% end %>
The model looks like this:
class Campaign < ActiveRecord::Base
has_many :alerts
end
now what I want to do is track the conversion rate for that specific campaign.
( CODE HERE* / campaign.alerts.count ) * 100
where CODE HERE* should be the count of how many that exists between :
campaign.alerts.map(&:owner_id)
and
current_user.followers.map(&:follower_id)
so what you are trying to do is just to compare two big arrays of ids (campagin.alerts.map(&:owner_id) and current_user.followers.map(&:follower_id)) and count how many ids are the same, you can use count and count everything that your block expression evaluates as true, and save that on a variable that you will use on your division, something like this:
result = (campagin.alerts.map(&:owner_id).count do |id|
current_user.followers.map(&:follower_id)).include?(id)
end
( result / campaign.alerts.count ) * 100
There are many other ways like using Array#all or other methods, maybe you can look at them here and see what fits you most:
http://ruby-doc.org/core-2.2.0/Array.html#method-i-2D

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 Average Multiple Columns in Rails

I have the following objects: Products, Ratings, and Users. I am trying to filter and select a number of Products that the User owns (through a has_many :through relationship with UserProducts) and average a certain column the Ratings table that matches their User ID and the correct Product ID.
So, my function is something along these lines:
def find_rating(criteria)
product = self.products.find(:all, :conditions => ["criteria = ?", criteria])
rating = self.ratings.where("product_id = ?", product).average(:overall)
end
I think that I'm going about this the wrong way, because I'm trying to find a product_id by passing an entire array of data consisting of multiple products. But, I think of using a more traditional loop and that seems convoluted. Can someone point me in the right direction for solving this problem? Thanks!
If product is a single entry, as it appears to be in your code, I would do this:
rating = self.products.find_by_criteria(criteria).ratings.average(:overall)
If it's an array of products, this method may help you: http://apidock.com/rails/ActiveRecord/Batches/ClassMethods/find_each

Rails, two dimensional table, pivot, nested hash loops

I am building grade-book report - a two dimensional table that shows lesson names going horizontally and a list of students going vertically.
Student Name | LessonID x | LessonID x | LessonID x
Joe 95% 95%
Mary 80% 80%
Sam 80% 80%
My data is in a table that has these fields:
student_id, lesson_id, grade_in_pct, grade_in_pts, grade_high, grade_low, grade_median
The total number of students and lessons is not fixed.
I considered using ruport/acts_as_reportable or mysql pivot procedure, however it looks like the pivot only gives me one dimension. So that's not going to work, because in my view I want to add mouse-over features and conditional formatting to show more info on each grade.
So I think my only option is to generate a nested hash and then loop through it in the view. What are your thoughts? Could someone suggest a way to build a nested hash? Would it be too processor intensive to loop through 250 rows (~50 students, 5 lessons each)?
I am stuck. Please help. Thanks!
This is how I would do it:
MODELS:
Student Model:
has_many: Grades
has_and_belongs_to_many: Lessons
Lesson Model:
has_many: Grades
has_and_belongs_to_many: Students
Grade Model:
belongs_to: Student, Lesson
CONTROLLER:
#data = Student.all
#lessons = Lesson.all
VIEW:
header_row
#data.each do |student|
#lessons.each do |lesson|
student.grades.find_by_lesson(lesson).some_data

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