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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/Gemfile +4 -0
- data/README.md +204 -0
- data/Rakefile +11 -0
- data/app/controllers/redmineup_controller.rb +26 -0
- data/app/views/redmine_crm/redmineup_calendar/_calendar.html.erb +34 -0
- data/app/views/redmineup/_money.html.erb +44 -0
- data/app/views/redmineup/settings.html.erb +10 -0
- data/bitbucket-pipelines.yml +50 -0
- data/config/currency_iso.json +2544 -0
- data/config/locales/cs.yml +13 -0
- data/config/locales/de.yml +13 -0
- data/config/locales/en.yml +13 -0
- data/config/locales/es.yml +13 -0
- data/config/locales/ru.yml +13 -0
- data/config/routes.rb +5 -0
- data/doc/CHANGELOG +14 -0
- data/doc/LICENSE.txt +339 -0
- data/lib/redmineup/acts_as_draftable/draft.rb +40 -0
- data/lib/redmineup/acts_as_draftable/up_acts_as_draftable.rb +172 -0
- data/lib/redmineup/acts_as_list/list.rb +282 -0
- data/lib/redmineup/acts_as_priceable/up_acts_as_priceable.rb +33 -0
- data/lib/redmineup/acts_as_taggable/tag.rb +81 -0
- data/lib/redmineup/acts_as_taggable/tag_list.rb +111 -0
- data/lib/redmineup/acts_as_taggable/tagging.rb +16 -0
- data/lib/redmineup/acts_as_taggable/up_acts_as_taggable.rb +357 -0
- data/lib/redmineup/acts_as_viewed/up_acts_as_viewed.rb +274 -0
- data/lib/redmineup/acts_as_votable/up_acts_as_votable.rb +80 -0
- data/lib/redmineup/acts_as_votable/up_acts_as_voter.rb +20 -0
- data/lib/redmineup/acts_as_votable/votable.rb +323 -0
- data/lib/redmineup/acts_as_votable/vote.rb +28 -0
- data/lib/redmineup/acts_as_votable/voter.rb +131 -0
- data/lib/redmineup/assets_manager.rb +43 -0
- data/lib/redmineup/colors_helper.rb +192 -0
- data/lib/redmineup/compatibility/application_controller_patch.rb +33 -0
- data/lib/redmineup/compatibility/routing_mapper_patch.rb +25 -0
- data/lib/redmineup/currency/formatting.rb +224 -0
- data/lib/redmineup/currency/heuristics.rb +151 -0
- data/lib/redmineup/currency/loader.rb +23 -0
- data/lib/redmineup/currency.rb +450 -0
- data/lib/redmineup/engine.rb +4 -0
- data/lib/redmineup/helpers/external_assets_helper.rb +20 -0
- data/lib/redmineup/helpers/form_tag_helper.rb +88 -0
- data/lib/redmineup/helpers/rup_calendar_helper.rb +146 -0
- data/lib/redmineup/helpers/tags_helper.rb +13 -0
- data/lib/redmineup/helpers/vote_helper.rb +35 -0
- data/lib/redmineup/hooks/views_layouts_hook.rb +11 -0
- data/lib/redmineup/liquid/drops/attachment_drop.rb +47 -0
- data/lib/redmineup/liquid/drops/issue_relations_drop.rb +41 -0
- data/lib/redmineup/liquid/drops/issues_drop.rb +217 -0
- data/lib/redmineup/liquid/drops/news_drop.rb +54 -0
- data/lib/redmineup/liquid/drops/projects_drop.rb +86 -0
- data/lib/redmineup/liquid/drops/time_entries_drop.rb +65 -0
- data/lib/redmineup/liquid/drops/users_drop.rb +68 -0
- data/lib/redmineup/liquid/filters/arrays.rb +254 -0
- data/lib/redmineup/liquid/filters/base.rb +249 -0
- data/lib/redmineup/liquid/filters/colors.rb +31 -0
- data/lib/redmineup/money_helper.rb +66 -0
- data/lib/redmineup/patches/liquid_patch.rb +33 -0
- data/lib/redmineup/settings/money.rb +46 -0
- data/lib/redmineup/settings.rb +53 -0
- data/lib/redmineup/version.rb +3 -0
- data/lib/redmineup.rb +108 -0
- data/redmineup.gemspec +29 -0
- data/test/acts_as_draftable/draft_test.rb +29 -0
- data/test/acts_as_draftable/rup_acts_as_draftable_test.rb +178 -0
- data/test/acts_as_taggable/rup_acts_as_taggable_test.rb +350 -0
- data/test/acts_as_taggable/tag_list_test.rb +34 -0
- data/test/acts_as_taggable/tag_test.rb +72 -0
- data/test/acts_as_taggable/tagging_test.rb +15 -0
- data/test/acts_as_viewed/rup_acts_as_viewed_test.rb +47 -0
- data/test/acts_as_votable/rup_acts_as_votable_test.rb +19 -0
- data/test/acts_as_votable/rup_acts_as_voter_test.rb +14 -0
- data/test/acts_as_votable/votable_test.rb +507 -0
- data/test/acts_as_votable/voter_test.rb +296 -0
- data/test/currency_test.rb +292 -0
- data/test/database.yml +17 -0
- data/test/fixtures/attachments.yml +14 -0
- data/test/fixtures/issues.yml +24 -0
- data/test/fixtures/news.yml +8 -0
- data/test/fixtures/projects.yml +10 -0
- data/test/fixtures/taggings.yml +32 -0
- data/test/fixtures/tags.yml +11 -0
- data/test/fixtures/users.yml +9 -0
- data/test/fixtures/votable_caches.yml +2 -0
- data/test/fixtures/votables.yml +4 -0
- data/test/fixtures/voters.yml +6 -0
- data/test/liquid/drops/attachment_drop_test.rb +15 -0
- data/test/liquid/drops/issue_relations_drop_test.rb +24 -0
- data/test/liquid/drops/issues_drop_test.rb +38 -0
- data/test/liquid/drops/news_drop_test.rb +38 -0
- data/test/liquid/drops/projects_drop_test.rb +44 -0
- data/test/liquid/drops/uses_drop_test.rb +36 -0
- data/test/liquid/filters/arrays_filter_test.rb +31 -0
- data/test/liquid/filters/base_filter_test.rb +67 -0
- data/test/liquid/filters/colors_filter_test.rb +33 -0
- data/test/liquid/liquid_helper.rb +34 -0
- data/test/models/attachment.rb +3 -0
- data/test/models/issue.rb +21 -0
- data/test/models/issue_relation.rb +10 -0
- data/test/models/news.rb +3 -0
- data/test/models/project.rb +8 -0
- data/test/models/user.rb +11 -0
- data/test/models/vote_classes.rb +33 -0
- data/test/money_helper_test.rb +12 -0
- data/test/schema.rb +144 -0
- data/test/tags_helper_test.rb +29 -0
- data/test/test_helper.rb +66 -0
- data/test/vote_helper_test.rb +28 -0
- data/vendor/assets/images/money.png +0 -0
- data/vendor/assets/images/vcard.png +0 -0
- data/vendor/assets/javascripts/Chart.bundle.min.js +16 -0
- data/vendor/assets/javascripts/select2.js +2 -0
- data/vendor/assets/javascripts/select2_helpers.js +192 -0
- data/vendor/assets/stylesheets/money.css +96 -0
- data/vendor/assets/stylesheets/select2.css +424 -0
- 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
|