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 +6 -14
- data/.ruby-version +1 -1
- data/Gemfile +5 -0
- data/README.md +95 -3
- data/acts_as_nps_rateable.gemspec +2 -2
- data/lib/acts_as_nps_rateable.rb +2 -1
- data/lib/acts_as_nps_rateable/hook.rb +8 -2
- data/lib/acts_as_nps_rateable/railtie.rb +1 -0
- data/lib/acts_as_nps_rateable/rateable.rb +156 -0
- data/lib/acts_as_nps_rateable/rater.rb +80 -0
- data/lib/acts_as_nps_rateable/version.rb +1 -1
- data/lib/generators/acts_as_nps_rateable/install_generator.rb +19 -4
- data/lib/generators/acts_as_nps_rateable/templates/01-migration.rb +1 -0
- data/lib/generators/acts_as_nps_rateable/templates/02-migration.rb +2 -1
- data/lib/generators/acts_as_nps_rateable/templates/03-migration.rb +11 -0
- data/lib/generators/acts_as_nps_rateable/templates/04-migration.rb +11 -0
- data/lib/nps_rating.rb +55 -15
- metadata +21 -17
- data/lib/acts_as_nps_rateable/instance_methods.rb +0 -63
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
ruby-2.2.2
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
-
#
|
1
|
+
# acts_as_nps_rateable
|
2
2
|
|
3
|
-
|
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
|
-
|
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', '
|
16
|
-
gem.add_dependency 'rails', '
|
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) }
|
data/lib/acts_as_nps_rateable.rb
CHANGED
@@ -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 :
|
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::
|
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
|
@@ -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,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
|
14
|
-
|
15
|
-
|
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
|
@@ -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
|
data/lib/nps_rating.rb
CHANGED
@@ -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 :
|
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 :
|
13
|
-
validates_presence_of :
|
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
|
-
|
16
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
38
|
-
|
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
|
+
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:
|
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:
|
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:
|
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:
|
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:
|
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.
|
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
|