Module: ActiveRecord::Acts::Rated::ClassMethods
- Defined in:
- lib/acts_as_rated.rb,
lib/acts_as_rated.rb
Instance Method Summary collapse
-
#acts_as_rated(options = {}) ⇒ Object
Make the model ratable.
-
#add_ratings_columns ⇒ Object
Create the needed columns for acts_as_rated.
-
#create_ratings_table(options = {}) ⇒ Object
Create the ratings table === Options hash: *
:with_rater
- add the rated_id column *:table_name
- use a table name other than ratings *:with_stats_table
- create also a rating statistics table *:stats_table_name
- the name of the rating statistics table. -
#drop_ratings_table(options = {}) ⇒ Object
Drop the ratings table.
-
#find_by_rating(value, precision = 10, round = true) ⇒ Object
Find by rating - pass either a specific value or a range and the precision to calculate with *
value
- the value to look for or a range *precision
- number of decimal digits to round to. -
#find_rated_by(rater) ⇒ Object
Find all ratings for a specific rater.
-
#generate_ratings_columns(table) ⇒ Object
Generate the ratings columns on a table, to be used when creating the table in a migration.
-
#remove_ratings_columns ⇒ Object
Remove the acts_as_rated specific columns added with add_ratings_columns To be used during migration, but can also be used in other places.
Instance Method Details
#acts_as_rated(options = {}) ⇒ Object
Make the model ratable. Can work both with and without a rater entity (defaults to User). The Rating model, holding the details of the ratings, will be created dynamically if it doesn’t exist.
-
Adds a
has_many :ratings
association to the model for easy retrieval of the detailed ratings. -
Adds a
has_many :raters
association to the onject, unless:no_rater
is given as a configuration parameter. -
Adds a
has_many :ratings
associations to the rater class. -
Adds a
has_one :rating_statistic
association to the model, if:with_stats_table => true
is given as a configuration param.
Options
-
:rating_class
- class of the model used for the ratings. Defaults to Rating. This class will be dynamically created if not already defined. If the class is predefined, it must have in it the following definitions:belongs_to :rated, :polymorphic => true
and if using a rater (which is true in most cases, see below) alsobelongs_to :rater, :class_name => 'User', :foreign_key => :rater_id
replace user with the rater class if needed. -
:rater_class
- class of the model that creates the rating. Defaults to User This class will NOT be created, so it must be defined in the app. Another option will be to keep a session or IP based ID here to prevent multiple ratings from the same client. -
:no_rater
- do not keep track of who created the rating. This will change the behaviour to one that just collects and averages ratings, but doesn’t keep track of who posted the rating. Useful in a public application that doesn’t care about individual votes -
:rating_range
- A range object for the acceptable rating value range. Defaults to not limited -
:with_stats_table
- Use a separate statistics table to hold the count/total/average rating of the rated object instead of adding the columns to the object’s table. This means we do not have to change the model table. It still holds a big performance advantage over using SQL to get the statistics -
:stats_class - Class of the statics table model. Only needed if <tt>:with_stats_table
is set to true. Default to RatingStat. This class need to have the following defined:belongs_to :rated, :polymorphic => true
. And must make sure that it has the attributesrating_count
,rating_total
andrating_avg
and those must be initialized to 0 on new instances
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/acts_as_rated.rb', line 88 def acts_as_rated( = {}) # don't allow multiple calls return if self.included_modules.include?(ActiveRecord::Acts::Rated::RateMethods) send :include, ActiveRecord::Acts::Rated::RateMethods # Create the model for ratings if it doesn't yet exist = [:rating_class] || 'Rating' rater_class = [:rater_class] || 'User' stats_class = [:stats_class] || 'RatingStatistic' if [:with_stats_table] unless Object.const_defined?() Object.class_eval <<-EOV class #{} < ActiveRecord::Base belongs_to :rated, :polymorphic => true #{[:no_rater] ? '' : "belongs_to :rater, :class_name => #{rater_class}, :foreign_key => :rater_id"} end EOV end unless stats_class.nil? || Object.const_defined?(stats_class) Object.class_eval <<-EOV class #{stats_class} < ActiveRecord::Base belongs_to :rated, :polymorphic => true end EOV end raise RatedError, ":rating_range must be a range object" unless [:rating_range].nil? || (Range === [:rating_range]) write_inheritable_attribute( :acts_as_rated_options , { :rating_range => [:rating_range], :rating_class => , :stats_class => stats_class, :rater_class => rater_class } ) class_inheritable_reader :acts_as_rated_options class_eval do has_many :ratings, :as => :rated, :dependent => :delete_all, :class_name => .to_s has_many(:raters, :through => :ratings, :class_name => rater_class.to_s) unless [:no_rater] has_one(:rating_statistic, :class_name => stats_class.to_s, :as => :rated, :dependent => :delete) unless stats_class.nil? before_create :init_rating_fields end # Add to the User (or whatever the rater is) a has_many ratings if working with a rater return if [:no_rater] rater_as_class = rater_class.constantize return if rater_as_class.instance_methods.include?('find_in_ratings') rater_as_class.class_eval <<-EOS has_many :ratings, :foreign_key => :rater_id, :class_name => #{.to_s} EOS end |
#add_ratings_columns ⇒ Object
Create the needed columns for acts_as_rated. To be used during migration, but can also be used in other places.
303 304 305 306 307 308 309 310 |
# File 'lib/acts_as_rated.rb', line 303 def if !self.column_names.include? 'rating_count' self.connection.add_column table_name, :rating_count, :integer self.connection.add_column table_name, :rating_total, :decimal self.connection.add_column table_name, :rating_avg, :decimal, :precision => 10, :scale => 2 self.reset_column_information end end |
#create_ratings_table(options = {}) ⇒ Object
Create the ratings table
Options hash:
-
:with_rater
- add the rated_id column -
:table_name
- use a table name other than ratings -
:with_stats_table
- create also a rating statistics table -
:stats_table_name
- the name of the rating statistics table. Defaults to :rating_statistics
To be used during migration, but can also be used in other places
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 |
# File 'lib/acts_as_rated.rb', line 328 def = {} with_rater = [:with_rater] != false name = [:table_name] || :ratings stats_table = [:stats_table_name] || :rating_statistics if [:with_stats_table] self.connection.create_table(name) do |t| t.column(:rater_id, :integer) unless !with_rater t.column :rated_id, :integer t.column :rated_type, :string t.column :rating, :decimal end self.connection.add_index(name, :rater_id) unless !with_rater self.connection.add_index name, [:rated_type, :rated_id] unless stats_table.nil? self.connection.create_table(stats_table) do |t| t.column :rated_id, :integer t.column :rated_type, :string t.column :rating_count, :integer t.column :rating_total, :decimal t.column :rating_avg, :decimal, :precision => 10, :scale => 2 end self.connection.add_index stats_table, [:rated_type, :rated_id] end end |
#drop_ratings_table(options = {}) ⇒ Object
Drop the ratings table.
Options hash:
-
:table_name
- the name of the ratings table, defaults to ratings -
:with_stats_table
- remove the special rating statistics as well -
:stats_table_name
- the statistics table name. Defaults to :rating_statistics
To be used during migration, but can also be used in other places
362 363 364 365 366 367 |
# File 'lib/acts_as_rated.rb', line 362 def = {} name = [:table_name] || :ratings stats_table = [:stats_table_name] || :rating_statistics if [:with_stats_table] self.connection.drop_table name self.connection.drop_table stats_table unless stats_table.nil? end |
#find_by_rating(value, precision = 10, round = true) ⇒ Object
Find by rating - pass either a specific value or a range and the precision to calculate with
-
value
- the value to look for or a range -
precision
- number of decimal digits to round to. Default to 10. Use 0 for integer numbers comparision -
round_it
- round the rating average before comparing?. Defaults to true. Passing false will result in a faster query
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 |
# File 'lib/acts_as_rated.rb', line 386 def value, precision = 10, round = true = [:rating_class].constantize if column_names.include? "rating_avg" if Range === value conds = round ? [ 'round(rating_avg, ?) BETWEEN ? AND ?', precision.to_i, value.begin, value.end ] : [ 'rating_avg BETWEEN ? AND ?', value.begin, value.end ] else conds = round ? [ 'round(rating_avg, ?) = ?', precision.to_i, value ] : [ 'rating_avg = ?', value ] end find :all, :conditions => conds else if round base_sql = <<-EOS select #{table_name}.*,round(COALESCE(average,0), #{precision.to_i}) AS rating_average from #{table_name} left outer join (select avg(rating) as average, rated_id from #{.table_name} where rated_type = '#{class_name}' group by rated_id) as rated on rated_id=id EOS else base_sql = <<-EOS select #{table_name}.*,COALESCE(average,0) AS rating_average from #{table_name} left outer join (select avg(rating) as average, rated_id from #{.table_name} where rated_type = '#{class_name}' group by rated_id) as rated on rated_id=id EOS end if Range === value if round where_part = " WHERE round(COALESCE(average,0), #{precision.to_i}) BETWEEN #{connection.quote(value.begin)} AND #{connection.quote(value.end)}" else where_part = " WHERE COALESCE(average,0) BETWEEN #{connection.quote(value.begin)} AND #{connection.quote(value.end)}" end else if round where_part = " WHERE round(COALESCE(average,0), #{precision.to_i}) = #{connection.quote(value)}" else where_part = " WHERE COALESCE(average,0) = #{connection.quote(value)}" end end find_by_sql base_sql + where_part end end |
#find_rated_by(rater) ⇒ Object
Find all ratings for a specific rater. Will raise an error if this acts_as_rated is without a rater.
371 372 373 374 375 376 377 378 379 |
# File 'lib/acts_as_rated.rb', line 371 def find_rated_by rater = [:rating_class].constantize raise RateError, "The rater object must be the one used when defining acts_as_rated (or a descendent of it). other objects are not acceptable" if !([:rater_class].constantize === rater) raise RateError, 'Cannot find_rated_by if not using a rater' if !.column_names.include? "rater_id" raise RateError, "Rater must be an existing object with an id" if rater.id.nil? rated_class = ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s conds = [ 'rated_type = ? AND rater_id = ?', rated_class, rater.id ] [:rating_class].constantize.find(:all, :conditions => conds).collect {|r| r.rated_type.constantize.find_by_id r.rated.id } end |
#generate_ratings_columns(table) ⇒ Object
Generate the ratings columns on a table, to be used when creating the table in a migration. This is the preferred way to do in a migration that creates new tables as it will make it as part of the table creation, and not generate ALTER TABLE calls after the fact
295 296 297 298 299 |
# File 'lib/acts_as_rated.rb', line 295 def table table.column :rating_count, :integer table.column :rating_total, :decimal table.column :rating_avg, :decimal, :precision => 10, :scale => 2 end |
#remove_ratings_columns ⇒ Object
Remove the acts_as_rated specific columns added with add_ratings_columns To be used during migration, but can also be used in other places
314 315 316 317 318 319 |
# File 'lib/acts_as_rated.rb', line 314 def if self.column_names.include? 'rating_count' self.connection.remove_columns table_name, :rating_count, :rating_total, :rating_avg self.reset_column_information end end |