redmine_crm 0.0.8 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,3 @@
1
1
  module RedmineCrm
2
- VERSION = "0.0.8"
2
+ VERSION = "0.0.10"
3
3
  end
@@ -0,0 +1,334 @@
1
+ require 'redmine_crm/helpers/vote_helper'
2
+
3
+ module RedmineCrm
4
+ module ActsAsVotable
5
+ module Votable
6
+
7
+ include ActsAsVotable::Helpers::Words
8
+
9
+ def self.included base
10
+
11
+ # allow the user to define these himself
12
+ aliases = {
13
+
14
+ :vote_up => [
15
+ :up_by, :upvote_by, :like_by, :liked_by,
16
+ :up_from, :upvote_from, :upvote_by, :like_from, :liked_from, :vote_from
17
+ ],
18
+
19
+ :vote_down => [
20
+ :down_by, :downvote_by, :dislike_by, :disliked_by,
21
+ :down_from, :downvote_from, :downvote_by, :dislike_by, :disliked_by
22
+ ],
23
+
24
+ :get_up_votes => [
25
+ :get_true_votes, :get_ups, :get_upvotes, :get_likes, :get_positives, :get_for_votes,
26
+ ],
27
+
28
+ :get_down_votes => [
29
+ :get_false_votes, :get_downs, :get_downvotes, :get_dislikes, :get_negatives
30
+ ],
31
+ :unvote_by => [
32
+ :unvote_up, :unvote_down, :unliked_by, :undisliked_by
33
+ ]
34
+ }
35
+
36
+ base.class_eval do
37
+ has_many :votes_for, :class_name => 'RedmineCrm::ActsAsVotable::Vote', :as => :votable, :dependent => :destroy do
38
+ def voters
39
+ includes(:voter).map(&:voter)
40
+ end
41
+ end
42
+
43
+ aliases.each do |method, links|
44
+ links.each do |new_method|
45
+ alias_method(new_method, method)
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+
52
+ attr_accessor :vote_registered
53
+
54
+ def vote_registered?
55
+ return self.vote_registered
56
+ end
57
+
58
+ def default_conditions
59
+ {
60
+ :votable_id => self.id,
61
+ :votable_type => self.class.base_class.name.to_s
62
+ }
63
+ end
64
+
65
+ # voting
66
+ def vote_by args = {}
67
+
68
+ options = {
69
+ :vote => true,
70
+ :vote_scope => nil
71
+ }.merge(args)
72
+
73
+ self.vote_registered = false
74
+
75
+ if options[:voter].nil?
76
+ return false
77
+ end
78
+
79
+ # find the vote
80
+ _votes_ = find_votes_for({
81
+ :voter_id => options[:voter].id,
82
+ :vote_scope => options[:vote_scope],
83
+ :voter_type => options[:voter].class.base_class.name
84
+ })
85
+
86
+ if _votes_.count == 0 or options[:duplicate]
87
+ # this voter has never voted
88
+ vote = RedmineCrm::ActsAsVotable::Vote.new(
89
+ :votable => self,
90
+ :voter => options[:voter],
91
+ :vote_scope => options[:vote_scope]
92
+ )
93
+ else
94
+ # this voter is potentially changing his vote
95
+ vote = _votes_.last
96
+ end
97
+
98
+ last_update = vote.updated_at
99
+
100
+ vote.vote_flag = votable_words.meaning_of(options[:vote])
101
+
102
+ #Allowing for a vote_weight to be associated with every vote. Could change with every voter object
103
+ vote.vote_weight = (options[:vote_weight].to_i if options[:vote_weight].present?) || 1
104
+
105
+ if vote.save
106
+ self.vote_registered = true if last_update != vote.updated_at
107
+ update_cached_votes options[:vote_scope]
108
+ return true
109
+ else
110
+ self.vote_registered = false
111
+ return false
112
+ end
113
+
114
+ end
115
+
116
+ def unvote args = {}
117
+ return false if args[:voter].nil?
118
+ _votes_ = find_votes_for(:voter_id => args[:voter].id, :vote_scope => args[:vote_scope], :voter_type => args[:voter].class.base_class.name)
119
+
120
+ return true if _votes_.size == 0
121
+ _votes_.each(&:destroy)
122
+ update_cached_votes args[:vote_scope]
123
+ self.vote_registered = false if votes_for.count == 0
124
+ return true
125
+ end
126
+
127
+ def vote_up voter, options={}
128
+ self.vote_by :voter => voter, :vote => true, :vote_scope => options[:vote_scope], :vote_weight => options[:vote_weight]
129
+ end
130
+
131
+ def vote_down voter, options={}
132
+ self.vote_by :voter => voter, :vote => false, :vote_scope => options[:vote_scope], :vote_weight => options[:vote_weight]
133
+ end
134
+
135
+ def unvote_by voter, options = {}
136
+ self.unvote :voter => voter, :vote_scope => options[:vote_scope] #Does not need vote_weight since the votes_for are anyway getting destroyed
137
+ end
138
+
139
+ def scope_cache_field field, vote_scope
140
+ return field if vote_scope.nil?
141
+
142
+ case field
143
+ when :cached_votes_total=
144
+ "cached_scoped_#{vote_scope}_votes_total="
145
+ when :cached_votes_total
146
+ "cached_scoped_#{vote_scope}_votes_total"
147
+ when :cached_votes_up=
148
+ "cached_scoped_#{vote_scope}_votes_up="
149
+ when :cached_votes_up
150
+ "cached_scoped_#{vote_scope}_votes_up"
151
+ when :cached_votes_down=
152
+ "cached_scoped_#{vote_scope}_votes_down="
153
+ when :cached_votes_down
154
+ "cached_scoped_#{vote_scope}_votes_down"
155
+ when :cached_votes_score=
156
+ "cached_scoped_#{vote_scope}_votes_score="
157
+ when :cached_votes_score
158
+ "cached_scoped_#{vote_scope}_votes_score"
159
+ when :cached_weighted_total
160
+ "cached_weighted_#{vote_scope}_total"
161
+ when :cached_weighted_total=
162
+ "cached_weighted_#{vote_scope}_total="
163
+ when :cached_weighted_score
164
+ "cached_weighted_#{vote_scope}_score"
165
+ when :cached_weighted_score=
166
+ "cached_weighted_#{vote_scope}_score="
167
+ when :cached_weighted_average
168
+ "cached_weighted_#{vote_scope}_average"
169
+ when :cached_weighted_average=
170
+ "cached_weighted_#{vote_scope}_average="
171
+ end
172
+ end
173
+
174
+ # caching
175
+ def update_cached_votes vote_scope = nil
176
+
177
+ updates = {}
178
+
179
+ if self.respond_to?(:cached_votes_total=)
180
+ updates[:cached_votes_total] = count_votes_total(true)
181
+ end
182
+
183
+ if self.respond_to?(:cached_votes_up=)
184
+ updates[:cached_votes_up] = count_votes_up(true)
185
+ end
186
+
187
+ if self.respond_to?(:cached_votes_down=)
188
+ updates[:cached_votes_down] = count_votes_down(true)
189
+ end
190
+
191
+ if self.respond_to?(:cached_votes_score=)
192
+ updates[:cached_votes_score] = (
193
+ (updates[:cached_votes_up] || count_votes_up(true)) -
194
+ (updates[:cached_votes_down] || count_votes_down(true))
195
+ )
196
+ end
197
+
198
+ if self.respond_to?(:cached_weighted_total=)
199
+ updates[:cached_weighted_total] = weighted_total(true)
200
+ end
201
+
202
+ if self.respond_to?(:cached_weighted_score=)
203
+ updates[:cached_weighted_score] = weighted_score(true)
204
+ end
205
+
206
+ if self.respond_to?(:cached_weighted_average=)
207
+ updates[:cached_weighted_average] = weighted_average(true)
208
+ end
209
+
210
+ if vote_scope
211
+ if self.respond_to?(scope_cache_field :cached_votes_total=, vote_scope)
212
+ updates[scope_cache_field :cached_votes_total, vote_scope] = count_votes_total(true, vote_scope)
213
+ end
214
+
215
+ if self.respond_to?(scope_cache_field :cached_votes_up=, vote_scope)
216
+ updates[scope_cache_field :cached_votes_up, vote_scope] = count_votes_up(true, vote_scope)
217
+ end
218
+
219
+ if self.respond_to?(scope_cache_field :cached_votes_down=, vote_scope)
220
+ updates[scope_cache_field :cached_votes_down, vote_scope] = count_votes_down(true, vote_scope)
221
+ end
222
+
223
+ if self.respond_to?(scope_cache_field :cached_weighted_total=, vote_scope)
224
+ updates[scope_cache_field :cached_weighted_total, vote_scope] = weighted_total(true, vote_scope)
225
+ end
226
+
227
+ if self.respond_to?(scope_cache_field :cached_weighted_score=, vote_scope)
228
+ updates[scope_cache_field :cached_weighted_score, vote_scope] = weighted_score(true, vote_scope)
229
+ end
230
+
231
+ if self.respond_to?(scope_cache_field :cached_votes_score=, vote_scope)
232
+ updates[scope_cache_field :cached_votes_score, vote_scope] = (
233
+ (updates[scope_cache_field :cached_votes_up, vote_scope] || count_votes_up(true, vote_scope)) -
234
+ (updates[scope_cache_field :cached_votes_down, vote_scope] || count_votes_down(true, vote_scope))
235
+ )
236
+ end
237
+
238
+ if self.respond_to?(scope_cache_field :cached_weighted_average=, vote_scope)
239
+ updates[scope_cache_field :cached_weighted_average, vote_scope] = weighted_average(true, vote_scope)
240
+ end
241
+ end
242
+
243
+ if (::ActiveRecord::VERSION::MAJOR == 3) && (::ActiveRecord::VERSION::MINOR != 0)
244
+ self.update_attributes(updates, :without_protection => true) if updates.size > 0
245
+ else
246
+ self.update_attributes(updates) if updates.size > 0
247
+ end
248
+
249
+ end
250
+
251
+
252
+ # results
253
+ def find_votes_for extra_conditions = {}
254
+ votes_for.where(extra_conditions)
255
+ end
256
+
257
+ def get_up_votes options={}
258
+ vote_scope_hash = scope_or_empty_hash(options[:vote_scope])
259
+ find_votes_for({:vote_flag => true}.merge(vote_scope_hash))
260
+ end
261
+
262
+ def get_down_votes options={}
263
+ vote_scope_hash = scope_or_empty_hash(options[:vote_scope])
264
+ find_votes_for({:vote_flag => false}.merge(vote_scope_hash))
265
+ end
266
+
267
+
268
+ # counting
269
+ def count_votes_total skip_cache = false, vote_scope = nil
270
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_votes_total, vote_scope)
271
+ return self.send(scope_cache_field :cached_votes_total, vote_scope)
272
+ end
273
+ find_votes_for(scope_or_empty_hash(vote_scope)).count
274
+ end
275
+
276
+ def count_votes_up skip_cache = false, vote_scope = nil
277
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_votes_up, vote_scope)
278
+ return self.send(scope_cache_field :cached_votes_up, vote_scope)
279
+ end
280
+ get_up_votes(:vote_scope => vote_scope).count
281
+ end
282
+
283
+ def count_votes_down skip_cache = false, vote_scope = nil
284
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_votes_down, vote_scope)
285
+ return self.send(scope_cache_field :cached_votes_down, vote_scope)
286
+ end
287
+ get_down_votes(:vote_scope => vote_scope).count
288
+ end
289
+
290
+ def weighted_total skip_cache = false, vote_scope = nil
291
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_weighted_total, vote_scope)
292
+ return self.send(scope_cache_field :cached_weighted_total, vote_scope)
293
+ end
294
+ ups = get_up_votes(:vote_scope => vote_scope).sum(:vote_weight)
295
+ downs = get_down_votes(:vote_scope => vote_scope).sum(:vote_weight)
296
+ ups + downs
297
+ end
298
+
299
+ def weighted_score skip_cache = false, vote_scope = nil
300
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_weighted_score, vote_scope)
301
+ return self.send(scope_cache_field :cached_weighted_score, vote_scope)
302
+ end
303
+ ups = get_up_votes(:vote_scope => vote_scope).sum(:vote_weight)
304
+ downs = get_down_votes(:vote_scope => vote_scope).sum(:vote_weight)
305
+ ups - downs
306
+ end
307
+
308
+ def weighted_average skip_cache = false, vote_scope = nil
309
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_weighted_average, vote_scope)
310
+ return self.send(scope_cache_field :cached_weighted_average, vote_scope)
311
+ end
312
+
313
+ count = count_votes_total(skip_cache, vote_scope).to_i
314
+ if count > 0
315
+ weighted_score(skip_cache, vote_scope).to_f / count
316
+ else
317
+ 0.0
318
+ end
319
+ end
320
+
321
+ # voters
322
+ def voted_on_by? voter
323
+ votes = find_votes_for :voter_id => voter.id, :voter_type => voter.class.base_class.name
324
+ votes.count > 0
325
+ end
326
+
327
+ private
328
+
329
+ def scope_or_empty_hash(vote_scope)
330
+ vote_scope ? { :vote_scope => vote_scope } : {}
331
+ end
332
+ end
333
+ end
334
+ end
@@ -0,0 +1,30 @@
1
+ require 'redmine_crm/helpers/vote_helper'
2
+
3
+ module RedmineCrm
4
+ module ActsAsVotable
5
+ class Vote < ActiveRecord::Base
6
+
7
+ include Helpers::Words
8
+
9
+ if defined?(ProtectedAttributes) || ::ActiveRecord::VERSION::MAJOR < 4
10
+ attr_accessible :votable_id, :votable_type,
11
+ :voter_id, :voter_type,
12
+ :votable, :voter,
13
+ :vote_flag, :vote_scope
14
+ end
15
+
16
+ belongs_to :votable, :polymorphic => true
17
+ belongs_to :voter, :polymorphic => true
18
+
19
+ scope :up, lambda{ where(:vote_flag => true) }
20
+ scope :down, lambda{ where(:vote_flag => false) }
21
+ scope :for_type, lambda{ |klass| where(:votable_type => klass) }
22
+ scope :by_type, lambda{ |klass| where(:voter_type => klass) }
23
+
24
+ validates_presence_of :votable_id
25
+ validates_presence_of :voter_id
26
+
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,136 @@
1
+ module RedmineCrm
2
+ module ActsAsVotable
3
+ module Voter
4
+
5
+ def self.included(base)
6
+
7
+ # allow user to define these
8
+ aliases = {
9
+ :vote_up_for => [:likes, :upvotes, :up_votes],
10
+ :vote_down_for => [:dislikes, :downvotes, :down_votes],
11
+ :unvote_for => [:unlike, :undislike],
12
+ :voted_on? => [:voted_for?],
13
+ :voted_up_on? => [:voted_up_for?, :liked?],
14
+ :voted_down_on? => [:voted_down_for?, :disliked?],
15
+ :voted_as_when_voting_on => [:voted_as_when_voted_on, :voted_as_when_voting_for, :voted_as_when_voted_for],
16
+ :find_up_voted_items => [:find_liked_items],
17
+ :find_down_voted_items => [:find_disliked_items]
18
+ }
19
+
20
+ base.class_eval do
21
+
22
+ has_many :votes, :class_name => 'RedmineCrm::ActsAsVotable::Vote', :as => :voter, :dependent => :destroy do
23
+ def votables
24
+ includes(:votable).map(&:votable)
25
+ end
26
+ end
27
+
28
+ aliases.each do |method, links|
29
+ links.each do |new_method|
30
+ alias_method(new_method, method)
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
38
+ # voting
39
+ def vote args
40
+ args[:votable].vote_by args.merge({:voter => self})
41
+ end
42
+
43
+ def vote_up_for model=nil, args={}
44
+ vote :votable => model, :vote_scope => args[:vote_scope], :vote => true
45
+ end
46
+
47
+ def vote_down_for model=nil, args={}
48
+ vote :votable => model, :vote_scope => args[:vote_scope], :vote => false
49
+ end
50
+
51
+ def unvote_for model, args={}
52
+ model.unvote :voter => self, :vote_scope => args[:vote_scope]
53
+ end
54
+
55
+ # results
56
+ def voted_on? votable, args={}
57
+ votes = find_votes(:votable_id => votable.id, :votable_type => votable.class.base_class.name,
58
+ :vote_scope => args[:vote_scope])
59
+ votes.size > 0
60
+ end
61
+
62
+ def voted_up_on? votable, args={}
63
+ votes = find_votes(:votable_id => votable.id, :votable_type => votable.class.base_class.name,
64
+ :vote_scope => args[:vote_scope], :vote_flag => true)
65
+ votes.size > 0
66
+ end
67
+
68
+ def voted_down_on? votable, args={}
69
+ votes = find_votes(:votable_id => votable.id, :votable_type => votable.class.base_class.name,
70
+ :vote_scope => args[:vote_scope], :vote_flag => false)
71
+ votes.size > 0
72
+ end
73
+
74
+ def voted_as_when_voting_on votable, args={}
75
+ vote = find_votes(:votable_id => votable.id, :votable_type => votable.class.base_class.name,
76
+ :vote_scope => args[:vote_scope]).select(:vote_flag).last
77
+ return nil unless vote
78
+ return vote.vote_flag
79
+ end
80
+
81
+ def find_votes extra_conditions = {}
82
+ votes.where(extra_conditions)
83
+ end
84
+
85
+ def find_up_votes args={}
86
+ find_votes :vote_flag => true, :vote_scope => args[:vote_scope]
87
+ end
88
+
89
+ def find_down_votes args={}
90
+ find_votes :vote_flag => false, :vote_scope => args[:vote_scope]
91
+ end
92
+
93
+ def find_votes_for_class klass, extra_conditions = {}
94
+ find_votes extra_conditions.merge({:votable_type => klass.name})
95
+ end
96
+
97
+ def find_up_votes_for_class klass, args={}
98
+ find_votes_for_class klass, :vote_flag => true, :vote_scope => args[:vote_scope]
99
+ end
100
+
101
+ def find_down_votes_for_class klass, args={}
102
+ find_votes_for_class klass, :vote_flag => false, :vote_scope => args[:vote_scope]
103
+ end
104
+
105
+ # Including polymporphic relations for eager loading
106
+ def include_objects
107
+ ActsAsVotable::Vote.includes(:votable)
108
+ end
109
+
110
+ def find_voted_items extra_conditions = {}
111
+ options = extra_conditions.merge :voter_id => id, :voter_type => self.class.base_class.name
112
+ include_objects.where(options).collect(&:votable)
113
+ end
114
+
115
+ def find_up_voted_items extra_conditions = {}
116
+ find_voted_items extra_conditions.merge(:vote_flag => true)
117
+ end
118
+
119
+ def find_down_voted_items extra_conditions = {}
120
+ find_voted_items extra_conditions.merge(:vote_flag => false)
121
+ end
122
+
123
+ def get_voted klass, extra_conditions = {}
124
+ klass.joins(:votes_for).merge find_votes(extra_conditions)
125
+ end
126
+
127
+ def get_up_voted klass
128
+ klass.joins(:votes_for).merge find_up_votes
129
+ end
130
+
131
+ def get_down_voted klass
132
+ klass.joins(:votes_for).merge find_down_votes
133
+ end
134
+ end
135
+ end
136
+ end