acts_as_nps_rateable 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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