voteable_mongoid 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/README.rdoc +12 -8
  2. data/lib/mongoid/voteable.rb +104 -115
  3. data/lib/mongoid/voter.rb +25 -21
  4. metadata +15 -86
data/README.rdoc CHANGED
@@ -1,8 +1,10 @@
1
- = Votable Mongoid
1
+ = Voteable Mongoid
2
2
 
3
- Votable Mongoid allows you to make your Mongoid::Document objects votable (up or down)
3
+ Voteable Mongoid allows you to make your Mongoid::Document objects voteable (up or down)
4
4
 
5
- For instance, in a q&a site, a user can vote up (or down) on a post or a comment.
5
+ For instance, in a Q&A site, a user can vote up (or down) on a post or a comment.
6
+
7
+ When user provided enough information, voteable_mongoid will use only one update-in-place operation per collection.
6
8
 
7
9
  == Installation
8
10
 
@@ -10,13 +12,13 @@ For instance, in a q&a site, a user can vote up (or down) on a post or a comment
10
12
 
11
13
  To install the gem, add this to your Gemfile
12
14
 
13
- gem 'votable_mongoid'
15
+ gem 'voteable_mongoid'
14
16
 
15
17
  After that, remember to run "bundle install"
16
18
 
17
19
  == Usage
18
20
 
19
- === Making Post and Comment votable, User being the voter
21
+ === Making Post and Comment voteable, User being the voter
20
22
 
21
23
  post.rb
22
24
 
@@ -77,9 +79,11 @@ user.rb
77
79
 
78
80
  @user.unvote(@comment)
79
81
 
80
- == Contributors
82
+ == Credits
81
83
 
82
- * Alex N. - Author, Maintainer
84
+ * Alex N. - Author
83
85
  * Stefan N. - Unvoting
84
86
 
85
- Copyright (c) 2010-2011 Alex Nguyen (http://alex.vinova.sg) and Vinova Pre Ltd (http://vinova.sg)
87
+ Copyright (c) 2010-2011 Vinova Pte Ltd (http://vinova.sg)
88
+
89
+ Licensed under the MIT license.
@@ -3,7 +3,7 @@ module Mongoid
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  # How many points should be assigned for each up or down vote.
6
- # This array should be manipulated using Votable.vote_point method
6
+ # This array should be manipulated using Voteable.vote_point method
7
7
  VOTE_POINT = {}
8
8
 
9
9
  included do
@@ -38,127 +38,140 @@ module Mongoid
38
38
  # - :voter_id: the voter document id
39
39
  # - :value: :up or :down
40
40
  # - :revote: change from vote up to vote down
41
+ # - :unvote: unvote the vote value (:up or :down)
41
42
  def self.vote(options)
42
43
  options.symbolize_keys!
44
+ value = options[:value].to_sym
43
45
 
44
46
  votee_id = options[:votee_id]
45
47
  voter_id = options[:voter_id]
46
- value = options[:value]
47
48
 
48
49
  votee_id = BSON::ObjectId(votee_id) if votee_id.is_a?(String)
49
50
  voter_id = BSON::ObjectId(voter_id) if voter_id.is_a?(String)
50
51
 
51
- value = value.to_sym
52
- value_point = VOTE_POINT[self.name][self.name]
52
+ klass = options[:class]
53
+ klass ||= VOTE_POINT.keys.include?(name) ? name : collection.name.classify
54
+ value_point = VOTE_POINT[klass][klass]
53
55
 
54
56
  if options[:revote]
55
57
  if value == :up
56
- push_field = :up_voter_ids
57
- pull_field = :down_voter_ids
58
+ positive_field = :up_voter_ids
59
+ negative_field = :down_voter_ids
58
60
  point_delta = value_point[:up] - value_point[:down]
59
61
  else
60
- push_field = :down_voter_ids
61
- pull_field = :up_voter_ids
62
+ positive_field = :down_voter_ids
63
+ negative_field = :up_voter_ids
62
64
  point_delta = -value_point[:up] + value_point[:down]
63
65
  end
64
66
 
65
- collection.update({
67
+ update_result = collection.update({
68
+ # Validate voter_id did a vote with value for votee_id
66
69
  :_id => votee_id,
67
- push_field => { '$ne' => voter_id },
68
- pull_field => voter_id
70
+ positive_field => { '$ne' => voter_id },
71
+ negative_field => voter_id
69
72
  }, {
70
- '$pull' => { pull_field => voter_id },
71
- '$push' => { push_field => voter_id },
73
+ # then update
74
+ '$pull' => { negative_field => voter_id },
75
+ '$push' => { positive_field => voter_id },
72
76
  '$inc' => {
73
77
  :votes_point => point_delta
74
78
  }
79
+ }, {
80
+ :safe => true
75
81
  })
76
82
 
83
+ elsif options[:unvote]
84
+ if value == :down
85
+ positive_field = :down_voter_ids
86
+ negative_field = :up_voter_ids
87
+ else
88
+ positive_field = :up_voter_ids
89
+ negative_field = :down_voter_ids
90
+ end
91
+
92
+ # Check if voter_id did a vote with value for votee_id
93
+ update_result = collection.update({
94
+ # Validate voter_id did a vote with value for votee_id
95
+ :_id => votee_id,
96
+ negative_field => { '$ne' => voter_id },
97
+ positive_field => voter_id
98
+ }, {
99
+ # then update
100
+ '$pull' => { positive_field => voter_id },
101
+ '$inc' => {
102
+ :votes_count => -1,
103
+ :votes_point => -value_point[value]
104
+ }
105
+ }, {
106
+ :safe => true
107
+ })
108
+
77
109
  else # new vote
78
110
  if value.to_sym == :up
79
- push_field = :up_voter_ids
111
+ positive_field = :up_voter_ids
80
112
  else
81
- push_field = :down_voter_ids
113
+ positive_field = :down_voter_ids
82
114
  end
83
115
 
84
- collection.update({
116
+ update_result = collection.update({
117
+ # Validate voter_id did not vote for votee_id yet
85
118
  :_id => votee_id,
86
119
  :up_voter_ids => { '$ne' => voter_id },
87
120
  :down_voter_ids => { '$ne' => voter_id },
88
121
  }, {
89
- '$push' => { push_field => voter_id },
122
+ # then update
123
+ '$push' => { positive_field => voter_id },
90
124
  '$inc' => {
91
125
  :votes_count => +1,
92
126
  :votes_point => value_point[value]
93
127
  }
128
+ }, {
129
+ :safe => true
94
130
  })
95
131
  end
96
132
 
97
- VOTE_POINT[self.name].each do |class_name, value_point|
98
- next unless relation_metadata = relations[class_name.underscore]
99
- next unless foreign_key_value = options[relation_metadata.foreign_key.to_sym]
100
- foreign_key_value = BSON::ObjectId(foreign_key_value) if foreign_key_value.is_a?(String)
101
-
102
- class_name.constantize.collection.update({ :_id => foreign_key_value }, {
103
- '$inc' => options[:revote] ? {
104
- :votes_point => ( value == :up ?
105
- value_point[:up] - value_point[:down] :
106
- -value_point[:up] + value_point[:down] )
107
- } : {
108
- :votes_count => value_point[:not_increase_votes_count] ? 0 : 1,
109
- :votes_point => value_point[value]
110
- }
111
- })
112
- end
113
- end
133
+ # Only update parent class if votee is updated successfully
134
+ successed = ( update_result['err'] == nil and
135
+ update_result['updatedExisting'] == true and
136
+ update_result['n'] == 1 )
114
137
 
115
- # Cancel a vote on an object of this class
116
- #
117
- # @param [Hash] options a hash containings:
118
- # - :votee_id: the votee document id
119
- # - :voter_id: the voter document id
120
- # - :value: voted :up or :down
121
- def self.unvote(options)
122
- options.symbolize_keys!
123
-
124
- voter_id = options[:voter_id]
125
- voter_id = BSON::ObjectId(voter_id) if voter_id.is_a?(String)
126
- votee_id = options[:votee_id]
127
- votee_id = BSON::ObjectId(votee_id) if votee_id.is_a?(String)
128
- value = options[:value].to_sym
129
- return unless value # not voted yet!
130
-
131
- value_point = VOTE_POINT[self.name][self.name]
132
-
133
- pull_field = if value == :up
134
- :up_voter_ids
135
- else
136
- :down_voter_ids
137
- end
138
-
139
- collection.update({
140
- :_id => votee_id,
141
- }, {
142
- '$pull' => { pull_field => voter_id },
143
- '$inc' => {
144
- :votes_count => -1,
145
- :votes_point => -value_point[value]
146
- }
147
- })
148
-
149
- VOTE_POINT[self.name].each do |class_name, value_point|
150
- next unless relation_metadata = relations[class_name.underscore]
151
- next unless foreign_key_value = options[relation_metadata.foreign_key.to_sym]
152
- foreign_key_value = BSON::ObjectId(foreign_key_value) if foreign_key_value.is_a?(String)
138
+ if successed
139
+ VOTE_POINT[klass].each do |class_name, value_point|
140
+ # For other class in VOTE_POINT options, if is parent of current class
141
+ next unless relation_metadata = relations[class_name.underscore]
142
+ votee ||= options[:votee] || find(options[:votee_id])
143
+ # If can find current votee foreign_key value for that class
144
+ next unless foreign_key_value = votee.read_attribute(relation_metadata.foreign_key.to_sym)
153
145
 
154
- class_name.constantize.collection.update({ :_id => foreign_key_value }, {
155
- '$inc' => {
156
- :votes_count => value_point[:not_increase_votes_count] ? 0 : -1,
157
- :votes_point => -value_point[value]
158
- }
159
- })
146
+ # Update that class / collection
147
+ inc_options = if options[:revote]
148
+ {
149
+ :votes_point => ( value == :up ?
150
+ value_point[:up] - value_point[:down] :
151
+ -value_point[:up] + value_point[:down] )
152
+ }
153
+ elsif options[:unvote]
154
+ {
155
+ :votes_count => value_point[:not_increase_votes_count] ? 0 : -1,
156
+ :votes_point => -value_point[value]
157
+ }
158
+ else
159
+ {
160
+ :votes_count => value_point[:not_increase_votes_count] ? 0 : 1,
161
+ :votes_point => value_point[value]
162
+ }
163
+ end
164
+
165
+ class_name.constantize.collection.update(
166
+ { :_id => foreign_key_value },
167
+ { '$inc' => inc_options }
168
+ )
169
+ end
160
170
  end
171
+
172
+ successed
161
173
  end
174
+
162
175
  end
163
176
 
164
177
  # Make a vote on this votee
@@ -167,59 +180,35 @@ module Mongoid
167
180
  # - :voter_id: the voter document id
168
181
  # - :value: vote :up or vote :down
169
182
  def vote(options)
170
- VOTE_POINT[self.class.name].each do |class_name, value_point|
171
- next unless relation_metadata = relations[class_name.underscore]
172
- next unless foreign_key = relation_metadata.foreign_key
173
- options[foreign_key.to_sym] = read_attribute(foreign_key)
174
- end
175
-
176
183
  options[:votee_id] ||= _id
177
- options[:revote] ||= !vote_value(options[:voter_id]).nil?
184
+ options[:votee] ||= self
178
185
 
179
- self.class.vote(options)
180
- end
181
-
182
- # Cancel a vote on this votee
183
- #
184
- # @param [Hash] options a hash containings:
185
- # - :voter_id: the voter document id
186
- def unvote(options)
187
- VOTE_POINT[self.class.name].each do |class_name, value_point|
188
- next unless relation_metadata = relations[class_name.underscore]
189
- next unless foreign_key = relation_metadata.foreign_key
190
- options[foreign_key.to_sym] = read_attribute(foreign_key)
186
+ if options[:unvote]
187
+ options[:value] ||= vote_value(options[:voter_id])
188
+ else
189
+ options[:revote] ||= !vote_value(options[:voter_id]).nil?
191
190
  end
192
-
193
- options[:votee_id] ||= _id
194
- options[:value] = vote_value(options[:voter_id])
195
- self.class.unvote(options)
191
+
192
+ self.class.vote(options)
196
193
  end
197
194
 
198
195
  # Get a voted value on this votee
199
196
  #
200
- # @param [String, BSON::ObjectId] x the id of the voter who made the vote
201
- def vote_value(x)
202
- voter_id = case x
203
- when String
204
- BSON::ObjectId(x)
205
- when BSON::ObjectId
206
- x
207
- else
208
- x.id
209
- end
210
-
197
+ # @param [Mongoid Object, BSON::ObjectId] voter is Mongoid object the id of the voter who made the vote
198
+ def vote_value(voter)
199
+ voter_id = voter.is_a?(BSON::ObjectId) ? voter : voter._id
211
200
  return :up if up_voter_ids.try(:include?, voter_id)
212
201
  return :down if down_voter_ids.try(:include?, voter_id)
213
202
  end
214
203
 
215
204
  # Get the number of up votes
216
205
  def up_votes_count
217
- up_voter_ids.length
206
+ (up_voter_ids||[]).length
218
207
  end
219
208
 
220
209
  # Get the number of down votes
221
210
  def down_votes_count
222
- down_voter_ids.length
211
+ (down_voter_ids||[]).length
223
212
  end
224
213
  end
225
- end
214
+ end
data/lib/mongoid/voter.rb CHANGED
@@ -5,7 +5,7 @@ module Mongoid
5
5
  # Get list of voted votees
6
6
  #
7
7
  # @param [Class] klass the votee class, e.g. `Post` or `Comment`
8
- # @return [Array, nil] an array of votable objects voted by this voter
8
+ # @return [Array, nil] an array of voteable objects voted by this voter
9
9
  def votees(klass)
10
10
  klass.any_of({ :up_voter_ids => _id }, { :down_voter_ids => _id })
11
11
  end
@@ -22,7 +22,7 @@ module Mongoid
22
22
  votee = options[:votee]
23
23
  if votee
24
24
  votee_class = votee.class
25
- votee_id = votee.id
25
+ votee_id = votee._id
26
26
  else
27
27
  votee_class = options[:votee_type].classify.constantize
28
28
  votee_id = options[:votee_id]
@@ -32,13 +32,6 @@ module Mongoid
32
32
  votees(votee_class).where(:_id => votee_id).count == 1
33
33
  end
34
34
 
35
- # Cancel the vote on a votee
36
- #
37
- # @param [Object] votee the votee to be unvoted
38
- def unvote(votee)
39
- votee.unvote(:voter_id => _id)
40
- end
41
-
42
35
  # Get the voted value on a votee
43
36
  #
44
37
  # @param (see #voted?)
@@ -47,38 +40,49 @@ module Mongoid
47
40
  votee = unless options.is_a?(Hash)
48
41
  options
49
42
  else
50
- options[:votee_type].classify.constantize.only(:up_vote_ids, :down_vote_ids).where(
43
+ options[:votee] || options[:votee_type].classify.constantize.only(:up_vote_ids, :down_vote_ids).where(
51
44
  :_id => options[:votee_id]
52
45
  ).first
53
46
  end
54
47
  votee.vote_value(_id)
55
48
  end
56
49
 
50
+ # Cancel the vote on a votee
51
+ #
52
+ # @param [Object] votee the votee to be unvoted
53
+ def unvote(options)
54
+ unless options.is_a?(Hash)
55
+ options = { :votee => options }
56
+ end
57
+ options[:unvote] = true
58
+ options[:revote] = false
59
+ vote(options)
60
+ end
61
+
57
62
  # Vote on a votee
58
63
  #
59
64
  # @param (see #voted?)
60
- # @param [:up, :down] vote_value vote up or vote down
61
- def vote(options, vote_value = nil)
62
- options.symbolize_keys!
63
-
64
- if options.is_a?(Hash)
65
+ # @param [:up, :down] vote_value vote up or vote down, nil to unvote
66
+ def vote(options, value = nil)
67
+ if options.is_a?(Hash)
65
68
  votee = options[:votee]
66
69
  else
67
70
  votee = options
68
- options = { :votee => votee, :value => vote_value }
71
+ options = { :votee => votee, :value => value }
69
72
  end
70
73
 
71
74
  if votee
72
- options[:votee_id] = votee.id
75
+ options[:votee_id] = votee._id
73
76
  votee_class = votee.class
74
77
  else
75
78
  votee_class = options[:votee_type].classify.constantize
76
79
  end
77
80
 
78
- options[:revote] = if options.has_key?(:revote)
79
- !options[:revote].blank?
81
+ if options[:value].nil?
82
+ options[:unvote] = true
83
+ options[:value] = vote_value(options)
80
84
  else
81
- options.has_key?(:new) ? options[:new].blank? : voted?(options)
85
+ options[:revote] = options.has_key?(:revote) ? !options[:revote].blank? : voted?(options)
82
86
  end
83
87
 
84
88
  options[:voter_id] = _id
@@ -86,4 +90,4 @@ module Mongoid
86
90
  ( votee || votee_class ).vote(options)
87
91
  end
88
92
  end
89
- end
93
+ end
metadata CHANGED
@@ -1,13 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: voteable_mongoid
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
5
4
  prerelease:
6
- segments:
7
- - 0
8
- - 4
9
- - 1
10
- version: 0.4.1
5
+ version: 0.4.2
11
6
  platform: ruby
12
7
  authors:
13
8
  - Alex Nguyen
@@ -15,124 +10,64 @@ autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
12
 
18
- date: 2011-02-23 00:00:00 +08:00
13
+ date: 2011-03-01 00:00:00 +08:00
19
14
  default_executable:
20
15
  dependencies:
21
16
  - !ruby/object:Gem::Dependency
22
17
  name: voteable_mongoid
23
- version_requirements: &id001 !ruby/object:Gem::Requirement
18
+ requirement: &id001 !ruby/object:Gem::Requirement
24
19
  none: false
25
20
  requirements:
26
21
  - - ">="
27
22
  - !ruby/object:Gem::Version
28
- hash: 3
29
- segments:
30
- - 0
31
23
  version: "0"
32
- prerelease: false
33
24
  type: :runtime
34
- requirement: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: bson_ext
37
- version_requirements: &id002 !ruby/object:Gem::Requirement
38
- none: false
39
- requirements:
40
- - - ">="
41
- - !ruby/object:Gem::Version
42
- hash: 3
43
- segments:
44
- - 0
45
- version: "0"
46
25
  prerelease: false
47
- type: :development
48
- requirement: *id002
26
+ version_requirements: *id001
49
27
  - !ruby/object:Gem::Dependency
50
28
  name: rspec
51
- version_requirements: &id003 !ruby/object:Gem::Requirement
29
+ requirement: &id002 !ruby/object:Gem::Requirement
52
30
  none: false
53
31
  requirements:
54
32
  - - ">="
55
33
  - !ruby/object:Gem::Version
56
- hash: 3
57
- segments:
58
- - 0
59
34
  version: "0"
60
- prerelease: false
61
35
  type: :development
62
- requirement: *id003
63
- - !ruby/object:Gem::Dependency
64
- name: bson_ext
65
- version_requirements: &id004 !ruby/object:Gem::Requirement
66
- none: false
67
- requirements:
68
- - - ">="
69
- - !ruby/object:Gem::Version
70
- hash: 3
71
- segments:
72
- - 0
73
- version: "0"
74
36
  prerelease: false
75
- type: :development
76
- requirement: *id004
37
+ version_requirements: *id002
77
38
  - !ruby/object:Gem::Dependency
78
39
  name: rspec
79
- version_requirements: &id005 !ruby/object:Gem::Requirement
40
+ requirement: &id003 !ruby/object:Gem::Requirement
80
41
  none: false
81
42
  requirements:
82
43
  - - ">="
83
44
  - !ruby/object:Gem::Version
84
- hash: 3
85
- segments:
86
- - 0
87
45
  version: "0"
88
- prerelease: false
89
46
  type: :development
90
- requirement: *id005
47
+ prerelease: false
48
+ version_requirements: *id003
91
49
  - !ruby/object:Gem::Dependency
92
50
  name: mongoid
93
- version_requirements: &id006 !ruby/object:Gem::Requirement
51
+ requirement: &id004 !ruby/object:Gem::Requirement
94
52
  none: false
95
53
  requirements:
96
54
  - - ~>
97
55
  - !ruby/object:Gem::Version
98
- hash: 7712058
99
- segments:
100
- - 2
101
- - 0
102
- - 0
103
- - rc
104
56
  version: 2.0.0.rc
105
- prerelease: false
106
57
  type: :runtime
107
- requirement: *id006
108
- - !ruby/object:Gem::Dependency
109
- name: bson_ext
110
- version_requirements: &id007 !ruby/object:Gem::Requirement
111
- none: false
112
- requirements:
113
- - - ">="
114
- - !ruby/object:Gem::Version
115
- hash: 3
116
- segments:
117
- - 0
118
- version: "0"
119
58
  prerelease: false
120
- type: :development
121
- requirement: *id007
59
+ version_requirements: *id004
122
60
  - !ruby/object:Gem::Dependency
123
61
  name: rspec
124
- version_requirements: &id008 !ruby/object:Gem::Requirement
62
+ requirement: &id005 !ruby/object:Gem::Requirement
125
63
  none: false
126
64
  requirements:
127
65
  - - ">="
128
66
  - !ruby/object:Gem::Version
129
- hash: 3
130
- segments:
131
- - 0
132
67
  version: "0"
133
- prerelease: false
134
68
  type: :development
135
- requirement: *id008
69
+ prerelease: false
70
+ version_requirements: *id005
136
71
  description: Add Up / Down Voting for Mongoid
137
72
  email: alex@vinova.sg
138
73
  executables: []
@@ -162,23 +97,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
162
97
  requirements:
163
98
  - - ">="
164
99
  - !ruby/object:Gem::Version
165
- hash: 3
166
- segments:
167
- - 0
168
100
  version: "0"
169
101
  required_rubygems_version: !ruby/object:Gem::Requirement
170
102
  none: false
171
103
  requirements:
172
104
  - - ">="
173
105
  - !ruby/object:Gem::Version
174
- hash: 3
175
- segments:
176
- - 0
177
106
  version: "0"
178
107
  requirements: []
179
108
 
180
109
  rubyforge_project:
181
- rubygems_version: 1.5.2
110
+ rubygems_version: 1.5.3
182
111
  signing_key:
183
112
  specification_version: 3
184
113
  summary: Add Up / Down Voting for Mongoid