acts_as_nps_rateable 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- NDE0YTQ5NWUwNjdkMjFmM2YyYzM3ZjdjYzU5MTk5OTQ0ODM5ODM4Mg==
5
- data.tar.gz: !binary |-
6
- Yjk5MmEyMjE1ODVmZDJmMDk3YTYxM2VmNTM3MTAxNzA5NzdkNTNlMA==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- OGI3YmVlNmNmM2I2Mzc1NDczMjJmYmYwMTUwMGRkM2E0ZjMxNTRkY2MxMGI5
10
- NmI5YmYzN2NhYmQ2OGQwZmIzY2U3ODgxNGZkMjk0MjhiYTc3YmNmODkxYTcz
11
- MTg0NDBhN2ExNzQ4ZGZjZjA3OWVkZmUxZjJkMDMyZmIyNDNkN2Y=
12
- data.tar.gz: !binary |-
13
- ZTU4YzNmY2M2ZmJjODY4NmNhYWUyYWUxNjUxOTM1OGFjMWI3OTFlOGZhYTY2
14
- OGExY2I2ZTkyZDlmNTkwMDRkMmE4MjFjOTZiNDhkMWMwZWIyMWIzMzQ3N2Y2
15
- NjljOGY3MTU5YzVjMmE5ZWE0MTU4YzBkYjkyYjU2ODUyYTM4MTE=
2
+ SHA1:
3
+ metadata.gz: 6e58be26857db542d7dd276514b5788241047d96
4
+ data.tar.gz: 1e6fc6ba2fbdfa813d845211d70c893cf4b5bdd3
5
+ SHA512:
6
+ metadata.gz: 6257642935cd4dc8173ebfa8575b6c1f3009a283b5abce8c86d5b094bbb0f9daf738af366fd88171ed5e324ea23ba925d4b953d729216a21f338beacbf3ad3ec
7
+ data.tar.gz: 1626017a39016b48797e6299bf3385dda58c400fa6e4831df7362237b3598bd73f5ddbb653442c59aec96198a173e8c9e789c9354b1ca5117afcffc6cf1119eb
@@ -1 +1 @@
1
- 1.9.3-p194
1
+ ruby-2.2.2
data/Gemfile CHANGED
@@ -2,3 +2,8 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in acts_as_nps_rateable.gemspec
4
4
  gemspec
5
+
6
+ group :development do
7
+ gem 'yard'
8
+ gem 'activerecord'
9
+ end
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
- # ActsAsNpsRateable
1
+ # acts_as_nps_rateable
2
2
 
3
- TODO: Write a gem description
3
+ acts_as_nps_rateable provides Net Promoter Score (NPS) ratings and analysis for your ActiveRecord-based models. Net
4
+ Promoter Score is a measurement of customer satisfaction; it is the ratio of the percentage of customers who would
5
+ recommend your product/service to the percentage of customers who would not recommend it.
6
+
7
+ NPS is documented in more detail at Wikipedia: http://en.wikipedia.org/wiki/Net_Promoter
4
8
 
5
9
  ## Installation
6
10
 
@@ -8,6 +12,10 @@ Add this line to your application's Gemfile:
8
12
 
9
13
  gem 'acts_as_nps_rateable'
10
14
 
15
+ If you're adding this gem to a rails 3 application, you should change the line to be:
16
+
17
+ gem 'acts_as_nps_rateable', '=0.0.4'
18
+
11
19
  And then execute:
12
20
 
13
21
  $ bundle
@@ -16,9 +24,93 @@ Or install it yourself as:
16
24
 
17
25
  $ gem install acts_as_nps_rateable
18
26
 
27
+ Once you've installed the gem, you'll need to install the migrations required by acts_as_nps_rateable into your project
28
+ and run all pending migrations:
29
+
30
+ rails generate acts_as_nps_rateable:install
31
+ rake db:migrate
32
+
33
+ Now you're ready to use acts_as_nps_rateable.
34
+
35
+ ## Upgrading
36
+
37
+ If you're upgrading from a previous version of acts_as_nps_rateable, the major change here is that v0.0.5 is now only
38
+ compatible with rails 4.2. Making it rails 3 compatible shouldn't be a problem but I've moved on and don't have any
39
+ rails 3 applications to add it to.
40
+
41
+ I welcome a pull request that adds back rails 3 compatiblity.
42
+
19
43
  ## Usage
20
44
 
21
- TODO: Write usage instructions here
45
+ ### Setting up the Models
46
+
47
+ acts_as_nps_rateable relies on the concept of rateables and raters.
48
+
49
+ A rateable is any model which can be given a rating from 0 to 10 (inclusive) and an optional review. An example of a
50
+ rateable might be a Restaurant model. Set up a rateable by adding the following line to the model you wish to be a
51
+ rateable:
52
+
53
+ acts_as_nps_rateable
54
+
55
+ e.g.
56
+
57
+ class Restaurant < ActiveRecord::Base
58
+ acts_as_nps_rateable
59
+ end
60
+
61
+ A rater is any model which can give a rateable that rating and is essentially the user to attribute that rating to. An
62
+ example of a rater will usually be a User model it's possible to have multiple raters in your system e.g. an Employee
63
+ and a Manager, both of which can rate any rateable. Set up a rater by adding the following line to the model you wish
64
+ to be a rater:
65
+
66
+ acts_as_nps_rater
67
+
68
+ e.g.
69
+
70
+ class User < ActiveRecord::Base
71
+ acts_as_nps_rater
72
+ end
73
+
74
+ ### Rating
75
+
76
+ Let's assume we have the User and Restaurant models in the system with specific instances of each in our code as follows:
77
+
78
+ snob = User.find(31337)
79
+ rateotu = Restaurant.find(42)
80
+
81
+ Our rater has the following methods available to her:
82
+
83
+ snob.rate(rateotu, 5) # Rates the restaurant a 5 on a scale from 0 to 10 inclusive
84
+ snob.rate(rateotu, 3) # Overwrites the restaurant's rating to a 3
85
+ snob.average_rating # Returns the average score of all the ratings snob has given
86
+ snob.rated?(rateotu) # Returns true if snob has rated this restaurant before. False otherwise.
87
+ snob.rating_for(rateotu) # Returns the score snob gave this restaurant before. This could be nil.
88
+
89
+ snob.review("It was OK", rateotu) # Adds a review to an existing rating by snob for this restaurant.
90
+
91
+ Our rateable has the following methods available:
92
+
93
+ rateotu.rate(9, snob) # Adds a rating for the restaurant by a given rater
94
+ rateotu.rate(10, snob) # Overwrites the rating given by snob with a better number
95
+ rateotu.average_rating # Returns the average score of all the ratings this restaurant has received
96
+ rateotu.rated_by?(snob) # Returns true if snob has rated this restaurant before. False otherwise.
97
+ rateotu.rating_by(snob) # Returns the score snob gave this restaurant before. This could be nil.
98
+
99
+ # We will use this recent_ratings variable in some examples below. It's meant to be all ratings that were recorded
100
+ # in the last month.
101
+ recent_ratings = rateotu.nps_ratings.where("created_at > ?", 1.month.ago)
102
+
103
+ rateotu.promoters # Returns a count of the number of all ratings considered promoters in the NPS sense
104
+ rateotu.promoters(recent_ratings) # Returns a count of the number of recent ratings considered promoters
105
+ rateotu.passives # Returns a count of the number of all ratings considered passives in the NPS sense
106
+ rateotu.passives(recent_ratings) # Returns a count of the number of recent ratings considered passives
107
+ rateotu.detractors # Returns a count of the number of all ratings considered detractors in the NPS sense
108
+ rateotu.detractors(recent_ratings) # Returns a count of the number of recent ratings considered detractors
109
+
110
+ rateotu.net_promoter_score # Returns the Net Promoter Score based on all ratings for this restaurant
111
+ rateotu.net_promoter_score(recent_ratings) # Returns the Net Promoter Score based on recent ratings only
112
+
113
+ rateotu.review("It was Great!", snob) # Adds a review to an existing rating by snob for this restaurant
22
114
 
23
115
  ## Contributing
24
116
 
@@ -12,8 +12,8 @@ Gem::Specification.new do |gem|
12
12
  gem.summary = %q{Net Promoter Score ratings and analysis for ActiveRecord models}
13
13
  gem.homepage = 'https://github.com/sjaveed/acts_as_nps_rateable'
14
14
 
15
- gem.add_dependency 'activerecord', '~> 3.0'
16
- gem.add_dependency 'rails', '~> 3.0'
15
+ gem.add_dependency 'activerecord', '>= 4.2.0'
16
+ gem.add_dependency 'rails', '>= 4.2.0'
17
17
 
18
18
  gem.files = `git ls-files`.split($/)
19
19
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -2,7 +2,8 @@ require File.join(File.dirname(__FILE__), "acts_as_nps_rateable/railtie")
2
2
 
3
3
  module ActsAsNpsRateable
4
4
  autoload :Hook, File.join(File.dirname(__FILE__), "acts_as_nps_rateable/hook")
5
- autoload :InstanceMethods, File.join(File.dirname(__FILE__), "acts_as_nps_rateable/instance_methods")
5
+ autoload :Rateable, File.join(File.dirname(__FILE__), "acts_as_nps_rateable/rateable")
6
+ autoload :Rater, File.join(File.dirname(__FILE__), "acts_as_nps_rateable/rater")
6
7
  end
7
8
 
8
9
  require 'nps_rating'
@@ -1,10 +1,16 @@
1
+ # @private
1
2
  # This is the hook that implements the acts_as_nps_rateable method
2
3
  # and includes all acts_as_nps_rateable instance methods as needed
3
-
4
4
  module ActsAsNpsRateable::Hook
5
5
  def acts_as_nps_rateable
6
6
  has_many :nps_ratings, :as => :nps_rateable, class_name: 'ActsAsNpsRateable::NpsRating', :dependent => :destroy
7
7
 
8
- include ActsAsNpsRateable::InstanceMethods
8
+ include ActsAsNpsRateable::Rateable
9
+ end
10
+
11
+ def acts_as_nps_rater
12
+ has_many :nps_ratings, :as => :rater, class_name: 'ActsAsNpsRateable::NpsRating', :dependent => :destroy
13
+
14
+ include ActsAsNpsRateable::Rater
9
15
  end
10
16
  end
@@ -1,4 +1,5 @@
1
1
  module ActsAsNpsRateable
2
+ # @private
2
3
  class Railtie < Rails::Railtie
3
4
  # Extend ActiveRecord::Base here and include ActsAsNpsRateable instance methods
4
5
  config.to_prepare do
@@ -0,0 +1,156 @@
1
+ ##
2
+ # acts_as_nps_rateable is a library that provides the following functionality:
3
+ # * Net Promoter Score recording and analysis functionality.
4
+ # * Storing comments in addition to a rating
5
+ # * One response per comment from the entity being reviewed
6
+ # * Upvoting or downvoting comments (as many times as someone wants)
7
+ #
8
+ # These are all the instance methods that <code>acts_as_nps_rateable</code> includes into a model
9
+ module ActsAsNpsRateable::Rateable
10
+ ##
11
+ # Rate this rateable ensuring there's at most only one rating per rater per rateable
12
+ #
13
+ # @param [Integer] score
14
+ # This is the actual rating this rateable is getting
15
+ # @param [ActsAsNpsRateable::Rater] rater
16
+ # This is the rater who is rating this particular rateable. Nothing happens if this is absent.
17
+ def rate(score, rater)
18
+ return unless rater.present?
19
+
20
+ ActiveRecord::Base.transaction do
21
+ ratings_by(rater).delete_all
22
+ nps_ratings.create(score: score.to_i, rater: rater)
23
+ end
24
+ end
25
+
26
+ ##
27
+ # Returns the average of all ratings for this rateable
28
+ #
29
+ # @return [Float] the average of all ratings for this rateable
30
+ # This uses the database average aggregation
31
+ def average_rating
32
+ nps_ratings.average(:score)
33
+ end
34
+
35
+ ##
36
+ # Checks whether a rater has already rated this rateable
37
+ #
38
+ # @param [ActsAsNpsRateable::Rater] rater
39
+ # The rater who might have rated this rateable before
40
+ #
41
+ # @return [Boolean] if a rater was specified
42
+ # @return nil if rater is nil
43
+ def rated_by?(rater)
44
+ return unless rater.present?
45
+
46
+ ratings_by(rater).any?
47
+ end
48
+
49
+ ##
50
+ # Get the rating given to this rateable by a rater
51
+ #
52
+ # @param [ActsAsNpsRateable::Rater] rater
53
+ # The rater who might have rated this rateable before.
54
+ #
55
+ # @return [Boolean] if a rater was specified
56
+ # @return nil if rater is nil
57
+ def rating_by(rater)
58
+ return unless rater.present?
59
+
60
+ ratings_by(rater).first
61
+ end
62
+
63
+ ##
64
+ # Returns the number of ratings which are classified as promoters. Promoters are any ratings where the score is
65
+ # greater than 8
66
+ #
67
+ # @param [ActiveRecord::Relation<ActsAsNpsRateable::NpsRating>] ratings
68
+ # These are optional ratings - typically a subset of the whole - which you want to calculate the promoter count from
69
+ # @return [Integer] the number of promoter ratings among all the ratings for this rateable if ratings is nil
70
+ # @return [Integer] the number of promoter ratings among the given ratings if ratings is not nil
71
+ def promoters ratings = nil
72
+ if ratings.nil?
73
+ nps_ratings.promoters.size
74
+ else
75
+ ratings.promoters.size
76
+ end
77
+ end
78
+
79
+ ##
80
+ # Returns the number of ratings which are classified as passives. Passives are any ratings where the score is
81
+ # either 7 or 8
82
+ #
83
+ # @param [ActiveRecord::Relation<ActsAsNpsRateable::NpsRating>] ratings
84
+ # These are optional ratings - typically a subset of the whole - which you want to calculate the passive count from
85
+ # @return [Integer] the number of passive ratings among all the ratings for this rateable if ratings is nil
86
+ # @return [Integer] the number of passive ratings among the given ratings if ratings is not nil
87
+ def passives ratings = nil
88
+ if ratings.nil?
89
+ nps_ratings.passives.size
90
+ else
91
+ ratings.passives.size
92
+ end
93
+ end
94
+
95
+ ##
96
+ # Returns the number of ratings which are classified as detractors. Passives are any ratings where the score is
97
+ # less than 7
98
+ #
99
+ # @param [ActiveRecord::Relation<ActsAsNpsRateable::NpsRating>] ratings
100
+ # These are optional ratings - typically a subset of the whole - which you want to calculate the detractor count from
101
+ # @return [Integer] the number of detractor ratings among all the ratings for this rateable if ratings is nil
102
+ # @return [Integer] the number of detractor ratings among the given ratings if ratings is not nil
103
+ def detractors ratings = nil
104
+ if ratings.nil?
105
+ nps_ratings.detractors.size
106
+ else
107
+ ratings.detractors.size
108
+ end
109
+ end
110
+
111
+ ##
112
+ # Calculates the Net Promoter Score as defined at http://www.checkmarket.com/2011/06/net-promoter-score/
113
+ #
114
+ # @param [ActiveRecord::Relation<ActsAsNpsRateable::NpsRating>] ratings
115
+ # These are optional ratings - typically a subset of the whole - which you want to calculate the Net Promoter Score
116
+ # from
117
+ # @return [Integer] the Net Promoter Score based on all the ratings for this rateable if ratings is nil
118
+ # @return [Integer] the Net Promoter Score based on the given ratings if ratings is not nil
119
+ def net_promoter_score ratings = nil
120
+ total_ratings = ratings.nil? ? nps_ratings.size : ratings.size
121
+ return 0 if total_ratings.zero?
122
+
123
+ if ratings.nil?
124
+ (promoters - detractors) * 100 / total_ratings
125
+ else
126
+ (promoters(ratings) - detractors(ratings)) * 100 / total_ratings
127
+ end
128
+ end
129
+
130
+ ##
131
+ # Add an optional review to this rateable only if this rater has previously rated this rateable.
132
+ #
133
+ # @param [String] comment
134
+ # The actual comment
135
+ # @param [ActsAsNpsRateable::Rater] rater
136
+ # This is the rater who is rating this particular rateable. Nothing happens if this is absent.
137
+ def review(comment, rater)
138
+ return unless rater.present?
139
+
140
+ ratings_by(rater).update_all(comments: comment)
141
+ end
142
+
143
+ private
144
+
145
+ ##
146
+ # A convenience method to DRY up the logic of fetching all ratings for this rateable by a given rater
147
+ #
148
+ # @param [ActsAsNpsRateable::Rater] rater
149
+ # This is the rater for whom you want ratings retrieved
150
+ # @return [ActiveRecord::Relation<ActsAsNpsRateable::NpsRating>]
151
+ # the list of ratings by the rater for this rateable
152
+ def ratings_by(rater)
153
+ nps_ratings.where(rater: rater)
154
+ end
155
+
156
+ end
@@ -0,0 +1,80 @@
1
+ # These are all the instance methods that acts_as_nps_rater includes into an ActiveModel
2
+
3
+ module ActsAsNpsRateable::Rater
4
+ ##
5
+ # As this rater, rate the specified rateable ensuring there's at most only one rating per rater per rateable
6
+ #
7
+ # @param [ActsAsNpsRateable::Rateable] rateable
8
+ # This is the rateable which this rater is rating. Nothing happens if this is absent.
9
+ # @param [Integer] score
10
+ # This is the actual rating this rater is giving
11
+ def rate(rateable, score)
12
+ ActiveRecord::Base.transaction do
13
+ ratings_for(rateable).delete_all
14
+ nps_ratings.create(nps_rateable: rateable, score: score.to_i)
15
+ end
16
+ end
17
+
18
+ ##
19
+ # Returns the average of all ratings this rater has ever given
20
+ #
21
+ # @return [Float] the average of all ratings this rater has given
22
+ # This uses the database average aggregation
23
+ def average_rating
24
+ nps_ratings.average(:score)
25
+ end
26
+
27
+ ##
28
+ # Checks whether this rater has already rated a rateable
29
+ #
30
+ # @param [ActsAsNpsRateable::Rateable] rateable
31
+ # The rateable that this rater might have rated before
32
+ #
33
+ # @return [Boolean] if a rateable was specified
34
+ # @return nil if rater is nil
35
+ def rated?(rateable)
36
+ return unless rateable.present?
37
+
38
+ ratings_for(rateable).any?
39
+ end
40
+
41
+ ##
42
+ # Get the rating this rater has given to a rateable
43
+ #
44
+ # @param [ActsAsNpsRateable::Rateable] rateable
45
+ # The rateable that this rater might have rated before.
46
+ #
47
+ # @return [Boolean] if a rateable was specified
48
+ # @return nil if rater is nil
49
+ def rating_for(rateable)
50
+ return unless rateable.present?
51
+
52
+ ratings_for(rateable).first
53
+ end
54
+
55
+ ##
56
+ # Add an optional review by this rater to a rateable only if this rater has previously rated that rateable
57
+ #
58
+ # @param [String] comment
59
+ # The actual comment
60
+ # @param [ActsAsNpsRateable::Rateable] rateable
61
+ # This is the rateable for which this rater is adding a review. Nothing happens if this is absent.
62
+ def review(comment, rateable)
63
+ return unless rateable.present?
64
+
65
+ ratings_for(rateable).update_all(comments: comment)
66
+ end
67
+
68
+ private
69
+
70
+ ##
71
+ # A convenience method to DRY up the logic of fetching all ratings by this rater for a given rateable
72
+ #
73
+ # @param [ActsAsNpsRateable::Rateable] rateable
74
+ # This is the rateable for which you want ratings retrieved
75
+ # @return [ActiveRecord::Relation<ActsAsNpsRateable::NpsRating>]
76
+ # the list of ratings for the rateable by this rater
77
+ def ratings_for(rateable)
78
+ nps_ratings.where(nps_rateable: rateable)
79
+ end
80
+ end
@@ -1,3 +1,3 @@
1
1
  module ActsAsNpsRateable
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -1,6 +1,7 @@
1
1
  require 'rails/generators/migration'
2
2
 
3
3
  module ActsAsNpsRateable
4
+ # @private
4
5
  class InstallGenerator < Rails::Generators::Base
5
6
  include Rails::Generators::Migration
6
7
 
@@ -10,13 +11,27 @@ module ActsAsNpsRateable
10
11
  Time.now.utc.strftime("%Y%m%d%H%M%S")
11
12
  end
12
13
 
13
- def create_migration
14
- migration_template '01-migration.rb', 'db/migrate/acts_as_nps_rateable_migration'
15
- migration_template '02-migration.rb', 'db/migrate/acts_as_nps_rateable_migration_upgrade_0_0_2'
14
+ def create_nps_rateable_migrations
15
+ migration_mapping = {
16
+ '01-migration' => 'acts_as_nps_rateable_migration',
17
+ '02-migration' => 'acts_as_nps_rateable_migration_upgrade_0_0_2',
18
+ '03-migration' => 'acts_as_nps_rateable_migration_upgrade_0_0_5',
19
+ '04-migration' => 'acts_as_nps_rateable_migration_usefulness_upgrade'
20
+ }
21
+
22
+ migration_mapping.keys.sort.each do |src|
23
+ tgt = migration_mapping[src]
24
+
25
+ if self.class.migration_exists? 'db/migrate', tgt
26
+ puts "Not over-writing existing migration: #{tgt}"
27
+ else
28
+ migration_template "#{src}.rb", "db/migrate/#{tgt}.rb"
29
+ sleep 1
30
+ end
31
+ end
16
32
  end
17
33
 
18
34
  def install_models
19
- #template 'nps_rating.rb', 'app/models/nps_rating.rb'
20
35
  end
21
36
  end
22
37
  end
@@ -1,3 +1,4 @@
1
+ # @private
1
2
  class ActsAsNpsRateableMigration < ActiveRecord::Migration
2
3
  def change
3
4
  create_table :nps_ratings do |t|
@@ -1,4 +1,5 @@
1
- class ActsAsNpsRateableMigration < ActiveRecord::Migration
1
+ # @private
2
+ class ActsAsNpsRateableMigrationUpgrade002 < ActiveRecord::Migration
2
3
  def change
3
4
  add_column :nps_ratings, :comments, :text
4
5
  end
@@ -0,0 +1,11 @@
1
+ # @private
2
+ class ActsAsNpsRateableMigrationUpgrade005 < ActiveRecord::Migration
3
+ def change
4
+ remove_index :nps_ratings, column: [:nps_rateable_type, :nps_rateable_id, :user_id], unique: true, name: "acts_as_nps_rateable_unique_index"
5
+
6
+ rename_column :nps_ratings, :user_id, :rater_id
7
+ add_column :nps_ratings, :rater_type, :string, default: 'User'
8
+
9
+ add_index :nps_ratings, [:nps_rateable_type, :nps_rateable_id, :rater_type, :rater_id], unique: true, name: "acts_as_nps_rateable_unique_index"
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # @private
2
+ class ActsAsNpsRateableMigrationUsefulnessUpgrade < ActiveRecord::Migration
3
+ def change
4
+ add_column :nps_ratings, :up_votes, :integer, default: 0, null: false
5
+ add_column :nps_ratings, :down_votes, :integer, default: 0, null: false
6
+ add_column :nps_ratings, :net_votes, :integer, default: 0, null: false
7
+
8
+ add_column :nps_ratings, :response, :text
9
+ add_column :nps_ratings, :responded_at, :timestamp
10
+ end
11
+ end
@@ -1,30 +1,47 @@
1
+ ##
2
+ # acts_as_nps_rateable is a library that provides the following functionality:
3
+ # * Net Promoter Score recording and analysis functionality.
4
+ # * Storing comments in addition to a rating
5
+ # * One response per comment from the entity being reviewed
6
+ # * Upvoting or downvoting comments (as many times as someone wants)
1
7
  module ActsAsNpsRateable
8
+ ##
9
+ # This is the actual model that stores a rating, a review (if any), its response (if any) and a count of upvotes and
10
+ # downvotes
2
11
  class NpsRating < ActiveRecord::Base
3
12
  self.table_name = 'nps_ratings'
4
13
 
5
14
  belongs_to :nps_rateable, polymorphic: true
6
- belongs_to :user
15
+ belongs_to :rater, polymorphic: true
7
16
 
8
17
  validates_presence_of :score
9
18
  validates_numericality_of :score, only_integer: true
10
19
  validates_inclusion_of :score, in: (0..10).to_a, message: 'must be between 0 and 10'
11
20
 
12
- validates_presence_of :nps_rateable_id
13
- validates_presence_of :nps_rateable_type
21
+ validates_presence_of :nps_rateable
22
+ validates_presence_of :rater
23
+ validates_uniqueness_of :rater_id, scope: [:rater_type, :nps_rateable_type, :nps_rateable_id], message: 'has already rated'
14
24
 
15
- validates_presence_of :user_id
16
- validates_uniqueness_of :user_id, scope: [:nps_rateable_type, :nps_rateable_id], message: 'has already rated'
25
+ scope :promoters, lambda { where(score: [9, 10]) }
26
+ scope :passives, lambda { where(score: [7, 8]) }
27
+ scope :detractors, lambda { where(NpsRating.arel_table[:score].lteq(6)) }
17
28
 
18
- before_save :remove_blank_comments
19
-
20
- scope :promoters, where(score: [9, 10])
21
- scope :passives, where(score: [7, 8])
22
- scope :detractors, where('score <= 6')
23
- scope :with_comments, where('comments IS NOT NULL')
29
+ scope :with_comments, lambda { where.not(comments: nil) }
24
30
 
31
+ ##
32
+ # Returns the Net Promoter Score for the given collection of ratings.
33
+ #
34
+ # @param [ActiveRecord::Relation<NpsRating>] relevant_ratings
35
+ # This is essentially the result of an ActiveRecord query for nps_ratings
36
+ #
37
+ # @example
38
+ # NpsRating.calculate_for(fancy_product.nps_ratings)
39
+ #
40
+ # @return [Float] The calculated Net Promoter Score.
41
+ # This is 0 if <code>relevant_ratings.size</code> is 0
25
42
  def self.calculate_for relevant_ratings
26
43
  total_ratings = relevant_ratings.size
27
- return 0 if total_ratings == 0
44
+ return 0 if total_ratings.zero?
28
45
 
29
46
  promoters = relevant_ratings.promoters.size
30
47
  detractors = relevant_ratings.detractors.size
@@ -32,10 +49,33 @@ module ActsAsNpsRateable
32
49
  ((promoters - detractors) * 100.0 / total_ratings).round
33
50
  end
34
51
 
35
- private
52
+ ##
53
+ # @!attribute [w] comments
54
+ # Ensures that any comments that are recorded are non-blank
55
+ def comments= comments
56
+ write_attribute(:comments, comments.present? ? comments : nil)
57
+ end
58
+
59
+ ##
60
+ # @!attribute [w] response
61
+ # Ensures that when a non-blank response is recorded, the responded_at time is also updated to the current time
62
+ def response= response
63
+ if response.present?
64
+ write_attribute(:response, response)
65
+ write_attribute(:responded_at, Time.now)
66
+ end
67
+ end
68
+
69
+ ##
70
+ # Upvotes a rating atomically. Duplicate votes are possible.
71
+ def upvote!
72
+ ActsAsNpsRateable::NpsRating.update_counters(id, up_votes: 1, net_votes: 1)
73
+ end
36
74
 
37
- def remove_blank_comments
38
- write_attribute(:comments, nil) if comments.blank?
75
+ ##
76
+ # Downvotes a rating atomically. Duplicate votes are possible.
77
+ def downvote!
78
+ ActsAsNpsRateable::NpsRating.update_counters(id, down_votes: 1, net_votes: -1)
39
79
  end
40
80
  end
41
81
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_nps_rateable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shahbaz Javeed
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-08 00:00:00.000000000 Z
11
+ date: 2015-07-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '3.0'
19
+ version: 4.2.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '3.0'
26
+ version: 4.2.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rails
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '3.0'
33
+ version: 4.2.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '3.0'
40
+ version: 4.2.0
41
41
  description: Rails gem that provides Net Promoter Score (NPS) ratings and analysis
42
42
  for ActiveRecord models. It can be used as a regular 0 to 10 rating scale and you
43
43
  can just ignore the NPS analysis methods.
@@ -47,9 +47,9 @@ executables: []
47
47
  extensions: []
48
48
  extra_rdoc_files: []
49
49
  files:
50
- - .gitignore
51
- - .ruby-gemset
52
- - .ruby-version
50
+ - ".gitignore"
51
+ - ".ruby-gemset"
52
+ - ".ruby-version"
53
53
  - Gemfile
54
54
  - LICENSE.txt
55
55
  - README.md
@@ -57,12 +57,15 @@ files:
57
57
  - acts_as_nps_rateable.gemspec
58
58
  - lib/acts_as_nps_rateable.rb
59
59
  - lib/acts_as_nps_rateable/hook.rb
60
- - lib/acts_as_nps_rateable/instance_methods.rb
61
60
  - lib/acts_as_nps_rateable/railtie.rb
61
+ - lib/acts_as_nps_rateable/rateable.rb
62
+ - lib/acts_as_nps_rateable/rater.rb
62
63
  - lib/acts_as_nps_rateable/version.rb
63
64
  - lib/generators/acts_as_nps_rateable/install_generator.rb
64
65
  - lib/generators/acts_as_nps_rateable/templates/01-migration.rb
65
66
  - lib/generators/acts_as_nps_rateable/templates/02-migration.rb
67
+ - lib/generators/acts_as_nps_rateable/templates/03-migration.rb
68
+ - lib/generators/acts_as_nps_rateable/templates/04-migration.rb
66
69
  - lib/nps_rating.rb
67
70
  homepage: https://github.com/sjaveed/acts_as_nps_rateable
68
71
  licenses: []
@@ -73,18 +76,19 @@ require_paths:
73
76
  - lib
74
77
  required_ruby_version: !ruby/object:Gem::Requirement
75
78
  requirements:
76
- - - ! '>='
79
+ - - ">="
77
80
  - !ruby/object:Gem::Version
78
81
  version: '0'
79
82
  required_rubygems_version: !ruby/object:Gem::Requirement
80
83
  requirements:
81
- - - ! '>='
84
+ - - ">="
82
85
  - !ruby/object:Gem::Version
83
86
  version: '0'
84
87
  requirements: []
85
88
  rubyforge_project:
86
- rubygems_version: 2.0.6
89
+ rubygems_version: 2.4.6
87
90
  signing_key:
88
91
  specification_version: 4
89
92
  summary: Net Promoter Score ratings and analysis for ActiveRecord models
90
93
  test_files: []
94
+ has_rdoc:
@@ -1,63 +0,0 @@
1
- # These are all the instance methods that acts_as_nps_rateable includes into an ActiveModel
2
-
3
- module ActsAsNpsRateable::InstanceMethods
4
- # rate this rateable ensuring there's at most only one rating per user per rateable
5
- def rate(score, user)
6
- ActiveRecord::Base.transaction do
7
- nps_ratings.where(user_id: user.id).delete_all if user.present?
8
- nps_ratings.create(score: score.to_i, user_id: user.id)
9
- end
10
- end
11
-
12
- # the average of all ratings for this rateable
13
- def average_rating
14
- nps_ratings.average(:score)
15
- end
16
-
17
- # a check to see if the specified user has already rated this rateable
18
- def rated_by?(user)
19
- return unless user.present?
20
-
21
- nps_ratings.where(user_id: user.id).size > 0
22
- end
23
-
24
- #
25
- # Net Promoter Score Calculations: http://www.checkmarket.com/2011/06/net-promoter-score/
26
- #
27
-
28
- def promoters ratings = nil
29
- if ratings.nil?
30
- nps_ratings.promoters.size
31
- else
32
- ratings.promoters.size
33
- end
34
- end
35
-
36
- def passives ratings = nil
37
- if ratings.nil?
38
- nps_ratings.passives.size
39
- else
40
- ratings.passives.size
41
- end
42
- end
43
-
44
- def detractors ratings = nil
45
- if ratings.nil?
46
- nps_ratings.detractors.size
47
- else
48
- ratings.detractors.size
49
- end
50
- end
51
-
52
- def net_promoter_score ratings = nil
53
- total_ratings = ratings.nil? ? nps_ratings.size : ratings.size
54
- return 0 if total_ratings == 0
55
-
56
- if ratings.nil?
57
- (promoters - detractors) * 100 / total_ratings
58
- else
59
- (promoters(ratings) - detractors(ratings)) * 100 / total_ratings
60
- end
61
- end
62
-
63
- end