Skip to content
Browse files

- changed a lot (rateable :by, :concerning ...; specs)

- going to replace is remixable with custom solution
  • Loading branch information...
1 parent 5aa991f commit 2c95252835590f09583b4fe17b4306ca12d7f3a4 @Ragmaanir committed
View
2 Manifest.txt
@@ -8,5 +8,5 @@ lib/dm-is-rateable.rb
lib/dm-is-rateable/is/rateable.rb
lib/dm-is-rateable/is/version.rb
spec/integration/rateable_spec.rb
-spec/spec.opts
spec/spec_helper.rb
+.rspec
View
9 README.textile
@@ -38,14 +38,13 @@ class Trip
# allowing the following options:
#
# options = {
- # :rater => { :name => :user_id, :type => Integer },
- # :allowed_ratings => (0..5),
+ # :by => { :key => :user_id, :type => Integer },
+ # :with => (0..5),
# :timestamps => true,
- # :as => nil, # if symbol/string then add an alias by that name on has n, :ratings
- # :class_name => "#{self}Rating" # class_name to use for generated remixed model
+ # :as => nil # if symbol/string then add an alias by that name on has n, :ratings
# }
- is :rateable #, options
+ is :rateable, :by => :users
end
</code>
View
426 lib/dm-is-rateable/is/rateable.rb
@@ -29,17 +29,34 @@ def self.deduce_rating_type(allowed_ratings)
def self.infer_rater(by_option)
case by_option
when Symbol
- name = by_option.to_s.singularize.to_sym
+ name = by_option.to_s.singularize
{
- :name => name,
- :model => name.to_s.camelize,
- :key => Inflector.foreign_key(name.to_s).to_sym,
+ :name => name.to_sym,
+ :model => name.classify,
+ :key => Inflector.foreign_key(name).to_sym,
:type => Integer,
:options => { :required => true, :min => 0 } # FIXME only merge :min when type is integer
}
when Hash
by_option.assert_valid_keys(:name,:key,:type,:model,:options)
- by_option
+ name = by_option[:name].to_s
+ key = by_option[:key] || Inflector.foreign_key(name).to_sym
+ model = key.to_s.gsub(/_id/,'').classify
+ {
+ :model => model,
+ :key => key,
+ :type => Integer,
+ :options => { :required => true, :min => 0 } # FIXME only merge :min when type is integer
+ }.merge(by_option)
+ when DataMapper::Model
+ name = by_option.name.downcase
+ {
+ :name => name.to_sym,
+ :model => by_option.name,
+ :key => Inflector.foreign_key(name).to_sym,
+ :type => Integer,
+ :options => { :required => true, :min => 0 } # FIXME only merge :min when type is integer
+ }
when nil then raise ":by option missing"
else raise "invalid value for :by: #{by_option}"
end
@@ -48,9 +65,9 @@ def self.infer_rater(by_option)
module Rating
- def self.included(base)
- base.extend ClassMethods
- end
+# def self.included(base)
+# base.extend ClassMethods
+# end
include DataMapper::Resource
@@ -58,23 +75,23 @@ def self.included(base)
property :id, Serial
- module ClassMethods
-
- # total rating for all rateable instances of this type
- def total_rating
- rating_sum = self.sum(:rating).to_f
- rating_count = self.count.to_f
- rating_count > 0 ? rating_sum / rating_count : 0
- end
-
- end
+# module ClassMethods
+#
+# # total rating for all rateable instances of this type
+# def total_rating
+# rating_sum = self.sum(:rating).to_f
+# rating_count = self.count.to_f
+# rating_count > 0 ? rating_sum / rating_count : 0
+# end
+#
+# end
end
# is :rateable, :by => :users, :with => 0..5, :as => :user_ratings, :model => 'SpecialUserRating' do
# def xyz; true; end
# end
#
- # is :rateable, :by => {:name => :user, :fk => :user_id, :model => 'User'}
+ # is :rateable, :by => {:name => :user, :key => :user_id, :model => 'User'}
def is_rateable(options = {},&enhancer)
extend ClassMethods
@@ -82,27 +99,27 @@ def is_rateable(options = {},&enhancer)
rater = Helper.infer_rater(options[:by])
- raise(NotImplementedError,':model options not supported yet') if options[:model]
+ aspect = (options[:concerning] || '').camelize
+ rating_name = "#{aspect}Rating"
+ rateable_name = self.name
+
+ model = options[:model] || "#{rateable_name}#{rating_name}By#{rater[:model]}"
+ #as = model.underscore.pluralize.gsub("#{self.name.underscore}_",'').to_sym
+ as = if options[:model]
+ model.underscore.pluralize.gsub("#{rateable_name.underscore}_",'').to_sym
+ else
+ "ratings_by_#{rater[:name].to_s.pluralize}".to_sym
+ end
options = {
:with => 0..5,
:by => rater,
- :as => "#{rater[:name]}_ratings".to_sym,
+ #:as => "#{rater[:name]}_ratings".to_sym,
+ :as => as,
:timestamps => true,
- :model => "#{self.name}#{rater[:model]}Rating"
+ :model => model
}.merge(options)
- # FIXME remove class attributes that assume only one rating
- #class_attribute :allowed_ratings
- #self.allowed_ratings = options[:with]
- #
- #class_attribute :rateable_model
- #self.rateable_model = options[:model]
- #
- #class_attribute :rateable_key
- ##self.rateable_key = rateable_model.underscore.to_sym
- #self.rateable_key = rateable_model.underscore.to_sym
-
if self.respond_to?(:rating_configs)
raise("duplicate is :rateable, :by => #{rater[:model]}") if rating_configs.find{ |model,config|
config[:by][:model] == rater[:model]
@@ -115,9 +132,9 @@ def is_rateable(options = {},&enhancer)
# add rating config
self.rating_configs.merge!(options[:model] => options.merge(:by => rater))
- remix n, Rating, :as => options[:as], :model => options[:model], :for => rater[:name]
+ remix n, Rating, :as => options[:as], :model => options[:model], :for => rater[:model]
- class_attribute :remixed_rating
+ class_attribute :remixed_rating # FIXME remove
remix_name = options[:model].underscore.to_sym
@@ -127,38 +144,23 @@ def is_rateable(options = {},&enhancer)
# determine property type based on supplied values
rating_type = Helper.deduce_rating_type(options[:with])
- #class_attribute :rater_fk
- ##self.rater_fk = rater_opts.try(:[],:fk) # tmp_rater_fk
- #self.rater_fk = rater[:key] #rater[:key]
-
# close on this because enhance will class_eval in remixable model scope
rateable_key = self.rateable_fk
+ rateable_model = options[:model].constantize
+ rateable_name = self.name.underscore
+ parent_assocation = rateable_key.to_s.gsub(/_id/, '').to_sym
# enhance the rating model
enhance :rating, options[:model] do
property :rating, rating_type, :required => true
- #property :rating, rating_type, :required => true
-
- # XXX
- #p "-"*10
- #p options[:as]
- #belongs_to options[:as], model_name
-
- #if options[:rater]
- # property rater_name, rater_type, rater_property_opts # rater
- # belongs_to rater_association
- #
- # parent_assocation = parent_key.to_s.gsub(/_id/, '').to_sym
- # validates_uniqueness_of rater_name, :when => :testing_association, :scope => [parent_assocation]
- # validates_uniqueness_of rater_name, :when => :testing_property, :scope => [parent_key]
- #end
+
+ #belongs_to options[:as], model_name # XXX
if rater
property rater[:key], rater[:type], rater[:options]
- belongs_to rater[:name] # FIXME key
+ belongs_to rater[:name], rater[:model]
- parent_assocation = rateable_key.to_s.gsub(/_id/, '').to_sym
validates_uniqueness_of rater[:key], :when => :testing_association, :scope => [parent_assocation]
validates_uniqueness_of rater[:key], :when => :testing_property, :scope => [rateable_key]
end
@@ -168,26 +170,31 @@ def is_rateable(options = {},&enhancer)
class_eval(&enhancer) if enhancer
end
- # create average method for the ratings, e.g. for users: average_user_rating
- #singular_reader = remixed_rating[:reader].to_s.singularize
- singular_name = options[:as].to_s.singularize
+ # add :rater and :rateable relationships to rating model
+ rateable_model.class_eval do
+ define_method :rater do
+ send(rater[:name])
+ end
+
+ define_method :rateable do
+ send(rateable_name)
+ end
+ end
+
+# # create average method for the ratings, e.g. for users: average_user_rating
+# #singular_reader = remixed_rating[:reader].to_s.singularize
+# singular_name = options[:as].to_s.singularize
+#
+# define_method("average_#{singular_name}") do
+# average_rating_of(rater[:name].to_s.pluralize.to_sym)
+# end
+
+ singular_name = options[:as].to_s.singularize
- define_method("average_#{singular_name}") do
+ define_method("average_#{rating_name.underscore}_by_#{rater[:name].to_s.pluralize}") do
average_rating_of(rater[:name].to_s.pluralize.to_sym)
end
-=begin
- self.class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- def average_#{singular_reader}
- scope = { self.class.rateable_fk => self.id }
- model_class = self.class.remixables[:rating][rateable_key][:model]
- rating_sum = model_class.sum(:rating, scope).to_f
- rating_count = model_class.count(scope).to_f
- rating_count > 0 ? rating_sum / rating_count : 0
- end
- EOS
-=end
-
end
module ClassMethods
@@ -196,9 +203,9 @@ def rating_togglable?
self.properties.named? :rating_enabled
end
- def anonymous_rating_togglable?
- self.properties.named? :anonymous_rating_enabled
- end
+# def anonymous_rating_togglable?
+# self.properties.named? :anonymous_rating_enabled
+# end
#def total_rating
# remixables[:rating][rateable_key][:model].total_rating
@@ -225,15 +232,9 @@ def rating_config_for(raters_or_model)
# rating_model_for(User) #=> RateableUserRating
def rating_model_for(raters_or_model)
cfg = rating_config_for(raters_or_model)
- Kernel.const_get(cfg[:model])
+ cfg[:model].constantize
end
- #def average_rating_for(rateable,rater_model)
- # raise unless rater_model.is_a? DataMapper::Model
- # cfg = rating_config_for(rater_model)
- # rateable.send("average_#{cfg[:as].to_s.singularize}")
- #end
-
end
module InstanceMethods
@@ -242,28 +243,28 @@ def rating_togglable?
self.class.rating_togglable?
end
- def anonymous_rating_togglable?
- self.class.anonymous_rating_togglable?
- end
+# def anonymous_rating_togglable?
+# self.class.anonymous_rating_togglable?
+# end
def rating_enabled?
self.rating_togglable? ? attribute_get(:rating_enabled) : true
end
- def anonymous_rating_enabled?
- self.anonymous_rating_togglable? ? attribute_get(:anonymous_rating_enabled) : false
- end
+# def anonymous_rating_enabled?
+# self.anonymous_rating_togglable? ? attribute_get(:anonymous_rating_enabled) : false
+# end
# convenience method
def rating_disabled?
!self.rating_enabled?
end
- # convenience method
- def anonymous_rating_disabled?
- !self.anonymous_rating_enabled?
- end
+# # convenience method
+# def anonymous_rating_disabled?
+# !self.anonymous_rating_enabled?
+# end
def disable_rating!
@@ -288,7 +289,7 @@ def enable_rating!
end
end
-
+=begin
def disable_anonymous_rating!
if self.anonymous_rating_togglable?
if self.anonymous_rating_enabled?
@@ -308,22 +309,8 @@ def enable_anonymous_rating!
raise TogglableAnonymousRatingDisabled, "Anonymous Ratings cannot be toggled for #{self}"
end
end
-
-
- #def rater
- # self.class.rater_fk.to_s.gsub(/_id/, '').to_sym
- #end
-
- # derpecated
- #def rating
- # puts 'deprecated: use average_xyz_rating'
- # scope = { self.class.rateable_fk => self.id }
- # model_class = self.class.remixables[:rating][rateable_key][:model]
- # rating_sum = model_class.sum(:rating, scope).to_f
- # rating_count = model_class.count(scope).to_f
- # rating_count > 0 ? rating_sum / rating_count : 0
- #end
-
+=end
+
def rate(rating, rater)
unless self.rating_enabled?
raise(RatingDisabled, "Ratings are not enabled for #{self}")
@@ -337,7 +324,7 @@ def rate(rating, rater)
#if r = self.user_rating(rater)
#if r = self.rating_association_for(rating_model)(rater)
- if r = self.rating_for(rater)
+ if r = self.rating_of(rater)
if r.rating != rating
r.update(:rating => rating) or raise
end
@@ -352,10 +339,6 @@ def rate(rating, rater)
end
- #def rating_config_for(rater_cls)
- # self.class.rating_configs.find{ |model,conf| conf[:by][:model] == rater_cls.to_s }
- #end
-
# average_rating_of(:users) => nil
# average_rating_of(Account) => 3.76
def average_rating_of(raters_or_model)
@@ -370,224 +353,17 @@ def rating_assoc_for(rater)
self.send(config[:as])
end
- def rating_for(rater)
+ # FIXME rating_for or rating_of???
+
+ def rating_of(rater)
raise unless rater.is_a? DataMapper::Resource
config = self.class.rating_config_for(rater.class)
rating_assoc_for(rater).first(config[:by][:key] => rater.id)
end
-
-# def rate(rating, rater = nil)
-# unless self.rating_enabled?
-# raise(RatingDisabled, "Ratings are not enabled for #{self}")
-# end
-#
-# unless self.class.allowed_ratings.include?(rating)
-# raise(ImpossibleRatingValue, "Rating (#{rating}) must be in #{allowed_ratings.inspect}")
-# end
-#
-# if rater
-# if r = self.user_rating(rater)
-# if r.rating != rating
-# r.update(:rating => rating) or raise
-# end
-# else
-# # FIXME: save or raise
-# rater_type = rater.class.name
-# rater_name = rater_type.underscore.to_sym
-# rateable_name = self.class.name.underscore.to_sym
-# rating_model = Kernel.const_get("#{self.class.name}#{rater_type}Rating") # FIXME get model name
-# res = rating_model.create(rater_name => rater, rateable_name => self, :rating => rating)
-# #res = self.ratings.create(self.rater => user, :rating => rating)
-# raise(res.errors.inspect) unless res.saved?
-# res
-# end
-# else
-# unless self.anonymous_rating_enabled?
-# raise(AnonymousRatingDisabled, "Anonymous ratings are not enabled for #{self}")
-# end
-#
-# # FIXME: save or raise
-# res = self.ratings.create(:rating => rating)
-# #rating_model = Kernel.const_get("#{self.class.name}#{rater_type}Rating") # FIXME get model name
-# #res = rating_model.create(rater_name => user, rateable_name => self, :rating => rating)
-# raise(res.errors.inspect) unless res.saved?
-# res
-# end
-#
-# end
- #def user_rating(user, conditions = {})
- # self.ratings(conditions.merge(self.class.rater_fk => user.id)).first
- #end
+ #alias_method :rating_for, :rating_of
end
-
-=begin
- # FIXME is :rateable, :by => :user
- def is_rateable(options = {})
-
- extend ClassMethods
- include InstanceMethods
-
- options = {
- :allowed_ratings => (0..5),
- :timestamps => true,
- :as => nil,
- :model => "#{self}Rating"
- }.merge(options)
-
- class_attribute :allowed_ratings
- self.allowed_ratings = options[:allowed_ratings]
-
- class_attribute :rateable_model
- self.rateable_model = options[:model]
-
- class_attribute :rateable_key
- self.rateable_key = rateable_model.underscore.to_sym
-
- #def rater_fk_name(name)
- # name ? DataMapper::Inflector.foreign_key(name.to_s).to_sym : :user_id
- #end
-
- def rater_fk_name(name)
- DataMapper::Inflector.foreign_key((name || :user).to_s).to_sym
- end
-
- rater_o = options[:rater]
- rater_n = if rater_o
- rater_o.is_a?(Hash) ? (rater_o[:name] || :user) : rater_o
- end
-
- #remix n, Rating, :as => options[:as], :model => options[:model], :for => rater_n
-
- # FIXME TripUserRating not UserTripRating?
- model_name = "#{self}#{rater_n.to_s.camelize}Rating" if rater_n
- model_name ||= options[:model]
-
- remix n, Rating, :as => options[:as], :model => model_name, :for => rater_n
-
- remix_name = model_name.underscore.to_sym
-
- puts "model name: #{model_name}"
- puts "rater name: #{rater_n}"
- puts "remix_name: #{remix_name.inspect}"
- puts "remixables: #{remixables}"
- puts "rateable_key: #{rateable_key.inspect}"
-
- class_attribute :remixed_rating
-
- #self.remixed_rating = remixables[:rating]
- self.remixed_rating = remixables[:rating][remix_name]
-
- if remixed_rating[:reader] != :ratings
- p "creating alias for :ratings : :#{remixed_rating[:reader]}"
- self.class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- alias :ratings :#{remixed_rating[:reader]}
- EOS
- end
-
- #if remixed_rating[:reader] != :ratings
- # self.class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- # alias :ratings :#{remixed_rating[rateable_key][:reader]}
- # EOS
- #end
-
- # determine property type based on supplied values
- rating_type = Helper.deduce_rating_type(options[:allowed_ratings])
- #rating_type = case options[:allowed_ratings]
- # when Range then
- # options[:allowed_ratings].first.is_a?(Integer) ? Integer : String
- # when Enum then
- # require 'dm-types'
- # DataMapper::Types::Enum
- # else
- # msg = "#{options[:allowed_ratings].class} is no supported rating type"
- # raise ImpossibleRatingType, msg
- #end
-
-
- # prepare rating enhancements
-
- #def rater_fk(name)
- # name ? DataMapper::Inflector.foreign_key(name.to_s.singularize).to_sym : :user_id
- #end
-
-
- #tmp_rater_fk = if options[:rater]
- # rater_opts = options[:rater]
- # rater_name = rater_opts.is_a?(Hash) ? (rater_opts.delete(:name) || :user_id) : rater_fk_name(rater_opts)
- # rater_type = rater_opts.is_a?(Hash) ? (rater_opts.delete(:type) || Integer) : Integer
- # #rater_property_opts = rater_opts.is_a?(Hash) ? rater_opts : { :required => true }
- # rater_property_opts = rater_opts.is_a?(Hash) ? rater_opts : { :required => false }
- # rater_property_opts.merge!(:min => 0) if rater_type == Integer # Match referenced column type
- # rater_association = rater_name.to_s.gsub(/_id/, '').to_sym
- #
- # rater_name
- #else
- # nil # no rater association established
- #end
-
- # dm-rateable:
- # options[:rater] can be
- # - hash: {:name, :type} + dm-property-opts
- # - string/sym like :user
- rater_opts = if options[:rater]
- opts = options[:rater]
- r_name = (opts.is_a?(Hash) ? (opts[:name] || :user) : opts).to_sym
- r_fk = DataMapper::Inflector.foreign_key(r_name)
- #r_type = opts.is_a?(Hash) ? (opts[:type] || Integer) : Integer
- r_type = opts[:type] if opts.is_a?(Hash)
- r_type ||= Integer
- r_property = opts.is_a?(Hash) ? opts.except(:name,:type) : {:required => false}
- r_property.merge!(:min => 0) if r_type == Integer
-
- {:name => r_name, :fk => r_fk, :type => r_type, :property => r_property}
- else
- nil
- end
-
- #class_inheritable_reader :rater_fk
- class_attribute :rater_fk
- self.rater_fk = rater_opts.try(:[],:fk) # tmp_rater_fk
-
- # close on this because enhance will class_eval in remixable model scope
- parent_key = self.rateable_fk
-
- #enhance :rating, rateable_model do
- enhance :rating, model_name do
-
- property :rating, rating_type, :required => true
- property :rating, rating_type, :required => true
-
- # XXX
- #p "-"*10
- #p options[:as]
- #belongs_to options[:as], model_name
-
- #if options[:rater]
- # property rater_name, rater_type, rater_property_opts # rater
- # belongs_to rater_association
- #
- # parent_assocation = parent_key.to_s.gsub(/_id/, '').to_sym
- # validates_uniqueness_of rater_name, :when => :testing_association, :scope => [parent_assocation]
- # validates_uniqueness_of rater_name, :when => :testing_property, :scope => [parent_key]
- #end
-
- if rater_opts
- property rater_opts[:fk], rater_opts[:type], rater_opts[:property]
- belongs_to rater_opts[:name]
-
- parent_assocation = parent_key.to_s.gsub(/_id/, '').to_sym
- validates_uniqueness_of rater_opts[:fk], :when => :testing_association, :scope => [parent_assocation]
- validates_uniqueness_of rater_opts[:fk], :when => :testing_property, :scope => [parent_key]
- end
-
- timestamps(:at) if options[:timestamps]
-
- end
-
- end
-=end
end
end
View
164 spec/integration/is_rateable_by_model_spec.rb
@@ -0,0 +1,164 @@
+
+describe DataMapper::Is::Rateable do
+
+ def unload_consts(*consts)
+ consts.each do |c|
+ c = "#{c}"
+ Object.send(:remove_const, c) if Object.const_defined?(c)
+ end
+ end
+
+ # --------------------------------------------------------------------------------------------------
+ # SCENARIO
+ # --------------------------------------------------------------------------------------------------
+ before do
+ class Account
+ include DataMapper::Resource
+ property :id, Serial
+ end
+
+ class Trip
+ include DataMapper::Resource
+ property :id, Serial
+
+ is :rateable, :by => :accounts, :model => 'AccountTripQualityRating'
+ end
+
+ [Trip,Account,AccountTripQualityRating].each(&:auto_migrate!)
+ end
+
+ after do
+ unload_consts(Trip,Account,AccountTripQualityRating)
+ end
+
+ let(:rateable_model){ Trip }
+ let(:rater_model) { Account }
+ let(:rating_model) { AccountTripQualityRating }
+
+ # --------------------------------------------------------------------------------------------------
+ # RATEABLE
+ # --------------------------------------------------------------------------------------------------
+ describe 'Rateable' do
+
+ describe 'Model' do
+ subject{ rateable_model }
+
+ its(:rating_configs) { should == {
+ 'AccountTripQualityRating' => {
+ :by => {
+ :name => :account,
+ :key => :account_id,
+ :model => 'Account',
+ :type => Integer,
+ :options => {:required => true, :min => 0}
+ },
+ :with => 0..5,
+ :as => :account_quality_ratings,
+ :model => 'AccountTripQualityRating',
+ :timestamps => true
+ }
+ } }
+
+ its(:relationships) { should be_named(:account_quality_ratings) }
+ end
+
+ describe 'Instance' do
+ let(:rateable) { rateable_model.create }
+
+ subject{ rateable }
+
+ context 'when no rating exists' do
+ it{ rateable.average_rating_of(rater_model).should == nil }
+ its(:average_account_quality_rating) { should == nil }
+ end
+
+ context 'when one rating exists' do
+ before{ rateable.rate(1,rater_model.create) }
+
+ it{ rateable.average_rating_of(rater_model).should == 1 }
+ its(:average_account_quality_rating) { should == 1 }
+ end
+
+ context 'when multiple ratings exist' do
+ let(:avg) { ratings.sum.to_f/ratings.length }
+ let(:ratings) { 10.times.map{ (0..5).to_a.sample } }
+ before{ ratings.each{ |r| rateable.rate(r,rater_model.create) } }
+
+ it{ rateable.average_rating_of(rater_model).should == avg }
+ its(:average_account_quality_rating) { should == avg }
+ end
+ end
+ end
+
+ # --------------------------------------------------------------------------------------------------
+ # RATER
+ # --------------------------------------------------------------------------------------------------
+ describe 'Rater' do
+
+ describe 'Model' do
+ subject{ rater_model }
+
+ its(:relationships){ should be_named(:account_trip_quality_ratings) }
+ end
+
+ describe 'Instance' do
+ let(:rater) { rater_model.create }
+ subject{ rater }
+
+ its(:account_trip_quality_ratings) { should be_empty }
+
+ context 'when rated a rateable' do
+ let(:rateable) { rateable_model.create }
+ before{ rateable.rate(1,rater) }
+
+ its(:account_trip_quality_ratings) { should have(1).entry }
+ its(:'account_trip_quality_ratings.first.rating') { should == 1 }
+ end
+
+ context 'when rated multiple rateables' do
+ let(:rateables) { 3.times.map{ rateable_model.create } }
+ before{ rateables.each{ |r| r.rate(1,rater) } }
+
+ its(:account_trip_quality_ratings) { should have(rateables.length).entries }
+ its(:'account_trip_quality_ratings.first.rating') { should == 1 }
+ end
+ end
+ end
+
+ # --------------------------------------------------------------------------------------------------
+ # RATING
+ # --------------------------------------------------------------------------------------------------
+ describe 'Rating' do
+
+ describe 'Model' do
+ subject{ rating_model }
+
+ its(:properties) { should be_named(:trip_id) }
+ its(:properties) { should be_named(:account_id) }
+ its(:properties) { should be_named(:rating) }
+ its(:properties) { should be_named(:created_at) }
+ its(:properties) { should be_named(:updated_at) }
+
+ its(:relationships) { should be_named(:account) }
+ its(:relationships) { should be_named(:trip) }
+
+ its(:instance_methods) { should include(:rater) }
+ its(:instance_methods) { should include(:rateable) }
+ end
+
+ describe 'Instance' do
+ let(:rateable){ rateable_model.create }
+ let(:rater) { rater_model.create }
+ let(:rating) { rateable.rating_of(rater) }
+ before { rateable.rate(1,rater) }
+ subject{ rating }
+
+ its(:rating) { should == 1 }
+ its(:rateable){ should == rateable }
+ its(:rater) { should == rater }
+ end
+
+ end
+
+end
+
View
164 spec/integration/is_rateable_by_spec.rb
@@ -0,0 +1,164 @@
+
+describe DataMapper::Is::Rateable do
+
+ def unload_consts(*consts)
+ consts.each do |c|
+ c = "#{c}"
+ Object.send(:remove_const, c) if Object.const_defined?(c)
+ end
+ end
+
+ # --------------------------------------------------------------------------------------------------
+ # SCENARIO
+ # --------------------------------------------------------------------------------------------------
+ before do
+ class Account
+ include DataMapper::Resource
+ property :id, Serial
+ end
+
+ class Trip
+ include DataMapper::Resource
+ property :id, Serial
+
+ is :rateable, :by => :accounts
+ end
+
+ [Trip,Account,TripRatingByAccount].each(&:auto_migrate!)
+ end
+
+ after do
+ unload_consts(Trip,Account,TripRatingByAccount)
+ end
+
+ let(:rateable_model){ Trip }
+ let(:rater_model) { Account }
+ let(:rating_model) { TripRatingByAccount }
+
+ # --------------------------------------------------------------------------------------------------
+ # RATEABLE
+ # --------------------------------------------------------------------------------------------------
+ describe 'Rateable' do
+
+ describe 'Model' do
+ subject{ rateable_model }
+
+ its(:rating_configs) { should == {
+ 'TripRatingByAccount' => {
+ :by => {
+ :name => :account,
+ :key => :account_id,
+ :model => 'Account',
+ :type => Integer,
+ :options => {:required => true, :min => 0}
+ },
+ :with => 0..5,
+ :as => :ratings_by_accounts,
+ :model => 'TripRatingByAccount',
+ :timestamps => true
+ }
+ } }
+
+ its(:relationships) { should be_named(:ratings_by_accounts) }
+ end
+
+ describe 'Instance' do
+ let(:rateable) { rateable_model.create }
+
+ subject{ rateable }
+
+ context 'when no rating exists' do
+ it{ rateable.average_rating_of(rater_model).should == nil }
+ its(:average_rating_by_accounts) { should == nil }
+ end
+
+ context 'when one rating exists' do
+ before{ rateable.rate(1,rater_model.create) }
+
+ it{ rateable.average_rating_of(rater_model).should == 1 }
+ its(:average_rating_by_accounts) { should == 1 }
+ end
+
+ context 'when multiple ratings exist' do
+ let(:avg) { ratings.sum.to_f/ratings.length }
+ let(:ratings) { 10.times.map{ (0..5).to_a.sample } }
+ before{ ratings.each{ |r| rateable.rate(r,rater_model.create) } }
+
+ it{ rateable.average_rating_of(rater_model).should == avg }
+ its(:average_rating_by_accounts) { should == avg }
+ end
+ end
+ end
+
+ # --------------------------------------------------------------------------------------------------
+ # RATER
+ # --------------------------------------------------------------------------------------------------
+ describe 'Rater' do
+
+ describe 'Model' do
+ subject{ rater_model }
+
+ its(:relationships){ should be_named(:trip_account_ratings) }
+ end
+
+ describe 'Instance' do
+ let(:rater) { rater_model.create }
+ subject{ rater }
+
+ its(:trip_ratings) { should be_empty }
+
+ context 'when rated a rateable' do
+ let(:rateable) { rateable_model.create }
+ before{ rateable.rate(1,rater) }
+
+ its(:trip_ratings) { should have(1).entry }
+ its(:'trip_ratings.first.rating') { should == 1 }
+ end
+
+ context 'when rated multiple rateables' do
+ let(:rateables) { 3.times.map{ rateable_model.create } }
+ before{ rateables.each{ |r| r.rate(1,rater) } }
+
+ its(:trip_ratings) { should have(rateables.length).entries }
+ its(:'trip_ratings.first.rating') { should == 1 }
+ end
+ end
+ end
+
+ # --------------------------------------------------------------------------------------------------
+ # RATING
+ # --------------------------------------------------------------------------------------------------
+ describe 'Rating' do
+
+ describe 'Model' do
+ subject{ rating_model }
+
+ its(:properties) { should be_named(:trip_id) }
+ its(:properties) { should be_named(:account_id) }
+ its(:properties) { should be_named(:rating) }
+ its(:properties) { should be_named(:created_at) }
+ its(:properties) { should be_named(:updated_at) }
+
+ its(:relationships) { should be_named(:account) }
+ its(:relationships) { should be_named(:trip) }
+
+ its(:instance_methods) { should include(:rater) }
+ its(:instance_methods) { should include(:rateable) }
+ end
+
+ describe 'Instance' do
+ let(:rateable){ rateable_model.create }
+ let(:rater) { rater_model.create }
+ let(:rating) { rateable.rating_of(rater) }
+ before { rateable.rate(1,rater) }
+ subject{ rating }
+
+ its(:rating) { should == 1 }
+ its(:rateable){ should == rateable }
+ its(:rater) { should == rater }
+ end
+
+ end
+
+end
+
View
247 spec/integration/rateable_spec.rb
@@ -39,7 +39,111 @@ class Trip
[Trip,Account,User]
}
- after { unload_consts('User','Account','Trip','TripUserRating','TripAccountRating') }
+ after do
+ unload_consts('User','Account','Trip','TripUserRating','TripAccountRating')
+ end
+
+ # --------------------------------------------------------------------------------------------------
+ #
+ # SHARED CONTEXTS
+ #
+ # --------------------------------------------------------------------------------------------------
+
+ # ----------------------------------------
+ shared_context 'is :rateable, :by => :users', :rateable => :by_users do
+ before do
+ setup_rateable.call do
+ is :rateable, :by => :users
+ end
+
+ [TripUserRating].each(&:auto_migrate!)
+ end
+
+ let(:rateable_class) { Trip }
+ let(:rating_class) { TripUserRating }
+ let(:rater_class) { User }
+ end
+
+ # ----------------------------------------
+ shared_context 'is :rateable, :by => User', :rateable => :by_user_model do
+ before do
+ setup_rateable.call do
+ is :rateable, :by => User
+ end
+
+ [TripUserRating].each(&:auto_migrate!)
+ end
+
+ let(:rateable_class) { Trip }
+ let(:rating_class) { TripUserRating }
+ let(:rater_class) { User }
+ end
+
+ # ----------------------------------------
+ shared_context 'is :rateable, :by => :accounts', :rateable => :by_accounts do
+ before do
+ setup_rateable.call do
+ is :rateable, :by => :accounts
+ end
+
+ [TripAccountRating].each(&:auto_migrate!)
+ end
+
+ let(:rateable_class) { Trip }
+ let(:rating_class) { TripAccountRating }
+ let(:rater_class) { Account }
+ end
+
+ # ----------------------------------------
+ shared_context 'is :rateable, :by => Account', :rateable => :by_account_model do
+ before do
+ setup_rateable.call do
+ is :rateable, :by => Account
+ end
+
+ [TripAccountRating].each(&:auto_migrate!)
+ end
+
+ let(:rateable_class) { Trip }
+ let(:rating_class) { TripAccountRating }
+ let(:rater_class) { Account }
+ end
+
+ # ----------------------------------------
+ shared_context 'is :rateable, :by => :users, :as => :special_ratings', :rateable => :by_users_as_special_ratings do
+ before do
+ setup_rateable.call do
+ is :rateable, :by => :users, :as => :special_ratings
+ end
+
+ [TripUserRating].each(&:auto_migrate!)
+ end
+
+ let(:rateable_class) { Trip }
+ let(:rating_class) { TripUserRating }
+ let(:rater_class) { User }
+ end
+
+ # ----------------------------------------
+ shared_context 'is :rateable, :by => {:name => :author, :key => :user_id}', :rateable => :by_author_user_id do
+ before do
+ setup_rateable.call do
+ is :rateable, :by => {:name => :author, :key => :user_id}
+ end
+
+ [TripUserRating].each(&:auto_migrate!)
+ end
+
+ let(:rateable_class) { Trip }
+ let(:rating_class) { TripUserRating }
+ let(:rater_class) { User }
+ end
+
+ # --------------------------------------------------------------------------------------------------
+ #
+ # SHARED EXAMPLES
+ #
+ # --------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------
@@ -147,6 +251,12 @@ class Trip
end
end
end
+
+ # --------------------------------------------------------------------------------------------------
+ #
+ # EXAMPLES
+ #
+ # --------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------
@@ -304,17 +414,6 @@ def extra_method; true; end
# --------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------
describe 'is :rateable, :by => :users, :model => not implemented' do
-# before do
-# setup_rateable.call do
-# is :rateable, :by => :users, :model => 'AwkwardRating'
-# end
-#
-# [AwkwardRating].each(&:auto_migrate!)
-# end
-#
-# it_behaves_like :rateable_model do
-# let(:rateable_model) { Trip }
-# end
it ':model option not implemented' do
expect{ setup_rateable.call do
@@ -325,18 +424,26 @@ def extra_method; true; end
# --------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------
- describe 'rateable model' do
- before do
- setup_rateable.call do
- is :rateable, :by => :users
- end
+ describe 'Rateable Class' do
+# before do
+# setup_rateable.call do
+# is :rateable, :by => :users
+# end
+#
+# [TripUserRating].each(&:auto_migrate!)
+# end
+#
+# let(:rateable_model){ Trip }
+# let(:rater_model) { User }
+# let(:rating_model) { TripUserRating }
- [TripUserRating].each(&:auto_migrate!)
- end
+ describe '#rateable_fk' do
+ subject{ rateable_model.rateable_fk }
- let(:rateable_model){ Trip }
- let(:rater_model) { User }
- let(:rating_model) { TripUserRating }
+ context 'when ' do
+ it{ should == :trip_id }
+ end
+ end
# #rating_config_for
describe '#rating_config_for' do
@@ -406,7 +513,9 @@ def extra_method; true; end
end
- describe 'rateable' do
+ # --------------------------------------------------------------------------------------------------
+ # --------------------------------------------------------------------------------------------------
+ describe 'Rateable Instance' do
before do
setup_rateable.call do
is :rateable, :by => :users
@@ -469,9 +578,99 @@ def extra_method; true; end
it_behaves_like 'average ratings'
end
end#average_rating_of
-
end
+ # --------------------------------------------------------------------------------------------------
+ # --------------------------------------------------------------------------------------------------
+ describe 'Rater' do
+
+ describe 'Class' do
+ subject{ rater_class }
+
+ #shared_examples :relationships do
+ # its(:relationships) { should be_named(:trip_user_ratings) }
+ #end
+
+ describe 'when rateable, :by => :users' do
+ include_context 'is :rateable, :by => :users' # FIXME rspec issue
+ its(:relationships) { should be_named(:trip_user_ratings) }
+ end
+
+ describe 'when rateable, :by => User' do
+ include_context 'is :rateable, :by => User' # FIXME rspec issue
+ its(:relationships) { should be_named(:trip_user_ratings) }
+ end
+ end#Class
+
+ describe 'Instance' do
+
+ end#Instance
+ end
+
+ # --------------------------------------------------------------------------------------------------
+ # --------------------------------------------------------------------------------------------------
+ describe 'Rating' do
+ describe 'Class' do
+ subject{ rating_class }
+
+ shared_examples :default_properties do
+ subject{ rating_class.properties }
+ #it { should be_named(:user_id) }
+ it { should be_named(:trip_id) }
+ it { should be_named(:rating) }
+ end
+
+ #describe 'when rateable by :users', :rateable => :by_users do
+ describe 'when rateable by :users' do
+ include_context 'is :rateable, :by => :users' # FIXME rspec issue
+ it_behaves_like :default_properties do
+ it { should be_named(:user_id) }
+ end
+ end
+
+ describe 'when rateable by User' do
+ include_context 'is :rateable, :by => User'
+ it_behaves_like :default_properties do
+ it { should be_named(:user_id) }
+ end
+ end
+
+ describe 'when rateable by Account' do
+ include_context 'is :rateable, :by => Account'
+ it_behaves_like :default_properties do
+ it { should be_named(:account_id) }
+ end
+ end
+
+ describe 'when rateable by :accounts' do
+ include_context 'is :rateable, :by => :accounts'
+ it_behaves_like :default_properties do
+ it { should be_named(:account_id) }
+ end
+ end
+
+ describe 'when rateable by :users as :special_ratings' do
+ include_context 'is :rateable, :by => :users, :as => :special_ratings'
+ it_behaves_like :default_properties do
+ it { should be_named(:user_id) }
+ end
+ end
+
+ describe 'when is :rateable, :by => {:name => :author, :key => :user_id}' do
+ include_context 'is :rateable, :by => {:name => :author, :key => :user_id}'
+ it_behaves_like :default_properties do
+ it { should be_named(:user_id) }
+ end
+ end
+ end
+
+ describe 'Instance' do
+
+ end
+ end
+
end
+
+

0 comments on commit 2c95252

Please sign in to comment.
Something went wrong with that request. Please try again.