voteable_mongoid 0.4.1 → 0.4.2

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.
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