I'm following tutorial and have models user
, hotel
and rating
. Users can create hotels, and users can rate them. Users rating value is recorded to table rating
together with user_id
and hotel_id
. When I render partial <%= render "hotels/hotels_list", :@hotels => Hotel.all %>
it shows list of hotels with their average rating that calculates in model hotel
Model Hotel.rb :
class Hotel < ActiveRecord::Base
attr_accessible :user_id
belongs_to :user
has_many :ratings
has_many :raters, :through => :ratings, :source => :users
def average_rating
@value = 0
self.ratings.each do |rating|
@value = @value + rating.value
end
@total = self.ratings.size
'%.2f' % (@value.to_f / @total.to_f)
end
end
Model User.rb :
class User < ActiveRecord::Base
has_many :hotels
has_many :ratings
has_many :rated_hotels, :through => :ratings, :source => :hotels
end
Model Rating.rb :
class Rating < ActiveRecord::Base
attr_accessible :value
belongs_to :user
belongs_to :hotel
end
I need to sort list of hotels by average rating, maybe need to add some column average_rating
that will be at once calculate average value like that average_rating
method in hotel model, so than I can easily access to it. How can I solve this issue?
RatingsController.rb
class RatingsController < ApplicationController
before_filter :authenticate_user!
def create
@hotel = Hotel.find_by_id(params[:hotel_id])
@rating = Rating.new(params[:rating])
@rating.hotel_id = @hotel.id
@rating.user_id = current_user.id
if @rating.save
respond_to do |format|
format.html { redirect_to hotel_path(@hotel), :notice => "Your rating has been saved" }
format.js
end
end
end
def update
@hotel = Hotel.find_by_id(params[:hotel_id])
@rating = current_user.ratings.find_by_hotel_id(@hotel.id)
if @rating.update_attributes(params[:rating])
respond_to do |format|
format.html { redirect_to hotel_path(@hotel), :notice => "Your rating has been updated" }
format.js
end
end
end
end
Very simple. First, you would add the average_rating
column to your Hotel model with a migration. Then, you would add a callback to your Rating model which updates the value in the Hotel model. Basically, every time a rating is created, destroyed, or updated, you need to update the average rating. It would look something like this:
class Hotel < ActiveRecord::Base
[ code snipped ]
def update_average_rating
@value = 0
self.ratings.each do |rating|
@value = @value + rating.value
end
@total = self.ratings.size
update_attributes(average_rating: @value.to_f / @total.to_f)
end
end
class Rating
belongs_to :hotel
after_create :update_hotel_rating
def update_hotel_rating
hotel.update_average_rating
end
end
Now you can easily sort by rating. I'm leaving some details out but I think you can get the general idea here.