Rails: How to average multiple database entries after create - ruby-on-rails

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

ActiveAdmin sort by attribute sum of associated objects

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.

Active Admin sort by 2nd level Association

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

Top Ranked Item from Associations

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.

Rails - how to setup create action to make multiple associations?

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

Update all data of model in rails

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

Resources