문제

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.

다른 팁

In the example by @muffinista you should probably do it more 'Ruby-ish' and do it in a single line:

def update_average_rating
  update_attributes(average_rating: self.ratings.collect(&:value).avg)
end

If you expect nil's you can do .compact.avg.

You need to extend Array with #avg:

class Array
  def avg
    sum.to_f/size
  end
end
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top