voteable_mongoid 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
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