voteable_mongoid 0.7.0 → 0.7.1

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.
data/CHANGELOG.rdoc CHANGED
@@ -1,3 +1,8 @@
1
+ == 0.7.1
2
+ * Refactor & cleanup source code
3
+ * Better doc
4
+ * Add votee#voted_by?(voter or voter_id)
5
+
1
6
  == 0.7.0
2
7
  * Use readable field names (up, down, up_count, down_count, count, point) instead of very short field names (u, d, uc, dc, c, p)
3
8
 
data/README.rdoc CHANGED
@@ -60,12 +60,38 @@ user.rb
60
60
 
61
61
  @user.vote(@post, :up)
62
62
  # is equivalent to
63
- @post.vote(:voter_id => @user.id, :value => :up)
63
+ @user.vote(:votee => @post, :value => :up)
64
+ @post.vote(:voter => @user, :value => :up)
64
65
 
65
- # this will affect @post.votes_count and @post.votes_point as well
66
- @user.vote(@comment, :down)
66
+ # In case you don't need to init voter and / or votee objects you can
67
+ @user.vote(:votee_type => 'Post', :votee_id => post_id, :value => :down)
68
+ @post.vote(:voter_id => user_id, :value => :up)
69
+ Post.vote(:voter_id => user_id, :votee_id => post_id, :value => :up)
67
70
 
68
- === Getting votes count and points
71
+ === Undo a vote
72
+
73
+ @user.unvote(@comment)
74
+
75
+ === If have full information you do not need to init voter and votee object
76
+
77
+ Post.vote(:voter_id => user_id, :votee_id => post_id, :value => :up, :revote => true)
78
+ Post.vote(:voter_id => user_id, :votee_id => post_id, :value => :up, :unvote => true)
79
+
80
+ === Getting vote_value
81
+
82
+ @user.vote_value(@post)
83
+ @user.vote_value(:class_type => 'Post', :votee_id => post_id)
84
+ @post.vote_value(@user)
85
+ @post.vote_value(user_id)
86
+
87
+ === Check if voted?
88
+
89
+ @user.voted?(@post)
90
+ @user.voted?(:class_type => 'Post', :votee_id => post_id)
91
+ @post.voted_by?(@user)
92
+ @post.voted_by?(user_id)
93
+
94
+ === Getting votes counts and points
69
95
 
70
96
  puts @post.votes_point
71
97
  puts @post.votes_count
@@ -78,29 +104,25 @@ user.rb
78
104
  Post.up_voted_by(@user)
79
105
  Post.down_voted_by(@user)
80
106
 
81
- === Undo a vote
82
-
83
- @user.unvote(@comment)
84
-
85
107
  == Utilities
86
108
 
87
109
  === Re-generate counters and vote points in case you change :up / :down vote points
88
110
  Rails
89
111
  rake db:mongoid:voteable:remake_stats
90
112
  Ruby
91
- Mongoid::Voteable::Stats.remake
113
+ Mongoid::Voteable::Tasks.remake_stats
92
114
 
93
115
  === Set counters and point to 0 for uninitialized voteable objects in order sort and query
94
116
  Rails
95
117
  rake db:mongoid:voteable:init_stats
96
118
  Ruby
97
- Mongoid::Voteable::Stats.init
119
+ Mongoid::Voteable::Tasks::init_stats
98
120
 
99
121
  === Migrate from version < 0.7.0
100
122
  Rails
101
123
  rake db:mongoid:voteable:migrate_old_votes
102
124
  Ruby
103
- Mongoid::Voteable.migrate_old_votes
125
+ Mongoid::Voteable::Tasks.migrate_old_votes
104
126
 
105
127
  == Credits
106
128
 
data/TODO CHANGED
@@ -1,2 +1,2 @@
1
1
  * Option hash validations
2
- * Refactor Voteable#vote function
2
+
@@ -1,10 +1,9 @@
1
1
  require 'mongoid'
2
- require 'voteable_mongoid/voteable/stats'
3
- require 'voteable_mongoid/voteable/votes'
4
2
  require 'voteable_mongoid/voteable'
5
3
  require 'voteable_mongoid/voter'
6
4
 
7
- # add railtie
5
+ require 'voteable_mongoid/voteable/tasks'
6
+ # Add railtie
8
7
  if defined?(Rails)
9
8
  require 'voteable_mongoid/railtie'
10
9
  end
@@ -3,17 +3,17 @@ namespace :db do
3
3
  namespace :voteable do
4
4
  desc 'Update up_votes_count, down_votes_count, votes_count and votes_point'
5
5
  task :remake_stats => :environment do
6
- Mongoid::Voteable::Stats.remake(:log)
6
+ Mongoid::Voteable::Tasks.remake_stats(:log)
7
7
  end
8
8
 
9
9
  desc 'Set counters and point to 0 for uninitizized voteable objects'
10
10
  task :init_stats => :environment do
11
- Mongoid::Voteable::Stats.init(:log)
11
+ Mongoid::Voteable::Tasks.init_stats(:log)
12
12
  end
13
13
 
14
- desc 'Migrate vote data created by version < 0.6.0 to new vote data storage'
14
+ desc 'Migrate vote data created by version < 0.7.0 to new vote data storage'
15
15
  task :migrate_old_votes => :environment do
16
- Mongoid::Voteable.migrate_old_votes(:log)
16
+ Mongoid::Voteable::Tasks.migrate_old_votes(:log)
17
17
  end
18
18
  end
19
19
  end
@@ -1,3 +1,3 @@
1
1
  module VoteableMongoid
2
- VERSION = '0.7.0'
2
+ VERSION = '0.7.1'
3
3
  end
@@ -1,236 +1,70 @@
1
+ require 'voteable_mongoid/voteable/votes'
2
+ require 'voteable_mongoid/voteable/voting'
3
+
1
4
  module Mongoid
2
5
  module Voteable
3
6
  extend ActiveSupport::Concern
4
7
 
5
- # How many points should be assigned for each up or down vote.
6
- # This hash should manipulated using voteable method
7
- VOTEABLE = {}
8
-
9
8
  included do
10
- include Mongoid::Document
11
- include Mongoid::Voteable::Stats
12
- field :votes, :type => Mongoid::Voteable::Votes
9
+ include Document
10
+ include Voting
11
+
12
+ field :votes, :type => Votes
13
+
14
+ before_create do
15
+ # Init votes so that counters and point have numeric values (0)
16
+ self.votes = Votes::DEFAULT_ATTRIBUTES
17
+ end
13
18
 
14
19
  scope :voted_by, lambda { |voter|
15
- voter_id = voter.is_a?(BSON::ObjectId) ? voter : voter._id
20
+ voter_id = voter.is_a?(BSON::ObjectId) ? voter : voter.id
16
21
  any_of({ 'votes.up' => voter_id }, { 'votes.down' => voter_id })
17
22
  }
18
23
 
19
24
  scope :up_voted_by, lambda { |voter|
20
- voter_id = voter.is_a?(BSON::ObjectId) ? voter : voter._id
25
+ voter_id = voter.is_a?(BSON::ObjectId) ? voter : voter.id
21
26
  where( 'votes.up' => voter_id )
22
27
  }
23
28
 
24
29
  scope :down_voted_by, lambda { |voter|
25
- voter_id = voter.is_a?(BSON::ObjectId) ? voter : voter._id
30
+ voter_id = voter.is_a?(BSON::ObjectId) ? voter : voter.id
26
31
  where( 'votes.down' => voter_id )
27
32
  }
28
-
29
- before_create do
30
- # Init votes so that counters and point have numeric values (0)
31
- self.votes = VOTES_DEFAULT_ATTRIBUTES
32
- end
33
-
33
+ end # include
34
+
35
+ # How many points should be assigned for each up or down vote and other options
36
+ # This hash should manipulated using voteable method
37
+ VOTEABLE = {}
38
+
39
+ module ClassMethods
34
40
  # Set vote point for each up (down) vote on an object of this class
35
41
  #
36
42
  # @param [Hash] options a hash containings:
37
43
  #
38
44
  # voteable self, :up => +1, :down => -3
39
45
  # voteable Post, :up => +2, :down => -1, :update_counters => false # skip counter update
40
- def self.voteable(klass = self, options = nil)
46
+ def voteable(klass = self, options = nil)
41
47
  VOTEABLE[self.name] ||= {}
42
48
  VOTEABLE[self.name][klass.name] ||= options
43
49
  end
44
-
45
- # We usually need to show current_user his voting value on voteable object
46
- # voting value can be nil (not voted yet), :up or :down
47
- # from voting value, we can decide it should be new vote or revote with :up or :down
48
- # In this case, validation can be skip to maximize performance
49
-
50
- # Make a vote on an object of this class
51
- #
52
- # @param [Hash] options a hash containings:
53
- # - :votee_id: the votee document id
54
- # - :voter_id: the voter document id
55
- # - :value: :up or :down
56
- # - :revote: change from vote up to vote down
57
- # - :unvote: unvote the vote value (:up or :down)
58
- def self.vote(options)
59
- options.symbolize_keys!
60
- value = options[:value].to_sym
61
-
62
- votee_id = options[:votee_id]
63
- voter_id = options[:voter_id]
64
-
65
- votee_id = BSON::ObjectId(votee_id) if votee_id.is_a?(String)
66
- voter_id = BSON::ObjectId(voter_id) if voter_id.is_a?(String)
67
-
68
- klass = options[:class]
69
- klass ||= VOTEABLE.keys.include?(name) ? name : collection.name.classify
70
- voteable = VOTEABLE[klass][klass]
71
-
72
- if options[:revote]
73
- if value == :up
74
- positive_voter_ids = 'votes.up'
75
- negative_voter_ids = 'votes.down'
76
- positive_votes_count = 'votes.up_count'
77
- negative_votes_count = 'votes.down_count'
78
- point_delta = voteable[:up] - voteable[:down]
79
- else
80
- positive_voter_ids = 'votes.down'
81
- negative_voter_ids = 'votes.up'
82
- positive_votes_count = 'votes.down_count'
83
- negative_votes_count = 'votes.up_count'
84
- point_delta = -voteable[:up] + voteable[:down]
85
- end
86
-
87
- update_result = collection.update({
88
- # Validate voter_id did a vote with value for votee_id
89
- :_id => votee_id,
90
- positive_voter_ids => { '$ne' => voter_id },
91
- negative_voter_ids => voter_id
92
- }, {
93
- # then update
94
- '$pull' => { negative_voter_ids => voter_id },
95
- '$push' => { positive_voter_ids => voter_id },
96
- '$inc' => {
97
- positive_votes_count => +1,
98
- negative_votes_count => -1,
99
- 'votes.point' => point_delta
100
- }
101
- }, {
102
- :safe => true
103
- })
104
-
105
- elsif options[:unvote]
106
- if value == :up
107
- positive_voter_ids = 'votes.up'
108
- negative_voter_ids = 'votes.down'
109
- positive_votes_count = 'votes.up_count'
110
- else
111
- positive_voter_ids = 'votes.down'
112
- negative_voter_ids = 'votes.up'
113
- positive_votes_count = 'votes.down_count'
114
- end
115
-
116
- # Check if voter_id did a vote with value for votee_id
117
- update_result = collection.update({
118
- # Validate voter_id did a vote with value for votee_id
119
- :_id => votee_id,
120
- negative_voter_ids => { '$ne' => voter_id },
121
- positive_voter_ids => voter_id
122
- }, {
123
- # then update
124
- '$pull' => { positive_voter_ids => voter_id },
125
- '$inc' => {
126
- positive_votes_count => -1,
127
- 'votes.count' => -1,
128
- 'votes.point' => -voteable[value]
129
- }
130
- }, {
131
- :safe => true
132
- })
133
-
134
- else # new vote
135
- if value.to_sym == :up
136
- positive_voter_ids = 'votes.up'
137
- positive_votes_count = 'votes.up_count'
138
- else
139
- positive_voter_ids = 'votes.down'
140
- positive_votes_count = 'votes.down_count'
141
- end
142
-
143
- update_result = collection.update({
144
- # Validate voter_id did not vote for votee_id yet
145
- :_id => votee_id,
146
- 'votes.up' => { '$ne' => voter_id },
147
- 'votes.down' => { '$ne' => voter_id }
148
- }, {
149
- # then update
150
- '$push' => { positive_voter_ids => voter_id },
151
- '$inc' => {
152
- 'votes.count' => +1,
153
- positive_votes_count => +1,
154
- 'votes.point' => voteable[value] }
155
- }, {
156
- :safe => true
157
- })
158
- end
159
-
160
- # Only update parent class if votee is updated successfully
161
- successed = ( update_result['err'] == nil and
162
- update_result['updatedExisting'] == true and
163
- update_result['n'] == 1 )
164
-
165
- if successed
166
- VOTEABLE[klass].each do |class_name, voteable|
167
- # For other class in VOTEABLE options, if is parent of current class
168
- next unless relation_metadata = relations[class_name.underscore]
169
- next unless votee ||= options[:votee] || find(options[:votee_id])
170
- # If can find current votee foreign_key value for that class
171
- next unless foreign_key_value = votee.read_attribute(relation_metadata.foreign_key)
172
-
173
- inc_options = {}
174
-
175
- if options[:revote]
176
- if value == :up
177
- inc_options['votes.point'] = voteable[:up] - voteable[:down]
178
- unless voteable[:update_counters] == false
179
- inc_options['votes.up_count'] = +1
180
- inc_options['votes.down_count'] = -1
181
- end
182
- else
183
- inc_options['votes.point'] = -voteable[:up] + voteable[:down]
184
- unless voteable[:update_counters] == false
185
- inc_options['votes.up_count'] = -1
186
- inc_options['votes.down_count'] = +1
187
- end
188
- end
189
- elsif options[:unvote]
190
- inc_options['votes.point'] = -voteable[value]
191
- unless voteable[:update_counters] == false
192
- inc_options['votes.count'] = -1
193
- if value == :up
194
- inc_options['votes.up_count'] = -1
195
- else
196
- inc_options['votes.down_count'] = -1
197
- end
198
- end
199
- else # new vote
200
- inc_options['votes.point'] = voteable[value]
201
- unless voteable[:update_counters] == false
202
- inc_options['votes.count'] = +1
203
- if value == :up
204
- inc_options['votes.up_count'] = +1
205
- else
206
- inc_options['votes.down_count'] = +1
207
- end
208
- end
209
- end
210
-
211
- class_name.constantize.collection.update(
212
- { :_id => foreign_key_value },
213
- { '$inc' => inc_options }
214
- )
215
- end
216
- end
217
- true
218
- end
219
50
  end
220
-
51
+
221
52
  # Make a vote on this votee
222
53
  #
223
54
  # @param [Hash] options a hash containings:
224
55
  # - :voter_id: the voter document id
225
56
  # - :value: vote :up or vote :down
57
+ # - :revote: change from vote up to vote down
58
+ # - :unvote: unvote the vote value (:up or :down)
226
59
  def vote(options)
227
- options[:votee_id] ||= _id
228
- options[:votee] ||= self
60
+ options[:votee_id] = id
61
+ options[:votee] = self
62
+ options[:voter_id] ||= options[:voter].id
229
63
 
230
64
  if options[:unvote]
231
65
  options[:value] ||= vote_value(options[:voter_id])
232
66
  else
233
- options[:revote] ||= !vote_value(options[:voter_id]).nil?
67
+ options[:revote] ||= vote_value(options[:voter_id]).present?
234
68
  end
235
69
 
236
70
  self.class.vote(options)
@@ -240,19 +74,44 @@ module Mongoid
240
74
  #
241
75
  # @param [Mongoid Object, BSON::ObjectId] voter is Mongoid object or the id of the voter who made the vote
242
76
  def vote_value(voter)
243
- voter_id = voter.is_a?(BSON::ObjectId) ? voter : voter._id
77
+ voter_id = voter.is_a?(BSON::ObjectId) ? voter : voter.id
244
78
  return :up if up_voter_ids.include?(voter_id)
245
79
  return :down if down_voter_ids.include?(voter_id)
246
80
  end
81
+
82
+ def voted_by?(voter)
83
+ !!vote_value(voter)
84
+ end
247
85
 
248
86
  # Array of up voter ids
249
87
  def up_voter_ids
250
88
  votes.try(:[], 'up') || []
251
89
  end
252
-
90
+
253
91
  # Array of down voter ids
254
92
  def down_voter_ids
255
93
  votes.try(:[], 'down') || []
256
94
  end
95
+
96
+ # Get the number of up votes
97
+ def up_votes_count
98
+ votes.try(:[], 'up_count') || 0
99
+ end
100
+
101
+ # Get the number of down votes
102
+ def down_votes_count
103
+ votes.try(:[], 'down_count') || 0
104
+ end
105
+
106
+ # Get the number of votes
107
+ def votes_count
108
+ votes.try(:[], 'count') || 0
109
+ end
110
+
111
+ # Get the votes point
112
+ def votes_point
113
+ votes.try(:[], 'point') || 0
114
+ end
115
+
257
116
  end
258
117
  end
@@ -0,0 +1,156 @@
1
+ module Mongoid
2
+ module Voteable
3
+ module Tasks
4
+
5
+ # Set counters and point to 0 for uninitialized voteable objects
6
+ # in order sort and query
7
+ def self.init_stats(log = false)
8
+ VOTEABLE.each do |class_name, voteable|
9
+ klass = class_name.constantize
10
+ klass_voteable = voteable[class_name]
11
+ puts "Init stats for #{class_name}" if log
12
+ klass.collection.update({:votes => nil}, {
13
+ '$set' => { :votes => Votes::DEFAULT_ATTRIBUTES }
14
+ }, {
15
+ :safe => true,
16
+ :multi => true
17
+ })
18
+ end
19
+ end
20
+
21
+ # Re-generate vote counters and vote points
22
+ def self.remake_stats(log = false)
23
+ remake_stats_for_all_voteable_classes(log)
24
+ update_parent_stats(log)
25
+ end
26
+
27
+ # Convert votes from from version < 0.7.0 to new data store
28
+ def self.migrate_old_votes(log = false)
29
+ VOTEABLE.each do |class_name, voteable|
30
+ klass = class_name.constantize
31
+ klass_voteable = voteable[class_name]
32
+ puts "* Migrating old vote data for #{class_name} ..." if log
33
+ migrate_old_votes_for(klass, klass_voteable)
34
+ end
35
+ end
36
+
37
+ def self.migrate_old_votes_for(klass, voteable)
38
+ klass.all.each do |doc|
39
+ # Version 0.6.x use very short field names (u, d, uc, dc, c, p) to minimize
40
+ # votes storage but it's not human friendly
41
+ # Version >= 0.7.0 use readable field names (up, down, up_count, down_count,
42
+ # count, point)
43
+ votes = doc['votes'] || doc['voteable'] || {}
44
+
45
+ up_voter_ids = votes['u'] || votes['up'] ||
46
+ votes['up_voter_ids'] || doc['up_voter_ids'] || []
47
+
48
+ down_voter_ids = votes['d'] || votes['down'] ||
49
+ votes['down_voter_ids'] || doc['down_voter_ids'] || []
50
+
51
+ up_count = up_voter_ids.size
52
+ down_count = down_voter_ids.size
53
+
54
+ klass.collection.update({ :_id => doc.id }, {
55
+ '$set' => {
56
+ 'votes' => {
57
+ 'up' => up_voter_ids,
58
+ 'down' => down_voter_ids,
59
+ 'up_count' => up_count,
60
+ 'down_count' => down_count,
61
+ 'count' => up_count + down_count,
62
+ 'point' => voteable[:up]*up_count + voteable[:down]*down_count
63
+ }
64
+ },
65
+ '$unset' => {
66
+ 'up_voter_ids' => true,
67
+ 'down_voter_ids' => true,
68
+ 'votes_count' => true,
69
+ 'votes_point' => true,
70
+ 'voteable' => true
71
+ }
72
+ }, { :safe => true })
73
+ end
74
+ end
75
+
76
+
77
+ def self.remake_stats_for_all_voteable_classes(log)
78
+ VOTEABLE.each do |class_name, voteable|
79
+ klass = class_name.constantize
80
+ klass_voteable = voteable[class_name]
81
+ puts "Generating stats for #{class_name}" if log
82
+ klass.all.each{ |doc|
83
+ remake_stats_for(doc, klass_voteable)
84
+ }
85
+ end
86
+ end
87
+
88
+
89
+ def self.remake_stats_for(doc, voteable)
90
+ up_count = doc.up_voter_ids.length
91
+ down_count = doc.down_voter_ids.length
92
+
93
+ doc.update_attributes(
94
+ 'votes.up_count' => up_count,
95
+ 'votes.down_count' => down_count,
96
+ 'votes.count' => up_count + down_count,
97
+ 'votes.point' => voteable[:up]*up_count + voteable[:down]*down_count
98
+ )
99
+ end
100
+
101
+
102
+ def self.update_parent_stats(log)
103
+ VOTEABLE.each do |class_name, voteable|
104
+ klass = class_name.constantize
105
+ voteable.each do |parent_class_name, parent_voteable|
106
+ relation_metadata = klass.relations[parent_class_name.underscore]
107
+ if relation_metadata
108
+ parent_class = parent_class_name.constantize
109
+ foreign_key = relation_metadata.foreign_key
110
+ puts "Updating stats for #{class_name} > #{parent_class_name}" if log
111
+ klass.all.each{ |doc|
112
+ update_parent_stats_for(doc, parent_class, foreign_key, parent_voteable)
113
+ }
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+
120
+ def self.update_parent_stats_for(doc, parent_class, foreign_key, voteable)
121
+ parent_id = doc.read_attribute(foreign_key.to_sym)
122
+ if parent_id
123
+ up_count = doc.up_voter_ids.length
124
+ down_count = doc.down_voter_ids.length
125
+
126
+ return if up_count == 0 && down_count == 0
127
+
128
+ inc_options = {
129
+ 'votes.point' => voteable[:up]*up_count + voteable[:down]*down_count
130
+ }
131
+
132
+ unless voteable[:update_counters] == false
133
+ inc_options.merge!(
134
+ 'votes.count' => up_count + down_count,
135
+ 'votes.up_count' => up_count,
136
+ 'votes.down_count' => down_count
137
+ )
138
+ end
139
+
140
+ parent_class.collection.update(
141
+ { '_id' => parent_id },
142
+ { '$inc' => inc_options },
143
+ { :safe => true }
144
+ )
145
+ end
146
+ end
147
+
148
+ private_class_method :migrate_old_votes_for,
149
+ :remake_stats_for,
150
+ :remake_stats_for_all_voteable_classes,
151
+ :update_parent_stats,
152
+ :update_parent_stats_for
153
+
154
+ end
155
+ end
156
+ end
@@ -3,56 +3,16 @@ module Mongoid
3
3
 
4
4
  class Votes
5
5
  include Mongoid::Document
6
+
6
7
  field :up, :type => Array, :default => []
7
8
  field :down, :type => Array, :default => []
8
9
  field :up_count, :type => Integer, :default => 0
9
10
  field :down_count, :type => Integer, :default => 0
10
11
  field :count, :type => Integer, :default => 0
11
12
  field :point, :type => Integer, :default => 0
12
- end
13
-
14
- VOTES_DEFAULT_ATTRIBUTES = Votes.new.attributes
15
- VOTES_DEFAULT_ATTRIBUTES.delete('_id')
16
-
17
- def self.migrate_old_votes(log = false)
18
- VOTEABLE.each do |class_name, voteable|
19
- klass = class_name.constantize
20
- klass_voteable = voteable[class_name]
21
- puts "* Migrating old vote data for #{class_name} ..." if log
22
13
 
23
- klass.all.each do |doc|
24
- # Version 0.6.x use very short field names (u, d, uc, dc, c, p) to minimize
25
- # votes storage but it's not human friendly
26
- # Version >= 0.7.0 use readable field names (up, down, up_count, down_count,
27
- # count, point)
28
- votes = doc['votes'] || doc['voteable'] || {}
29
- up_voter_ids = votes['u'] || votes['up'] || votes['up_voter_ids'] || doc['up_voter_ids'] || []
30
- down_voter_ids = votes['d'] || votes['down'] || votes['down_voter_ids'] || doc['down_voter_ids'] || []
31
-
32
- up_count = up_voter_ids.size
33
- down_count = down_voter_ids.size
34
-
35
- klass.collection.update({ :_id => doc.id }, {
36
- '$set' => {
37
- 'votes' => {
38
- 'up' => up_voter_ids,
39
- 'down' => down_voter_ids,
40
- 'up_count' => up_count,
41
- 'down_count' => down_count,
42
- 'count' => up_count + down_count,
43
- 'point' => klass_voteable[:up]*up_count + klass_voteable[:down]*down_count
44
- }
45
- },
46
- '$unset' => {
47
- 'up_voter_ids' => true,
48
- 'down_voter_ids' => true,
49
- 'votes_count' => true,
50
- 'votes_point' => true,
51
- 'voteable' => true
52
- }
53
- })
54
- end
55
- end
14
+ DEFAULT_ATTRIBUTES = new.attributes
15
+ DEFAULT_ATTRIBUTES.delete('_id')
56
16
  end
57
17
 
58
18
  end
@@ -0,0 +1,202 @@
1
+ module Mongoid
2
+ module Voteable
3
+ module Voting
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ # Make a vote on an object of this class
8
+ #
9
+ # @param [Hash] options a hash containings:
10
+ # - :votee_id: the votee document id
11
+ # - :voter_id: the voter document id
12
+ # - :value: :up or :down
13
+ # - :revote: change from vote up to vote down
14
+ # - :unvote: unvote the vote value (:up or :down)
15
+ def vote(options)
16
+ options.symbolize_keys!
17
+ options[:votee_id] = BSON::ObjectId(options[:votee_id]) if options[:votee_id].is_a?(String)
18
+ options[:voter_id] = BSON::ObjectId(options[:voter_id]) if options[:voter_id].is_a?(String)
19
+ options[:value] = options[:value].to_sym
20
+ options[:voteable] = VOTEABLE[name][name]
21
+
22
+ update_parents = true
23
+
24
+ if options[:voteable]
25
+ update_result = if options[:revote]
26
+ revote(options)
27
+ elsif options[:unvote]
28
+ unvote(options)
29
+ else
30
+ new_vote(options)
31
+ end
32
+ # Only update parent votes if votee is updated successfully
33
+ update_parents = ( update_result['err'] == nil and
34
+ update_result['updatedExisting'] == true and
35
+ update_result['n'] == 1 )
36
+ end
37
+
38
+ update_parent_votes(options) if update_parents
39
+ end
40
+
41
+
42
+ private
43
+ def new_vote(options)
44
+ if options[:value] == :up
45
+ positive_voter_ids = 'votes.up'
46
+ positive_votes_count = 'votes.up_count'
47
+ else
48
+ positive_voter_ids = 'votes.down'
49
+ positive_votes_count = 'votes.down_count'
50
+ end
51
+
52
+ collection.update({
53
+ # Validate voter_id did not vote for votee_id yet
54
+ :_id => options[:votee_id],
55
+ 'votes.up' => { '$ne' => options[:voter_id] },
56
+ 'votes.down' => { '$ne' => options[:voter_id] }
57
+ }, {
58
+ # then update
59
+ '$push' => { positive_voter_ids => options[:voter_id] },
60
+ '$inc' => {
61
+ 'votes.count' => +1,
62
+ positive_votes_count => +1,
63
+ 'votes.point' => options[:voteable][options[:value]] }
64
+ }, {
65
+ :safe => true
66
+ })
67
+ end
68
+
69
+
70
+ def revote(options)
71
+ if options[:value] == :up
72
+ positive_voter_ids = 'votes.up'
73
+ negative_voter_ids = 'votes.down'
74
+ positive_votes_count = 'votes.up_count'
75
+ negative_votes_count = 'votes.down_count'
76
+ point_delta = options[:voteable][:up] - options[:voteable][:down]
77
+ else
78
+ positive_voter_ids = 'votes.down'
79
+ negative_voter_ids = 'votes.up'
80
+ positive_votes_count = 'votes.down_count'
81
+ negative_votes_count = 'votes.up_count'
82
+ point_delta = -options[:voteable][:up] + options[:voteable][:down]
83
+ end
84
+
85
+ collection.update({
86
+ # Validate voter_id did a vote with value for votee_id
87
+ :_id => options[:votee_id],
88
+ positive_voter_ids => { '$ne' => options[:voter_id] },
89
+ negative_voter_ids => options[:voter_id]
90
+ }, {
91
+ # then update
92
+ '$pull' => { negative_voter_ids => options[:voter_id] },
93
+ '$push' => { positive_voter_ids => options[:voter_id] },
94
+ '$inc' => {
95
+ positive_votes_count => +1,
96
+ negative_votes_count => -1,
97
+ 'votes.point' => point_delta
98
+ }
99
+ }, {
100
+ :safe => true
101
+ })
102
+ end
103
+
104
+
105
+ def unvote(options)
106
+ if options[:value] == :up
107
+ positive_voter_ids = 'votes.up'
108
+ negative_voter_ids = 'votes.down'
109
+ positive_votes_count = 'votes.up_count'
110
+ else
111
+ positive_voter_ids = 'votes.down'
112
+ negative_voter_ids = 'votes.up'
113
+ positive_votes_count = 'votes.down_count'
114
+ end
115
+
116
+ # Check if voter_id did a vote with value for votee_id
117
+ update_result = collection.update({
118
+ # Validate voter_id did a vote with value for votee_id
119
+ :_id => options[:votee_id],
120
+ negative_voter_ids => { '$ne' => options[:voter_id] },
121
+ positive_voter_ids => options[:voter_id]
122
+ }, {
123
+ # then update
124
+ '$pull' => { positive_voter_ids => options[:voter_id] },
125
+ '$inc' => {
126
+ positive_votes_count => -1,
127
+ 'votes.count' => -1,
128
+ 'votes.point' => -options[:voteable][options[:value]]
129
+ }
130
+ }, {
131
+ :safe => true
132
+ })
133
+ end
134
+
135
+
136
+ def update_parent_votes(options)
137
+ value = options[:value]
138
+ votee ||= options[:votee]
139
+
140
+ VOTEABLE[name].each do |class_name, voteable|
141
+ # For other class in VOTEABLE options, if is parent of current class
142
+ next unless relation_metadata = relations[class_name.underscore]
143
+ votee ||= find(options[:votee_id])
144
+ # If can find current votee foreign_key value for that class
145
+ next unless foreign_key_value = votee.read_attribute(relation_metadata.foreign_key)
146
+
147
+ class_name.constantize.collection.update(
148
+ { '_id' => foreign_key_value },
149
+ { '$inc' => parent_inc_options(value, voteable, options) }
150
+ )
151
+ end
152
+ end
153
+
154
+
155
+ def parent_inc_options(value, voteable, options)
156
+ inc_options = {}
157
+
158
+ if options[:revote]
159
+ if value == :up
160
+ inc_options['votes.point'] = voteable[:up] - voteable[:down]
161
+ unless voteable[:update_counters] == false
162
+ inc_options['votes.up_count'] = +1
163
+ inc_options['votes.down_count'] = -1
164
+ end
165
+ else
166
+ inc_options['votes.point'] = -voteable[:up] + voteable[:down]
167
+ unless voteable[:update_counters] == false
168
+ inc_options['votes.up_count'] = -1
169
+ inc_options['votes.down_count'] = +1
170
+ end
171
+ end
172
+
173
+ elsif options[:unvote]
174
+ inc_options['votes.point'] = -voteable[value]
175
+ unless voteable[:update_counters] == false
176
+ inc_options['votes.count'] = -1
177
+ if value == :up
178
+ inc_options['votes.up_count'] = -1
179
+ else
180
+ inc_options['votes.down_count'] = -1
181
+ end
182
+ end
183
+
184
+ else # new vote
185
+ inc_options['votes.point'] = voteable[value]
186
+ unless voteable[:update_counters] == false
187
+ inc_options['votes.count'] = +1
188
+ if value == :up
189
+ inc_options['votes.up_count'] = +1
190
+ else
191
+ inc_options['votes.down_count'] = +1
192
+ end
193
+ end
194
+ end
195
+
196
+ inc_options
197
+ end
198
+ end
199
+
200
+ end
201
+ end
202
+ end
@@ -9,12 +9,12 @@ module Mongoid
9
9
  def voted?(options)
10
10
  unless options.is_a?(Hash)
11
11
  votee_class = options.class
12
- votee_id = options._id
12
+ votee_id = options.id
13
13
  else
14
14
  votee = options[:votee]
15
15
  if votee
16
16
  votee_class = votee.class
17
- votee_id = votee._id
17
+ votee_id = votee.id
18
18
  else
19
19
  votee_class = options[:votee_type].classify.constantize
20
20
  votee_id = options[:votee_id]
@@ -64,7 +64,7 @@ module Mongoid
64
64
  end
65
65
 
66
66
  if votee
67
- options[:votee_id] = votee._id
67
+ options[:votee_id] = votee.id
68
68
  votee_class = votee.class
69
69
  else
70
70
  votee_class = options[:votee_type].classify.constantize
@@ -77,7 +77,8 @@ module Mongoid
77
77
  options[:revote] = options.has_key?(:revote) ? !options[:revote].blank? : voted?(options)
78
78
  end
79
79
 
80
- options[:voter_id] = _id
80
+ options[:voter] = self
81
+ options[:voter_id] = id
81
82
 
82
83
  ( votee || votee_class ).vote(options)
83
84
  end
data/spec/spec_helper.rb CHANGED
@@ -26,6 +26,6 @@ end
26
26
 
27
27
  Dir[ File.join(MODELS, "*.rb") ].sort.each { |file| require File.basename(file) }
28
28
 
29
-
30
- RSpec.configure do |config|
31
- end
29
+ User.collection.drop
30
+ Post.collection.drop
31
+ Comment.collection.drop
@@ -2,8 +2,6 @@ require "spec_helper"
2
2
 
3
3
  describe Mongoid::Voteable do
4
4
  before :all do
5
- Mongoid::database.connection.drop_database(Mongoid::database.name)
6
-
7
5
  @post1 = Post.create!
8
6
  @post2 = Post.create!
9
7
 
@@ -38,9 +36,9 @@ describe Mongoid::Voteable do
38
36
  Post.voted_by(@user2).should be_empty
39
37
  end
40
38
 
41
- it 'test Stats.init' do
42
- @post1.votes.should == Mongoid::Voteable::VOTES_DEFAULT_ATTRIBUTES
43
- @post2.votes.should == Mongoid::Voteable::VOTES_DEFAULT_ATTRIBUTES
39
+ it 'test init stats' do
40
+ @post1.votes.should == Mongoid::Voteable::Votes::DEFAULT_ATTRIBUTES
41
+ @post2.votes.should == Mongoid::Voteable::Votes::DEFAULT_ATTRIBUTES
44
42
 
45
43
  @post1.votes = nil
46
44
  @post1.save
@@ -48,17 +46,17 @@ describe Mongoid::Voteable do
48
46
  @post2.votes = nil
49
47
  @post2.save
50
48
 
51
- Mongoid::Voteable::Stats.init
49
+ Mongoid::Voteable::Tasks.init_stats
52
50
 
53
51
  @post1.reload
54
52
  @post2.reload
55
53
 
56
- @post1.votes.should == Mongoid::Voteable::VOTES_DEFAULT_ATTRIBUTES
57
- @post2.votes.should == Mongoid::Voteable::VOTES_DEFAULT_ATTRIBUTES
54
+ @post1.votes.should == Mongoid::Voteable::Votes::DEFAULT_ATTRIBUTES
55
+ @post2.votes.should == Mongoid::Voteable::Votes::DEFAULT_ATTRIBUTES
58
56
  end
59
57
 
60
58
  it 'revote has no effect' do
61
- Post.vote(:revote => true, :votee_id => @post1.id, :voter_id => @user1.id, :value => 'up')
59
+ @post1.vote(:revote => true, :voter => @user1, :value => 'up')
62
60
  @post1.reload
63
61
 
64
62
  @post1.up_votes_count.should == 0
@@ -89,7 +87,9 @@ describe Mongoid::Voteable do
89
87
  @post1.votes_point.should == 1
90
88
 
91
89
  @post1.vote_value(@user1).should == :up
90
+ @post1.should be_voted_by(@user1)
92
91
  @post1.vote_value(@user2.id).should be_nil
92
+ @post1.should_not be_voted_by(@user2.id)
93
93
 
94
94
  Post.voted_by(@user1).to_a.should == [ @post1 ]
95
95
  Post.voted_by(@user2).to_a.should be_empty
@@ -131,7 +131,7 @@ describe Mongoid::Voteable do
131
131
  context 'user1 change vote on post1 from up to down' do
132
132
  before :all do
133
133
  Post.vote(:revote => true, :votee_id => @post1.id, :voter_id => @user1.id, :value => :down)
134
- Mongoid::Voteable::Stats.remake
134
+ Mongoid::Voteable::Tasks.remake_stats
135
135
  @post1.reload
136
136
  end
137
137
 
@@ -171,7 +171,7 @@ describe Mongoid::Voteable do
171
171
  context 'user1 change vote on post2 from down to up' do
172
172
  before :all do
173
173
  Post.vote(:revote => true, :votee_id => @post2.id.to_s, :voter_id => @user1.id.to_s, :value => :up)
174
- Mongoid::Voteable::Stats.remake
174
+ Mongoid::Voteable::Tasks.remake_stats
175
175
  @post2.reload
176
176
  end
177
177
 
@@ -247,7 +247,7 @@ describe Mongoid::Voteable do
247
247
  context "user1 unvote on post1" do
248
248
  before(:all) do
249
249
  @post1.vote(:voter_id => @user1.id, :votee_id => @post1.id, :unvote => true)
250
- Mongoid::Voteable::Stats.remake
250
+ Mongoid::Voteable::Tasks.remake_stats
251
251
  @post1.reload
252
252
  end
253
253
 
@@ -283,7 +283,7 @@ describe Mongoid::Voteable do
283
283
  context "user1 unvote on comment" do
284
284
  before(:all) do
285
285
  @user1.unvote(@comment)
286
- Mongoid::Voteable::Stats.remake
286
+ Mongoid::Voteable::Tasks.remake_stats
287
287
  @comment.reload
288
288
  @post2.reload
289
289
  end
@@ -303,7 +303,7 @@ describe Mongoid::Voteable do
303
303
 
304
304
  context 'final' do
305
305
  it "test remake stats" do
306
- Mongoid::Voteable::Stats.remake
306
+ Mongoid::Voteable::Tasks.remake_stats
307
307
 
308
308
  @post1.up_votes_count.should == 0
309
309
  @post1.down_votes_count.should == 1
@@ -2,8 +2,6 @@ require "spec_helper"
2
2
 
3
3
  describe Mongoid::Voter do
4
4
  before :all do
5
- Mongoid::database.connection.drop_database(Mongoid::database.name)
6
-
7
5
  @post1 = Post.create!
8
6
  @post2 = Post.create!
9
7
 
@@ -15,8 +15,6 @@ Gem::Specification.new do |s|
15
15
  s.add_dependency 'mongoid', '~> 2.0.0'
16
16
 
17
17
  s.add_development_dependency 'rspec'
18
- s.add_development_dependency 'bson_ext'
19
- s.add_development_dependency 'watchr'
20
18
 
21
19
  s.rubyforge_project = "voteable_mongoid"
22
20
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: voteable_mongoid
3
3
  version: !ruby/object:Gem::Version
4
- hash: 3
4
+ hash: 1
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 7
9
- - 0
10
- version: 0.7.0
9
+ - 1
10
+ version: 0.7.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Alex Nguyen
@@ -15,8 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-04-02 00:00:00 +08:00
19
- default_executable:
18
+ date: 2011-04-03 00:00:00 Z
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
22
21
  name: mongoid
@@ -48,34 +47,6 @@ dependencies:
48
47
  version: "0"
49
48
  type: :development
50
49
  version_requirements: *id002
51
- - !ruby/object:Gem::Dependency
52
- name: bson_ext
53
- prerelease: false
54
- requirement: &id003 !ruby/object:Gem::Requirement
55
- none: false
56
- requirements:
57
- - - ">="
58
- - !ruby/object:Gem::Version
59
- hash: 3
60
- segments:
61
- - 0
62
- version: "0"
63
- type: :development
64
- version_requirements: *id003
65
- - !ruby/object:Gem::Dependency
66
- name: watchr
67
- prerelease: false
68
- requirement: &id004 !ruby/object:Gem::Requirement
69
- none: false
70
- requirements:
71
- - - ">="
72
- - !ruby/object:Gem::Version
73
- hash: 3
74
- segments:
75
- - 0
76
- version: "0"
77
- type: :development
78
- version_requirements: *id004
79
50
  description: Add Up / Down Voting for Mongoid (built for speed by using one Mongodb update-in-place for each collection when provided enough information)
80
51
  email:
81
52
  - alex@vinova.sg
@@ -98,8 +69,9 @@ files:
98
69
  - lib/voteable_mongoid/railties/database.rake
99
70
  - lib/voteable_mongoid/version.rb
100
71
  - lib/voteable_mongoid/voteable.rb
101
- - lib/voteable_mongoid/voteable/stats.rb
72
+ - lib/voteable_mongoid/voteable/tasks.rb
102
73
  - lib/voteable_mongoid/voteable/votes.rb
74
+ - lib/voteable_mongoid/voteable/voting.rb
103
75
  - lib/voteable_mongoid/voter.rb
104
76
  - spec/.rspec
105
77
  - spec/models/comment.rb
@@ -109,7 +81,6 @@ files:
109
81
  - spec/voteable_mongoid/voteable_spec.rb
110
82
  - spec/voteable_mongoid/voter_spec.rb
111
83
  - voteable_mongoid.gemspec
112
- has_rdoc: true
113
84
  homepage: https://github.com/vinova/voteable_mongoid
114
85
  licenses: []
115
86
 
@@ -139,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
110
  requirements: []
140
111
 
141
112
  rubyforge_project: voteable_mongoid
142
- rubygems_version: 1.6.2
113
+ rubygems_version: 1.7.1
143
114
  signing_key:
144
115
  specification_version: 3
145
116
  summary: Add Up / Down Voting for Mongoid
@@ -1,115 +0,0 @@
1
- module Mongoid
2
- module Voteable
3
- module Stats
4
- extend ActiveSupport::Concern
5
-
6
- # Get the number of up votes
7
- def up_votes_count
8
- votes.try(:[], 'up_count') || 0
9
- end
10
-
11
- # Get the number of down votes
12
- def down_votes_count
13
- votes.try(:[], 'down_count') || 0
14
- end
15
-
16
- # Get the number of votes
17
- def votes_count
18
- votes.try(:[], 'count') || 0
19
- end
20
-
21
- # Get the votes point
22
- def votes_point
23
- votes.try(:[], 'point') || 0
24
- end
25
-
26
- def self.init(log = false)
27
- VOTEABLE.each do |class_name, voteable|
28
- klass = class_name.constantize
29
- klass_voteable = voteable[class_name]
30
- puts "Init stats for #{class_name}" if log
31
- klass.collection.update({:votes => nil}, {
32
- '$set' => { :votes => VOTES_DEFAULT_ATTRIBUTES }
33
- }, {
34
- :safe => true,
35
- :multi => true
36
- })
37
- end
38
- end
39
-
40
- # Re-generate vote counters and vote points
41
- def self.remake(log = false)
42
- remake_stats(log)
43
- update_parent_stats(log)
44
- end
45
-
46
- def self.remake_stats(log)
47
- VOTEABLE.each do |class_name, voteable|
48
- klass = class_name.constantize
49
- klass_voteable = voteable[class_name]
50
- puts "Generating stats for #{class_name}" if log
51
- klass.all.each{ |doc|
52
- doc.remake_stats(klass_voteable)
53
- }
54
- end
55
- end
56
-
57
- def remake_stats(voteable)
58
- up_count = up_voter_ids.length
59
- down_count = down_voter_ids.length
60
-
61
- update_attributes(
62
- 'votes.up_count' => up_count,
63
- 'votes.down_count' => down_count,
64
- 'votes.count' => up_count + down_count,
65
- 'votes.point' => voteable[:up]*up_count + voteable[:down]*down_count
66
- )
67
- end
68
-
69
- def self.update_parent_stats(log)
70
- VOTEABLE.each do |class_name, voteable|
71
- klass = class_name.constantize
72
- voteable.each do |parent_class_name, parent_voteable|
73
- relation_metadata = klass.relations[parent_class_name.underscore]
74
- if relation_metadata
75
- parent_class = parent_class_name.constantize
76
- foreign_key = relation_metadata.foreign_key
77
- puts "Updating stats for #{class_name} > #{parent_class_name}" if log
78
- klass.all.each{ |doc|
79
- doc.update_parent_stats(parent_class, foreign_key, parent_voteable)
80
- }
81
- end
82
- end
83
- end
84
- end
85
-
86
- def update_parent_stats(parent_class, foreign_key, voteable)
87
- parent_id = read_attribute(foreign_key.to_sym)
88
- if parent_id
89
- up_count = up_voter_ids.length
90
- down_count = down_voter_ids.length
91
-
92
- return if up_count == 0 && down_count == 0
93
-
94
- inc_options = {
95
- 'votes.point' => voteable[:up]*up_count + voteable[:down]*down_count
96
- }
97
-
98
- unless voteable[:update_counters] == false
99
- inc_options.merge!(
100
- 'votes.count' => up_count + down_count,
101
- 'votes.up_count' => up_count,
102
- 'votes.down_count' => down_count
103
- )
104
- end
105
-
106
- parent_class.collection.update(
107
- { '_id' => parent_id },
108
- { '$inc' => inc_options }
109
- )
110
- end
111
- end
112
-
113
- end
114
- end
115
- end