redmineup 1.0.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 (118) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/Gemfile +4 -0
  4. data/README.md +204 -0
  5. data/Rakefile +11 -0
  6. data/app/controllers/redmineup_controller.rb +26 -0
  7. data/app/views/redmine_crm/redmineup_calendar/_calendar.html.erb +34 -0
  8. data/app/views/redmineup/_money.html.erb +44 -0
  9. data/app/views/redmineup/settings.html.erb +10 -0
  10. data/bitbucket-pipelines.yml +50 -0
  11. data/config/currency_iso.json +2544 -0
  12. data/config/locales/cs.yml +13 -0
  13. data/config/locales/de.yml +13 -0
  14. data/config/locales/en.yml +13 -0
  15. data/config/locales/es.yml +13 -0
  16. data/config/locales/ru.yml +13 -0
  17. data/config/routes.rb +5 -0
  18. data/doc/CHANGELOG +14 -0
  19. data/doc/LICENSE.txt +339 -0
  20. data/lib/redmineup/acts_as_draftable/draft.rb +40 -0
  21. data/lib/redmineup/acts_as_draftable/up_acts_as_draftable.rb +172 -0
  22. data/lib/redmineup/acts_as_list/list.rb +282 -0
  23. data/lib/redmineup/acts_as_priceable/up_acts_as_priceable.rb +33 -0
  24. data/lib/redmineup/acts_as_taggable/tag.rb +81 -0
  25. data/lib/redmineup/acts_as_taggable/tag_list.rb +111 -0
  26. data/lib/redmineup/acts_as_taggable/tagging.rb +16 -0
  27. data/lib/redmineup/acts_as_taggable/up_acts_as_taggable.rb +357 -0
  28. data/lib/redmineup/acts_as_viewed/up_acts_as_viewed.rb +274 -0
  29. data/lib/redmineup/acts_as_votable/up_acts_as_votable.rb +80 -0
  30. data/lib/redmineup/acts_as_votable/up_acts_as_voter.rb +20 -0
  31. data/lib/redmineup/acts_as_votable/votable.rb +323 -0
  32. data/lib/redmineup/acts_as_votable/vote.rb +28 -0
  33. data/lib/redmineup/acts_as_votable/voter.rb +131 -0
  34. data/lib/redmineup/assets_manager.rb +43 -0
  35. data/lib/redmineup/colors_helper.rb +192 -0
  36. data/lib/redmineup/compatibility/application_controller_patch.rb +33 -0
  37. data/lib/redmineup/compatibility/routing_mapper_patch.rb +25 -0
  38. data/lib/redmineup/currency/formatting.rb +224 -0
  39. data/lib/redmineup/currency/heuristics.rb +151 -0
  40. data/lib/redmineup/currency/loader.rb +23 -0
  41. data/lib/redmineup/currency.rb +450 -0
  42. data/lib/redmineup/engine.rb +4 -0
  43. data/lib/redmineup/helpers/external_assets_helper.rb +20 -0
  44. data/lib/redmineup/helpers/form_tag_helper.rb +88 -0
  45. data/lib/redmineup/helpers/rup_calendar_helper.rb +146 -0
  46. data/lib/redmineup/helpers/tags_helper.rb +13 -0
  47. data/lib/redmineup/helpers/vote_helper.rb +35 -0
  48. data/lib/redmineup/hooks/views_layouts_hook.rb +11 -0
  49. data/lib/redmineup/liquid/drops/attachment_drop.rb +47 -0
  50. data/lib/redmineup/liquid/drops/issue_relations_drop.rb +41 -0
  51. data/lib/redmineup/liquid/drops/issues_drop.rb +217 -0
  52. data/lib/redmineup/liquid/drops/news_drop.rb +54 -0
  53. data/lib/redmineup/liquid/drops/projects_drop.rb +86 -0
  54. data/lib/redmineup/liquid/drops/time_entries_drop.rb +65 -0
  55. data/lib/redmineup/liquid/drops/users_drop.rb +68 -0
  56. data/lib/redmineup/liquid/filters/arrays.rb +254 -0
  57. data/lib/redmineup/liquid/filters/base.rb +249 -0
  58. data/lib/redmineup/liquid/filters/colors.rb +31 -0
  59. data/lib/redmineup/money_helper.rb +66 -0
  60. data/lib/redmineup/patches/liquid_patch.rb +33 -0
  61. data/lib/redmineup/settings/money.rb +46 -0
  62. data/lib/redmineup/settings.rb +53 -0
  63. data/lib/redmineup/version.rb +3 -0
  64. data/lib/redmineup.rb +108 -0
  65. data/redmineup.gemspec +29 -0
  66. data/test/acts_as_draftable/draft_test.rb +29 -0
  67. data/test/acts_as_draftable/rup_acts_as_draftable_test.rb +178 -0
  68. data/test/acts_as_taggable/rup_acts_as_taggable_test.rb +350 -0
  69. data/test/acts_as_taggable/tag_list_test.rb +34 -0
  70. data/test/acts_as_taggable/tag_test.rb +72 -0
  71. data/test/acts_as_taggable/tagging_test.rb +15 -0
  72. data/test/acts_as_viewed/rup_acts_as_viewed_test.rb +47 -0
  73. data/test/acts_as_votable/rup_acts_as_votable_test.rb +19 -0
  74. data/test/acts_as_votable/rup_acts_as_voter_test.rb +14 -0
  75. data/test/acts_as_votable/votable_test.rb +507 -0
  76. data/test/acts_as_votable/voter_test.rb +296 -0
  77. data/test/currency_test.rb +292 -0
  78. data/test/database.yml +17 -0
  79. data/test/fixtures/attachments.yml +14 -0
  80. data/test/fixtures/issues.yml +24 -0
  81. data/test/fixtures/news.yml +8 -0
  82. data/test/fixtures/projects.yml +10 -0
  83. data/test/fixtures/taggings.yml +32 -0
  84. data/test/fixtures/tags.yml +11 -0
  85. data/test/fixtures/users.yml +9 -0
  86. data/test/fixtures/votable_caches.yml +2 -0
  87. data/test/fixtures/votables.yml +4 -0
  88. data/test/fixtures/voters.yml +6 -0
  89. data/test/liquid/drops/attachment_drop_test.rb +15 -0
  90. data/test/liquid/drops/issue_relations_drop_test.rb +24 -0
  91. data/test/liquid/drops/issues_drop_test.rb +38 -0
  92. data/test/liquid/drops/news_drop_test.rb +38 -0
  93. data/test/liquid/drops/projects_drop_test.rb +44 -0
  94. data/test/liquid/drops/uses_drop_test.rb +36 -0
  95. data/test/liquid/filters/arrays_filter_test.rb +31 -0
  96. data/test/liquid/filters/base_filter_test.rb +67 -0
  97. data/test/liquid/filters/colors_filter_test.rb +33 -0
  98. data/test/liquid/liquid_helper.rb +34 -0
  99. data/test/models/attachment.rb +3 -0
  100. data/test/models/issue.rb +21 -0
  101. data/test/models/issue_relation.rb +10 -0
  102. data/test/models/news.rb +3 -0
  103. data/test/models/project.rb +8 -0
  104. data/test/models/user.rb +11 -0
  105. data/test/models/vote_classes.rb +33 -0
  106. data/test/money_helper_test.rb +12 -0
  107. data/test/schema.rb +144 -0
  108. data/test/tags_helper_test.rb +29 -0
  109. data/test/test_helper.rb +66 -0
  110. data/test/vote_helper_test.rb +28 -0
  111. data/vendor/assets/images/money.png +0 -0
  112. data/vendor/assets/images/vcard.png +0 -0
  113. data/vendor/assets/javascripts/Chart.bundle.min.js +16 -0
  114. data/vendor/assets/javascripts/select2.js +2 -0
  115. data/vendor/assets/javascripts/select2_helpers.js +192 -0
  116. data/vendor/assets/stylesheets/money.css +96 -0
  117. data/vendor/assets/stylesheets/select2.css +424 -0
  118. metadata +295 -0
@@ -0,0 +1,80 @@
1
+ require 'active_record'
2
+
3
+ module Redmineup
4
+ module ActsAsVotable #:nodoc:
5
+ module Votable #:nodoc:
6
+ def votable?
7
+ false
8
+ end
9
+
10
+ def up_acts_as_votable
11
+ require 'redmineup/acts_as_votable/votable'
12
+ include Redmineup::ActsAsVotable::Votable
13
+
14
+ class_eval do
15
+ def self.votable?
16
+ true
17
+ end
18
+ end
19
+ end
20
+
21
+ def create_index(table_name, column_name)
22
+ return if self.connection.index_exists?(table_name, column_name)
23
+
24
+ self.connection.add_index table_name, column_name
25
+ end
26
+
27
+ def create_votable_table(options = {})
28
+ votes_name_table = options[:votes] || :votes
29
+
30
+ if !self.connection.table_exists?(votes_name_table)
31
+ self.connection.create_table(votes_name_table) do |t|
32
+ t.references :votable, :polymorphic => true
33
+ t.references :voter, :polymorphic => true
34
+
35
+ t.column :vote_flag, :boolean
36
+ t.column :vote_scope, :string
37
+ t.column :vote_weight, :integer
38
+ t.column :vote_ip, :string
39
+
40
+ t.timestamps
41
+ end
42
+ else #if table exists - check existence of separate columns
43
+ fields = {
44
+ :votable_id => :integer,
45
+ :votable_type => :string,
46
+ :voter_id => :integer,
47
+ :voter_type => :string,
48
+ :vote_flag => :boolean,
49
+ :vote_scope => :string,
50
+ :vote_weight => :integer,
51
+ :vote_ip => :string
52
+ }
53
+ fields.each do |name, type|
54
+ if !self.connection.column_exists?(votes_name_table, name)
55
+ self.connection.add_column(votes_name_table, name, type)
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ if ::ActiveRecord::VERSION::MAJOR < 4
62
+ create_index votes_name_table, [:votable_id, :votable_type, :vote_ip]
63
+ create_index votes_name_table, [:voter_id, :voter_type, :vote_ip]
64
+ end
65
+
66
+ create_index votes_name_table, [:voter_id, :voter_type, :vote_scope]
67
+ create_index votes_name_table, [:votable_id, :votable_type, :vote_scope]
68
+ create_index votes_name_table, [:voter_type, :vote_scope, :vote_ip]
69
+ create_index votes_name_table, [:votable_type, :vote_scope, :vote_ip]
70
+ end
71
+
72
+ def drop_votable_table(options = {})
73
+ votes_name_table = options[:votes] || :votes
74
+ if self.connection.table_exists?(votes_name_table)
75
+ self.connection.drop_table votes_name_table
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,20 @@
1
+ module Redmineup
2
+ module ActsAsVotable
3
+ module Voter
4
+ def voter?
5
+ false
6
+ end
7
+
8
+ def up_acts_as_voter(*args)
9
+ require 'redmineup/acts_as_votable/voter'
10
+ include Redmineup::ActsAsVotable::Voter
11
+
12
+ class_eval do
13
+ def self.voter?
14
+ true
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,323 @@
1
+ require 'redmineup/helpers/vote_helper'
2
+
3
+ module Redmineup
4
+ module ActsAsVotable
5
+ module Votable
6
+ include ActsAsVotable::Helpers::Words
7
+
8
+ def self.included(base)
9
+ # allow the user to define these himself
10
+ aliases = {
11
+
12
+ :vote_up => [
13
+ :up_by, :upvote_by, :like_by, :liked_by,
14
+ :up_from, :upvote_from, :upvote_by, :like_from, :liked_from, :vote_from
15
+ ],
16
+
17
+ :vote_down => [
18
+ :down_by, :downvote_by, :dislike_by, :disliked_by,
19
+ :down_from, :downvote_from, :downvote_by, :dislike_by, :disliked_by
20
+ ],
21
+
22
+ :get_up_votes => [
23
+ :get_true_votes, :get_ups, :get_upvotes, :get_likes, :get_positives, :get_for_votes,
24
+ ],
25
+
26
+ :get_down_votes => [
27
+ :get_false_votes, :get_downs, :get_downvotes, :get_dislikes, :get_negatives
28
+ ],
29
+ :unvote_by => [
30
+ :unvote_up, :unvote_down, :unliked_by, :undisliked_by
31
+ ]
32
+ }
33
+
34
+ base.class_eval do
35
+ has_many :votes_for, :class_name => 'Redmineup::ActsAsVotable::Vote', :as => :votable, :dependent => :destroy do
36
+ def voters
37
+ includes(:voter).map(&:voter)
38
+ end
39
+ end
40
+
41
+ aliases.each do |method, links|
42
+ links.each do |new_method|
43
+ alias_method(new_method, method)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ attr_accessor :vote_registered
50
+
51
+ def vote_registered?
52
+ self.vote_registered
53
+ end
54
+
55
+ def default_conditions
56
+ {
57
+ :votable_id => self.id,
58
+ :votable_type => self.class.base_class.name.to_s
59
+ }
60
+ end
61
+
62
+ # voting
63
+ def vote_by(args = {})
64
+ options = {
65
+ :vote => true,
66
+ :vote_scope => nil
67
+ }.merge(args)
68
+
69
+ self.vote_registered = false
70
+
71
+ return false if options[:voter].nil?
72
+
73
+ # find the vote
74
+ vote_conditions = { :vote_scope => options[:vote_scope], :voter_type => options[:voter].class.base_class.name }
75
+ vote_conditions.merge!(options[:vote_by_ip] ? { :vote_ip => options[:vote_ip] } : { :voter_id => options[:voter].id} )
76
+ votes_for = find_votes_for(vote_conditions)
77
+
78
+ if votes_for.count == 0 || options[:duplicate]
79
+ # this voter has never voted
80
+ vote_params = { :votable => self, :voter => options[:voter], :vote_scope => options[:vote_scope] }
81
+ vote_params[:vote_ip] = options[:vote_ip] if options[:vote_ip]
82
+ vote = Redmineup::ActsAsVotable::Vote.new(vote_params)
83
+ else
84
+ # this voter is potentially changing his vote
85
+ vote = votes_for.last
86
+ end
87
+
88
+ vote.vote_flag = votable_words.meaning_of(options[:vote])
89
+
90
+ # Allowing for a vote_weight to be associated with every vote. Could change with every voter object
91
+ vote.vote_weight = (options[:vote_weight].to_i if options[:vote_weight].present?) || 1
92
+
93
+ return false if vote.invalid?
94
+
95
+ self.vote_registered = vote.changed?
96
+ vote.save!(validate: false)
97
+ update_cached_votes(options[:vote_scope])
98
+ vote
99
+ end
100
+
101
+ def unvote(args = {})
102
+ return false if (!args[:vote_by_ip] && args[:voter].nil?) || (args[:vote_by_ip] && args[:vote_ip].nil?)
103
+ vote_conditions = { :vote_scope => args[:vote_scope], :voter_type => args[:voter].class.base_class.name }
104
+ vote_conditions.merge!(args[:vote_by_ip] ? { :vote_ip => args[:vote_ip] } : { :voter_id => args[:voter].id})
105
+ votes_for = find_votes_for(vote_conditions)
106
+
107
+ return true if votes_for.empty?
108
+ votes_for.each(&:destroy)
109
+ update_cached_votes args[:vote_scope]
110
+ self.vote_registered = false if votes_for.count == 0
111
+ true
112
+ end
113
+
114
+ def vote_up(voter, options={})
115
+ self.vote_by :voter => voter, :vote => true,
116
+ :vote_scope => options[:vote_scope], :vote_weight => options[:vote_weight], :vote_ip => options[:vote_ip],
117
+ :vote_by_ip => options[:vote_by_ip]
118
+ end
119
+
120
+ def vote_down(voter, options={})
121
+ self.vote_by :voter => voter, :vote => false,
122
+ :vote_scope => options[:vote_scope], :vote_weight => options[:vote_weight], :vote_ip => options[:vote_ip],
123
+ :vote_by_ip => options[:vote_by_ip]
124
+ end
125
+
126
+ def unvote_by(voter, options = {})
127
+ # Does not need vote_weight since the votes_for are anyway getting destroyed
128
+ self.unvote :voter => voter, :vote_scope => options[:vote_scope], :vote_ip => options[:vote_ip],
129
+ :vote_by_ip => options[:vote_by_ip]
130
+ end
131
+
132
+ def scope_cache_field(field, vote_scope)
133
+ return field if vote_scope.nil?
134
+
135
+ case field
136
+ when :cached_votes_total=
137
+ "cached_scoped_#{vote_scope}_votes_total="
138
+ when :cached_votes_total
139
+ "cached_scoped_#{vote_scope}_votes_total"
140
+ when :cached_votes_up=
141
+ "cached_scoped_#{vote_scope}_votes_up="
142
+ when :cached_votes_up
143
+ "cached_scoped_#{vote_scope}_votes_up"
144
+ when :cached_votes_down=
145
+ "cached_scoped_#{vote_scope}_votes_down="
146
+ when :cached_votes_down
147
+ "cached_scoped_#{vote_scope}_votes_down"
148
+ when :cached_votes_score=
149
+ "cached_scoped_#{vote_scope}_votes_score="
150
+ when :cached_votes_score
151
+ "cached_scoped_#{vote_scope}_votes_score"
152
+ when :cached_weighted_total
153
+ "cached_weighted_#{vote_scope}_total"
154
+ when :cached_weighted_total=
155
+ "cached_weighted_#{vote_scope}_total="
156
+ when :cached_weighted_score
157
+ "cached_weighted_#{vote_scope}_score"
158
+ when :cached_weighted_score=
159
+ "cached_weighted_#{vote_scope}_score="
160
+ when :cached_weighted_average
161
+ "cached_weighted_#{vote_scope}_average"
162
+ when :cached_weighted_average=
163
+ "cached_weighted_#{vote_scope}_average="
164
+ end
165
+ end
166
+
167
+ # caching
168
+ def update_cached_votes(vote_scope = nil)
169
+ updates = {}
170
+
171
+ if self.respond_to?(:cached_votes_total=)
172
+ updates[:cached_votes_total] = count_votes_total(true)
173
+ end
174
+
175
+ if self.respond_to?(:cached_votes_up=)
176
+ updates[:cached_votes_up] = count_votes_up(true)
177
+ end
178
+
179
+ if self.respond_to?(:cached_votes_down=)
180
+ updates[:cached_votes_down] = count_votes_down(true)
181
+ end
182
+
183
+ if self.respond_to?(:cached_votes_score=)
184
+ updates[:cached_votes_score] = (
185
+ (updates[:cached_votes_up] || count_votes_up(true)) -
186
+ (updates[:cached_votes_down] || count_votes_down(true))
187
+ )
188
+ end
189
+
190
+ if self.respond_to?(:cached_weighted_total=)
191
+ updates[:cached_weighted_total] = weighted_total(true)
192
+ end
193
+
194
+ if self.respond_to?(:cached_weighted_score=)
195
+ updates[:cached_weighted_score] = weighted_score(true)
196
+ end
197
+
198
+ if self.respond_to?(:cached_weighted_average=)
199
+ updates[:cached_weighted_average] = weighted_average(true)
200
+ end
201
+
202
+ if vote_scope
203
+ if self.respond_to?(scope_cache_field :cached_votes_total=, vote_scope)
204
+ updates[scope_cache_field :cached_votes_total, vote_scope] = count_votes_total(true, vote_scope)
205
+ end
206
+
207
+ if self.respond_to?(scope_cache_field :cached_votes_up=, vote_scope)
208
+ updates[scope_cache_field :cached_votes_up, vote_scope] = count_votes_up(true, vote_scope)
209
+ end
210
+
211
+ if self.respond_to?(scope_cache_field :cached_votes_down=, vote_scope)
212
+ updates[scope_cache_field :cached_votes_down, vote_scope] = count_votes_down(true, vote_scope)
213
+ end
214
+
215
+ if self.respond_to?(scope_cache_field :cached_weighted_total=, vote_scope)
216
+ updates[scope_cache_field :cached_weighted_total, vote_scope] = weighted_total(true, vote_scope)
217
+ end
218
+
219
+ if self.respond_to?(scope_cache_field :cached_weighted_score=, vote_scope)
220
+ updates[scope_cache_field :cached_weighted_score, vote_scope] = weighted_score(true, vote_scope)
221
+ end
222
+
223
+ if self.respond_to?(scope_cache_field :cached_votes_score=, vote_scope)
224
+ updates[scope_cache_field :cached_votes_score, vote_scope] = (
225
+ (updates[scope_cache_field :cached_votes_up, vote_scope] || count_votes_up(true, vote_scope)) -
226
+ (updates[scope_cache_field :cached_votes_down, vote_scope] || count_votes_down(true, vote_scope))
227
+ )
228
+ end
229
+
230
+ if self.respond_to?(scope_cache_field :cached_weighted_average=, vote_scope)
231
+ updates[scope_cache_field :cached_weighted_average, vote_scope] = weighted_average(true, vote_scope)
232
+ end
233
+ end
234
+ self.record_timestamps = false
235
+ if (::ActiveRecord::VERSION::MAJOR == 3) && (::ActiveRecord::VERSION::MINOR != 0)
236
+ self.assign_attributes(updates, :without_protection => true) && self.save if !updates.empty?
237
+ else
238
+ self.assign_attributes(updates) && self.save if !updates.empty?
239
+ end
240
+ end
241
+
242
+ # results
243
+ def find_votes_for(extra_conditions = {})
244
+ votes_for.where(extra_conditions)
245
+ end
246
+
247
+ def get_up_votes(options = {})
248
+ vote_scope_hash = scope_or_empty_hash(options[:vote_scope])
249
+ find_votes_for({:vote_flag => true}.merge(vote_scope_hash))
250
+ end
251
+
252
+ def get_down_votes(options = {})
253
+ vote_scope_hash = scope_or_empty_hash(options[:vote_scope])
254
+ find_votes_for({ :vote_flag => false }.merge(vote_scope_hash))
255
+ end
256
+
257
+ # counting
258
+ def count_votes_total(skip_cache = false, vote_scope = nil)
259
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_votes_total, vote_scope)
260
+ return self.send(scope_cache_field :cached_votes_total, vote_scope)
261
+ end
262
+ find_votes_for(scope_or_empty_hash(vote_scope)).count
263
+ end
264
+
265
+ def count_votes_up(skip_cache = false, vote_scope = nil)
266
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_votes_up, vote_scope)
267
+ return self.send(scope_cache_field :cached_votes_up, vote_scope)
268
+ end
269
+ get_up_votes(:vote_scope => vote_scope).count
270
+ end
271
+
272
+ def count_votes_down(skip_cache = false, vote_scope = nil)
273
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_votes_down, vote_scope)
274
+ return self.send(scope_cache_field :cached_votes_down, vote_scope)
275
+ end
276
+ get_down_votes(:vote_scope => vote_scope).count
277
+ end
278
+
279
+ def weighted_total(skip_cache = false, vote_scope = nil)
280
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_weighted_total, vote_scope)
281
+ return self.send(scope_cache_field :cached_weighted_total, vote_scope)
282
+ end
283
+ ups = get_up_votes(:vote_scope => vote_scope).sum(:vote_weight)
284
+ downs = get_down_votes(:vote_scope => vote_scope).sum(:vote_weight)
285
+ ups + downs
286
+ end
287
+
288
+ def weighted_score(skip_cache = false, vote_scope = nil)
289
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_weighted_score, vote_scope)
290
+ return self.send(scope_cache_field :cached_weighted_score, vote_scope)
291
+ end
292
+ ups = get_up_votes(:vote_scope => vote_scope).sum(:vote_weight)
293
+ downs = get_down_votes(:vote_scope => vote_scope).sum(:vote_weight)
294
+ ups - downs
295
+ end
296
+
297
+ def weighted_average(skip_cache = false, vote_scope = nil)
298
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_weighted_average, vote_scope)
299
+ return self.send(scope_cache_field :cached_weighted_average, vote_scope)
300
+ end
301
+
302
+ count = count_votes_total(skip_cache, vote_scope).to_i
303
+ if count > 0
304
+ weighted_score(skip_cache, vote_scope).to_f / count
305
+ else
306
+ 0.0
307
+ end
308
+ end
309
+
310
+ # voters
311
+ def voted_on_by?(voter)
312
+ votes = find_votes_for :voter_id => voter.id, :voter_type => voter.class.base_class.name
313
+ votes.count > 0
314
+ end
315
+
316
+ private
317
+
318
+ def scope_or_empty_hash(vote_scope)
319
+ vote_scope ? { :vote_scope => vote_scope } : {}
320
+ end
321
+ end
322
+ end
323
+ end
@@ -0,0 +1,28 @@
1
+ require 'redmineup/helpers/vote_helper'
2
+
3
+ module Redmineup
4
+ module ActsAsVotable
5
+ class Vote < ActiveRecord::Base
6
+ include Helpers::Words
7
+
8
+ if defined?(ProtectedAttributes) || ::ActiveRecord::VERSION::MAJOR < 4
9
+ attr_accessible :votable_id, :votable_type,
10
+ :voter_id, :voter_type,
11
+ :votable, :voter,
12
+ :vote_flag, :vote_scope,
13
+ :vote_ip
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.to_s) }
22
+ scope :by_type, lambda { |klass| where(:voter_type => klass.to_s) }
23
+
24
+ validates_presence_of :votable_id
25
+ validates_presence_of :voter_id
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,131 @@
1
+ module Redmineup
2
+ module ActsAsVotable
3
+ module Voter
4
+ def self.included(base)
5
+ # allow user to define these
6
+ aliases = {
7
+ :vote_up_for => [:likes, :upvotes, :up_votes],
8
+ :vote_down_for => [:dislikes, :downvotes, :down_votes],
9
+ :unvote_for => [:unlike, :undislike],
10
+ :voted_on? => [:voted_for?],
11
+ :voted_up_on? => [:voted_up_for?, :liked?],
12
+ :voted_down_on? => [:voted_down_for?, :disliked?],
13
+ :voted_as_when_voting_on => [:voted_as_when_voted_on, :voted_as_when_voting_for, :voted_as_when_voted_for],
14
+ :find_up_voted_items => [:find_liked_items],
15
+ :find_down_voted_items => [:find_disliked_items]
16
+ }
17
+
18
+ base.class_eval do
19
+ has_many :votes, :class_name => 'Redmineup::ActsAsVotable::Vote', :as => :voter, :dependent => :destroy do
20
+ def votables
21
+ includes(:votable).map(&:votable)
22
+ end
23
+ end
24
+
25
+ aliases.each do |method, links|
26
+ links.each do |new_method|
27
+ alias_method(new_method, method)
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ # voting
34
+ def vote(args)
35
+ args[:votable].vote_by args.merge(:voter => self)
36
+ end
37
+
38
+ def vote_up_for(model = nil, args = {})
39
+ vote :votable => model, :vote_scope => args[:vote_scope], :vote => true
40
+ end
41
+
42
+ def vote_down_for(model = nil, args = {})
43
+ vote :votable => model, :vote_scope => args[:vote_scope], :vote => false
44
+ end
45
+
46
+ def unvote_for(model, args = {})
47
+ model.unvote :voter => self, :vote_scope => args[:vote_scope]
48
+ end
49
+
50
+ # results
51
+ def voted_on?(votable, args = {})
52
+ votes = find_votes(:votable_id => votable.id, :votable_type => votable.class.base_class.name,
53
+ :vote_scope => args[:vote_scope])
54
+ !votes.empty?
55
+ end
56
+
57
+ def voted_up_on?(votable, args = {})
58
+ votes = find_votes(:votable_id => votable.id, :votable_type => votable.class.base_class.name,
59
+ :vote_scope => args[:vote_scope], :vote_flag => true)
60
+ !votes.empty?
61
+ end
62
+
63
+ def voted_down_on?(votable, args = {})
64
+ votes = find_votes(:votable_id => votable.id, :votable_type => votable.class.base_class.name,
65
+ :vote_scope => args[:vote_scope], :vote_flag => false)
66
+ !votes.empty?
67
+ end
68
+
69
+ def voted_as_when_voting_on(votable, args = {})
70
+ vote = find_votes(:votable_id => votable.id, :votable_type => votable.class.base_class.name,
71
+ :vote_scope => args[:vote_scope]).select(:vote_flag).last
72
+ return nil unless vote
73
+ vote.vote_flag
74
+ end
75
+
76
+ def find_votes(extra_conditions = {})
77
+ votes.where(extra_conditions)
78
+ end
79
+
80
+ def find_up_votes(args = {})
81
+ find_votes :vote_flag => true, :vote_scope => args[:vote_scope]
82
+ end
83
+
84
+ def find_down_votes(args = {})
85
+ find_votes :vote_flag => false, :vote_scope => args[:vote_scope]
86
+ end
87
+
88
+ def find_votes_for_class(klass, extra_conditions = {})
89
+ find_votes extra_conditions.merge(:votable_type => klass.name)
90
+ end
91
+
92
+ def find_up_votes_for_class(klass, args = {})
93
+ find_votes_for_class klass, :vote_flag => true, :vote_scope => args[:vote_scope]
94
+ end
95
+
96
+ def find_down_votes_for_class(klass, args = {})
97
+ find_votes_for_class klass, :vote_flag => false, :vote_scope => args[:vote_scope]
98
+ end
99
+
100
+ # Including polymporphic relations for eager loading
101
+ def include_objects
102
+ Redmineup::ActsAsVotable::Vote.includes(:votable)
103
+ end
104
+
105
+ def find_voted_items(extra_conditions = {})
106
+ options = extra_conditions.merge :voter_id => id, :voter_type => self.class.base_class.name
107
+ include_objects.where(options).collect(&:votable)
108
+ end
109
+
110
+ def find_up_voted_items(extra_conditions = {})
111
+ find_voted_items extra_conditions.merge(:vote_flag => true)
112
+ end
113
+
114
+ def find_down_voted_items(extra_conditions = {})
115
+ find_voted_items extra_conditions.merge(:vote_flag => false)
116
+ end
117
+
118
+ def get_voted(klass, extra_conditions = {})
119
+ klass.joins(:votes_for).merge find_votes(extra_conditions)
120
+ end
121
+
122
+ def get_up_voted(klass)
123
+ klass.joins(:votes_for).merge find_up_votes
124
+ end
125
+
126
+ def get_down_voted(klass)
127
+ klass.joins(:votes_for).merge find_down_votes
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,43 @@
1
+ module Redmineup
2
+ class AssetsManager
3
+ def self.install_assets
4
+ return unless Gem.loaded_specs[GEM_NAME]
5
+ source = File.join(Gem.loaded_specs[GEM_NAME].full_gem_path, 'vendor', 'assets')
6
+ destination = File.join(Dir.pwd, 'public', 'plugin_assets', GEM_NAME)
7
+ return unless File.directory?(source)
8
+
9
+ source_files = Dir[source + '/**/*']
10
+ source_dirs = source_files.select { |d| File.directory?(d) }
11
+ source_files -= source_dirs
12
+
13
+ unless source_files.empty?
14
+ base_target_dir = File.join(destination, File.dirname(source_files.first).gsub(source, ''))
15
+ begin
16
+ FileUtils.mkdir_p(base_target_dir)
17
+ rescue Exception => e
18
+ raise "Could not create directory #{base_target_dir}: " + e.message
19
+ end
20
+ end
21
+
22
+ source_dirs.each do |dir|
23
+ target_dir = File.join(destination, dir.gsub(source, ''))
24
+ begin
25
+ FileUtils.mkdir_p(target_dir)
26
+ rescue Exception => e
27
+ raise "Could not create directory #{target_dir}: " + e.message
28
+ end
29
+ end
30
+
31
+ source_files.each do |file|
32
+ begin
33
+ target = File.join(destination, file.gsub(source, ''))
34
+ unless File.exist?(target) && FileUtils.identical?(file, target)
35
+ FileUtils.cp(file, target)
36
+ end
37
+ rescue Exception => e
38
+ raise "Could not copy #{file} to #{target}: " + e.message
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end