Rails 3.1
I'll simplify my application to get at my question.
I have two tables: Items and Reviews
Items has a column "Average_Rating" and Reviews has a column "Item_ID" and "Rating"
For each item, I'd like to store the average rating for its corresponding reviews. Although I can't figure it out, I feel like what I want to do is add something to the create and update methods in the Reviews Controller along the lines of:
#review = Review.find(params[:id])
#item = Item.find(#review.Item_ID)
reviews_to_sum = Reviews.find_by_item_id(#item.id)
#item.Average_Rating = reviews_to_sum.Rating.sum/reviews_to_sum.count
I recognize, however, that the above probably isn't close to correct... I'm a beginner and I'm stuck.
And I do want to store the Average_Rating in the database, as opposed to calculating it when I need it, for a variety of reasons.
class Item < ActiveRecord::Base
has_many :reviews
end
class Review < ActiveRecord::Base
belongs_to :item
after_save do
item.update_attributes average_rating: item.reviews.average(:rating)
end
end
Related
I have the following models in my Ruby on Rails application in which I'm using Postgres Database:
class Sale < ApplicationRecord
has_many :line_items
end
class LineItem < ApplicationRecord
belongs_to :sale
end
There are now 2 things I want to achieve:
First, I'd like to create an index page in ActiveAdmin for Sales, so I could, for each sale, display the sum of line items prices.
What I already tried, which doesn't work very well(it's very slow):
ActiveAdmin.register Sale do
actions :index
remove_filter :line_items
index do
column :created_at
column :price do |sale|
sale.line_items.sum(:price)
end
end
end
Second, I'd like to make this column sortable - would it be possible?
My suggestion for your case above you can just group based sale_id and then sum the price and to the sort with method below, reverse will get you descending order
LineItem.group(:sale_id).sum(:price).sort_by{|k, v| v}.reverse
if you need probably top ten you can use first(10)
LineItem.group(:sale_id).sum(:price).sort_by{|k, v| v}.reverse.first(10)
Try registering a database view that sums the prices. The documentation on custom sorting should still apply. Links to tips and tricks like this can be found on the wiki.
I have three Models. Both makes and models table have name column inside.
class Review < ActiveRecord::Base
belongs_to :model
end
class Model < ActiveRecord::Base
belongs_to :make
has_many :reviews
end
class Make < ActiveRecord::Base
has_many :models
end
In Active Admin on Index Page I want to sort reviews by make and model.
I figured out how to sort it by model, because it has key the reviews table.
Tried a lot of things. Nothing works to sort by make name. I was able to list though.
ActiveAdmin.register Review do
column "Model", sortable: 'model_id' do |p|
p.model.name
end
column "Make", sortable: 'make_id' do |review|
review.model.make.name
end
end
Only sorting by model name works. I think if I add make_id to Reviews it will be working, but it seems redundant, cause a chain like review.model.make perfectly works
Let's say a review is a score between 1 and 5.
You will have to compute the average value for each make.
To compute an average value for a make, do:
reviews_sum = 0
total_reviews = 0
make.models.each do |model|
model.reviews.each do |review|
reviews_sum += review
total_reviews += 1
end
end
Then:
average_review = reviews_sum / total_reviews.to_f # I am using to_f to convert total_reviews from integer to float. This way you can have a float result.
I advice you to create an make_average_review(make_id, average_review)
This way you can store/update your average review for every make and then sort by make
I shall start by describing my associations. I have 6 resources. NationalOffice, Programme, Village, Discussion, Conversation, and Change. A NationalOffice has many Programmes. A Programme has many Villages. A Village has many Discussions. A Discussion has many Conversations. A Conversation has one (belongs_to) a Change.
In each Conversation a Change is talked about. The Conversation then gives the Change a rank. This is the table schema:
create_table "conversations", force: true do |t|
t.integer "discussion_id"
t.integer "change_id"
t.integer "rank" # 1 for 1st, 2 for 2nd, 3 for 3rd, ect.
end
What I want to do is the following: From the Discussion class I want to be able to pick out the top change. I have crudely implemented that with a helper:
def top_change discussion
conversation = discussion.conversations.order(:rank).first
# Incase there are no conversations for the discussion
if conversation.respond_to?('change')
conversation.change.name
else
'N/A'
end
end
If I take that up a level to the Village class I have an issue. How would I go through all the Discussions in a Village and find the top scoring Change? I would also have the same problem when trying it on the Programme class. And then the NationalOffice class.
This may be achievable through SQL or activerecord - I'm not certain.
I hope I have made myself clear - I sometimes have issues with clarity.
Edit:
It has been made apparent that I have not explained the Ranks correctly. I shall now attempt to explain how they work:
Each Conversation has a rank. Each Conversation is about a specific Change. So, if a Conversation about Change 1 is ranked 1st in that Discussion *Change* 1 will gain a 1st. In another Discussion a Conversation regarding Change 1 is ranked 2nd. So Change 1 now has a 1st and a 2nd (3 points?).
2 Discussions each have a Conversation that talks about Change 2. One Ranks it 1st the other 3rd. Change 2 has a 1st and a 3rd (4 points?)
Over all, Change 1 was the top change - it had less points (higher scoring) than Change 2.
Hopefully that is clear. This is the full application on github, just for context.
You can use has_many :through to achieve this.
http://ryandeussing.com/blog/2013/06/12/nested-associations-and-has-many-through/
Has_many through will add extra conditionals to join between 3 or more tables. So you should be able to use this for all the classes "above" Conversation.
Basically you can chain has_many :through as many times as you need to find the top conversation for any of the associated models.
In your models:
class Discussion < AR::Base
belongs_to :village
has_many :conversations
end
class Conversation < AR::Base
belongs_to :discussion
has_one :change
end
class Village < AR::Base
has_many :discussions
has_many :conversations, through: :discussions
end
class Programme < AR::Base
has_many :villages
has_many :conversations, though: :villages
end
In your controller:
class VillageController < ApplicationController
def top_change
village = Village.find(params[:id])
#change = village.conversations.order(:rank).first.change
end
end
class ProgrammesController < ApplicationController
def top_change
programme = Programme.find(params[:id])
#change = programme.conversations.order(:rank).first.change
end
end
I ended up working through the answer with James Strong in the #rubyonrails channel on freenode irc network. Here's what we got:
In the change model we added two actions:
def self.with_rank(load_conversation=true)
q = select("changes.*, SUM(11 - conversations.rank) as score").group("changes.id")
q = q.joins(:conversations) if load_conversation
return q
end
This went through all the conversations associated with a change, it then SUMmed all the ranks (but we made it so that higher was better. I.E. if the rank was 1: 1 - 11 = 10) and put it into the table as *conversations_rank*. It then joins the changes table with the conversations table.
def self.top_ranked(load_conversation=true)
with_rank(load_conversation).order("score desc").limit(1)
end
This next little snippet returns the top ranked change. It's pretty self explanatory.
In the Villages model we added a action that merged the top_rank query with the changes from that model (Villages.changes).
def top_change
changes.merge Change.top_ranked(false)
end
It also had the following association:
has_many :conversations, through: :discussions
This allows me to then do the following to get the top change's name:
#village.top_change.name
The biggest thing I have taken away from this experience it to break down big queries like this into small manageable chunks. For this we said:
Get changes with ranks based on conversions (their scores combined)
Get the highest rank
Get the highest rank within a village
Thanks again to jstrong in the IRC and all those who helped.
I think this is a pretty simple question, but I have an app that has User, Product, and Review models.
When a user creates a new review for a product, I currently have this in my create action for Review:
#review = #product.reviews.new(params[:review])
This works fine, but the review is associated with the product only and I also want to associate it with the creating user. However, if I do something like this:
#review = #product.reviews.new(params[:review])
#review = current_user.reviews.new(params[:review])
Would it create two instances of the review that are separate records? How could I do multiple associations like this with only one #review variable?
Just to note, I am not using polymorphic associations because as I understand it, I don't need to allow #review to belong to EITHER a product or user, as I always want to associate it with both.
Thus, in my Review model I have:
t.integer :product_id
t.integer :user_id
and my models look this:
Product:
has_many :reviews
User:
has_many :reviews
Review:
belongs_to :user
belongs_to :product
First, is this setup correct? Am I right in not using polymorphic associations? If so, how can I write my controller code to do what I want?
See Rails Associations
You probably want something like this:
#review = #product.reviews.new(params[:review])
current_user.reviews << #review
For example we have:
class PublicLibrary < ActiveRecord::Base
has_many :books
end
class Book < ActiveRecord::Base
belongs_to :public_library
end
If we want do update all books in PublicLibrary, we can add to PublicLibrary model
accepts_nested_attributes_for :books, :allow_destroy => true, :reject_if=>:all_blank
And now we can do something like this
library=PublicLibrary.find(ID)
library.update_attributes(:books_attributes=>{<bunch of books here>})
And all related books will be updated, some books'll be removed and some new books will be inserted in table books
Now I have some model Book, that doesn't have relation with PublicLibrary:
class Book < ActiveRecord::Base
end
I have an admin panel that show all books in one big-big table and want to update/delete/insert new books just by one click, so I want something like
Book.bulk_update({...books...})
Or working with subset (don't sure is I really need it, but if we can do that...why not to know how?)
books_to_update=Book.where(...).bulk_update({...books...})
Of course Book may have some nested models.
Do you have any ideas?
P.S. Currently I have only idea of having some parent and do update for it...
your {...books...} hash either has multiple books in it for which you want
books.each { |id, book| Book.find(id).update_attributes(book) }
or you want to do a real bulk update (meaning same update to all books in action)
then you can use http://apidock.com/rails/ActiveRecord/Relation/update_all