test_redmine_vz 0.0.24

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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/Gemfile +4 -0
  4. data/README.md +71 -0
  5. data/Rakefile +20 -0
  6. data/config/currency_iso.json +2532 -0
  7. data/doc/CHANGELOG +55 -0
  8. data/doc/LICENSE.txt +339 -0
  9. data/lib/redmine_crm.rb +67 -0
  10. data/lib/redmine_crm/currency.rb +439 -0
  11. data/lib/redmine_crm/currency/formatting.rb +227 -0
  12. data/lib/redmine_crm/currency/heuristics.rb +151 -0
  13. data/lib/redmine_crm/currency/loader.rb +24 -0
  14. data/lib/redmine_crm/helpers/tags_helper.rb +15 -0
  15. data/lib/redmine_crm/helpers/vote_helper.rb +38 -0
  16. data/lib/redmine_crm/liquid/drops/issues_drop.rb +61 -0
  17. data/lib/redmine_crm/liquid/drops/news_drop.rb +45 -0
  18. data/lib/redmine_crm/liquid/drops/projects_drop.rb +78 -0
  19. data/lib/redmine_crm/liquid/drops/users_drop.rb +59 -0
  20. data/lib/redmine_crm/liquid/filters.rb +85 -0
  21. data/lib/redmine_crm/money_helper.rb +67 -0
  22. data/lib/redmine_crm/rcrm_acts_as_taggable.rb +342 -0
  23. data/lib/redmine_crm/rcrm_acts_as_viewed.rb +287 -0
  24. data/lib/redmine_crm/rcrm_acts_as_votable.rb +79 -0
  25. data/lib/redmine_crm/rcrm_acts_as_voter.rb +27 -0
  26. data/lib/redmine_crm/tag.rb +81 -0
  27. data/lib/redmine_crm/tag_list.rb +112 -0
  28. data/lib/redmine_crm/tagging.rb +20 -0
  29. data/lib/redmine_crm/version.rb +3 -0
  30. data/lib/redmine_crm/votable.rb +334 -0
  31. data/lib/redmine_crm/vote.rb +30 -0
  32. data/lib/redmine_crm/voter.rb +136 -0
  33. data/redmine_crm.gemspec +22 -0
  34. data/test/acts_as_taggable_test.rb +384 -0
  35. data/test/currency_test.rb +292 -0
  36. data/test/database.yml +17 -0
  37. data/test/fixtures/issue.rb +14 -0
  38. data/test/fixtures/issues.yml +12 -0
  39. data/test/fixtures/taggings.yml +32 -0
  40. data/test/fixtures/tags.yml +11 -0
  41. data/test/fixtures/user.rb +7 -0
  42. data/test/fixtures/users.yml +5 -0
  43. data/test/fixtures/votable_caches.yml +2 -0
  44. data/test/fixtures/votables.yml +4 -0
  45. data/test/fixtures/vote_classes.rb +54 -0
  46. data/test/fixtures/voters.yml +6 -0
  47. data/test/liquid_test.rb +80 -0
  48. data/test/money_helper_test.rb +12 -0
  49. data/test/schema.rb +100 -0
  50. data/test/tag_test.rb +63 -0
  51. data/test/tagging_test.rb +14 -0
  52. data/test/tags_helper_test.rb +29 -0
  53. data/test/test_helper.rb +118 -0
  54. data/test/viewed_test.rb +45 -0
  55. data/test/votable_model_test.rb +478 -0
  56. data/test/votable_test.rb +17 -0
  57. data/test/vote_helper_test.rb +28 -0
  58. data/test/voter_model_test.rb +296 -0
  59. metadata +141 -0
@@ -0,0 +1,112 @@
1
+ module RedmineCrm
2
+ class TagList < Array
3
+ cattr_accessor :delimiter
4
+ self.delimiter = ','
5
+
6
+ def initialize(*args)
7
+ add(*args)
8
+ end
9
+
10
+ # Add tags to the tag_list. Duplicate or blank tags will be ignored.
11
+ #
12
+ # tag_list.add("Fun", "Happy")
13
+ #
14
+ # Use the <tt>:parse</tt> option to add an unparsed tag string.
15
+ #
16
+ # tag_list.add("Fun, Happy", :parse => true)
17
+ def add(*names)
18
+ extract_and_apply_options!(names)
19
+ concat(names)
20
+ clean!
21
+ self
22
+ end
23
+
24
+ # Remove specific tags from the tag_list.
25
+ #
26
+ # tag_list.remove("Sad", "Lonely")
27
+ #
28
+ # Like #add, the <tt>:parse</tt> option can be used to remove multiple tags in a string.
29
+ #
30
+ # tag_list.remove("Sad, Lonely", :parse => true)
31
+ def remove(*names)
32
+ extract_and_apply_options!(names)
33
+ delete_if { |name| names.include?(name) }
34
+ self
35
+ end
36
+
37
+ # Toggle the presence of the given tags.
38
+ # If a tag is already in the list it is removed, otherwise it is added.
39
+ def toggle(*names)
40
+ extract_and_apply_options!(names)
41
+
42
+ names.each do |name|
43
+ include?(name) ? delete(name) : push(name)
44
+ end
45
+
46
+ clean!
47
+ self
48
+ end
49
+
50
+ # Transform the tag_list into a tag string suitable for edting in a form.
51
+ # The tags are joined with <tt>TagList.delimiter</tt> and quoted if necessary.
52
+ #
53
+ # tag_list = TagList.new("Round", "Square,Cube")
54
+ # tag_list.to_s # 'Round, "Square,Cube"'
55
+ def to_s
56
+ clean!
57
+
58
+ map do |name|
59
+ name.include?(delimiter) ? "\"#{name}\"" : name
60
+ end.join(delimiter.ends_with?(" ") ? delimiter : "#{delimiter} ")
61
+ end
62
+
63
+ private
64
+ # Remove whitespace, duplicates, and blanks.
65
+ def clean!
66
+ reject!(&:blank?)
67
+ map!(&:strip)
68
+ uniq!
69
+ end
70
+
71
+ def extract_and_apply_options!(args)
72
+ options = args.last.is_a?(Hash) ? args.pop : {}
73
+ options.assert_valid_keys :parse
74
+
75
+ if options[:parse]
76
+ args.map! { |a| self.class.from(a) }
77
+ end
78
+
79
+ args.flatten!
80
+ end
81
+
82
+ class << self
83
+ # Returns a new TagList using the given tag string.
84
+ #
85
+ # tag_list = TagList.from("One , Two, Three")
86
+ # tag_list # ["One", "Two", "Three"]
87
+ def from(source)
88
+ tag_list = new
89
+
90
+ case source
91
+ when Array
92
+ tag_list.add(source)
93
+ else
94
+ string = source.to_s.dup
95
+
96
+ # Parse the quoted tags
97
+ [
98
+ /\s*#{delimiter}\s*(['"])(.*?)\1\s*/,
99
+ /^\s*(['"])(.*?)\1\s*#{delimiter}?/
100
+ ].each do |re|
101
+ string.gsub!(re) { tag_list << $2; "" }
102
+ end
103
+
104
+ tag_list.add(string.split(delimiter))
105
+ end
106
+
107
+ tag_list
108
+ end
109
+ end
110
+ end
111
+
112
+ end
@@ -0,0 +1,20 @@
1
+ module RedmineCrm
2
+
3
+ class Tagging < ActiveRecord::Base #:nodoc:
4
+ belongs_to :tag
5
+ belongs_to :taggable, :polymorphic => true
6
+
7
+ after_destroy :destroy_tag_if_unused
8
+
9
+ private
10
+
11
+ def destroy_tag_if_unused
12
+ if Tag.destroy_unused
13
+ if tag.taggings.count.zero?
14
+ tag.destroy
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,3 @@
1
+ module RedmineCrm
2
+ VERSION = "0.0.24"
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