redmineup 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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