redmine_crm 0.0.23 → 0.0.53

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/README.md +166 -33
  4. data/Rakefile +3 -12
  5. data/app/controllers/redmine_crm_controller.rb +26 -0
  6. data/app/views/redmine_crm/_money.html.erb +44 -0
  7. data/app/views/redmine_crm/settings.html.erb +10 -0
  8. data/bitbucket-pipelines.yml +54 -0
  9. data/config/currency_iso.json +12 -0
  10. data/config/locales/en.yml +13 -0
  11. data/config/locales/ru.yml +13 -0
  12. data/config/routes.rb +5 -0
  13. data/doc/CHANGELOG +123 -2
  14. data/lib/redmine_crm/acts_as_draftable/draft.rb +40 -0
  15. data/lib/redmine_crm/acts_as_draftable/rcrm_acts_as_draftable.rb +172 -0
  16. data/lib/redmine_crm/acts_as_list/list.rb +282 -0
  17. data/lib/redmine_crm/{rcrm_acts_as_taggable.rb → acts_as_taggable/rcrm_acts_as_taggable.rb} +112 -93
  18. data/lib/redmine_crm/acts_as_taggable/tag.rb +81 -0
  19. data/lib/redmine_crm/acts_as_taggable/tag_list.rb +111 -0
  20. data/lib/redmine_crm/acts_as_taggable/tagging.rb +16 -0
  21. data/lib/redmine_crm/acts_as_viewed/rcrm_acts_as_viewed.rb +274 -0
  22. data/lib/redmine_crm/{rcrm_acts_as_votable.rb → acts_as_votable/rcrm_acts_as_votable.rb} +15 -14
  23. data/lib/redmine_crm/acts_as_votable/rcrm_acts_as_voter.rb +20 -0
  24. data/lib/redmine_crm/{votable.rb → acts_as_votable/votable.rb} +54 -65
  25. data/lib/redmine_crm/{vote.rb → acts_as_votable/vote.rb} +6 -8
  26. data/lib/redmine_crm/{voter.rb → acts_as_votable/voter.rb} +29 -34
  27. data/lib/redmine_crm/assets_manager.rb +43 -0
  28. data/lib/redmine_crm/colors_helper.rb +192 -0
  29. data/lib/redmine_crm/compatibility/application_controller_patch.rb +33 -0
  30. data/lib/redmine_crm/currency/formatting.rb +5 -8
  31. data/lib/redmine_crm/currency/heuristics.rb +1 -1
  32. data/lib/redmine_crm/currency/loader.rb +5 -6
  33. data/lib/redmine_crm/currency.rb +28 -17
  34. data/lib/redmine_crm/engine.rb +4 -0
  35. data/lib/redmine_crm/helpers/external_assets_helper.rb +19 -0
  36. data/lib/redmine_crm/helpers/form_tag_helper.rb +76 -0
  37. data/lib/redmine_crm/helpers/tags_helper.rb +1 -3
  38. data/lib/redmine_crm/helpers/vote_helper.rb +29 -32
  39. data/lib/redmine_crm/hooks/views_layouts_hook.rb +11 -0
  40. data/lib/redmine_crm/liquid/drops/issues_drop.rb +191 -0
  41. data/lib/redmine_crm/liquid/drops/news_drop.rb +54 -0
  42. data/lib/redmine_crm/liquid/drops/projects_drop.rb +86 -0
  43. data/lib/redmine_crm/liquid/drops/time_entries_drop.rb +65 -0
  44. data/lib/redmine_crm/liquid/drops/users_drop.rb +68 -0
  45. data/lib/redmine_crm/liquid/filters/arrays.rb +187 -0
  46. data/lib/redmine_crm/liquid/filters/base.rb +217 -0
  47. data/lib/redmine_crm/liquid/filters/colors.rb +31 -0
  48. data/lib/redmine_crm/money_helper.rb +17 -18
  49. data/lib/redmine_crm/settings/money.rb +46 -0
  50. data/lib/redmine_crm/settings.rb +53 -0
  51. data/lib/redmine_crm/version.rb +1 -1
  52. data/lib/redmine_crm.rb +60 -21
  53. data/redmine_crm.gemspec +12 -6
  54. data/test/acts_as_draftable/draft_test.rb +29 -0
  55. data/test/acts_as_draftable/rcrm_acts_as_draftable_test.rb +178 -0
  56. data/test/{acts_as_taggable_test.rb → acts_as_taggable/rcrm_acts_as_taggable_test.rb} +117 -156
  57. data/test/acts_as_taggable/tag_list_test.rb +34 -0
  58. data/test/acts_as_taggable/tag_test.rb +72 -0
  59. data/test/acts_as_taggable/tagging_test.rb +15 -0
  60. data/test/{viewed_test.rb → acts_as_viewed/rcrm_acts_as_viewed_test.rb} +17 -15
  61. data/test/acts_as_votable/rcrm_acts_as_votable_test.rb +19 -0
  62. data/test/acts_as_votable/rcrm_acts_as_voter_test.rb +14 -0
  63. data/test/{votable_model_test.rb → acts_as_votable/votable_test.rb} +34 -5
  64. data/test/{voter_model_test.rb → acts_as_votable/voter_test.rb} +8 -8
  65. data/test/currency_test.rb +10 -10
  66. data/test/database.yml +14 -14
  67. data/test/fixtures/issues.yml +13 -1
  68. data/test/fixtures/news.yml +8 -0
  69. data/test/fixtures/projects.yml +10 -0
  70. data/test/fixtures/users.yml +6 -2
  71. data/test/liquid/drops/issues_drop_test.rb +34 -0
  72. data/test/liquid/drops/news_drop_test.rb +38 -0
  73. data/test/liquid/drops/projects_drop_test.rb +44 -0
  74. data/test/liquid/drops/uses_drop_test.rb +36 -0
  75. data/test/liquid/filters/arrays_filter_test.rb +31 -0
  76. data/test/liquid/filters/base_filter_test.rb +63 -0
  77. data/test/liquid/filters/colors_filter_test.rb +33 -0
  78. data/test/liquid/liquid_helper.rb +34 -0
  79. data/test/models/issue.rb +14 -0
  80. data/test/models/news.rb +3 -0
  81. data/test/models/project.rb +8 -0
  82. data/test/{fixtures → models}/user.rb +5 -1
  83. data/test/{fixtures → models}/vote_classes.rb +0 -21
  84. data/test/money_helper_test.rb +5 -5
  85. data/test/schema.rb +33 -10
  86. data/test/test_helper.rb +20 -72
  87. data/vendor/assets/images/money.png +0 -0
  88. data/vendor/assets/images/vcard.png +0 -0
  89. data/vendor/assets/javascripts/Chart.bundle.min.js +16 -0
  90. data/vendor/assets/javascripts/select2.js +2 -0
  91. data/vendor/assets/javascripts/select2_helpers.js +192 -0
  92. data/vendor/assets/stylesheets/money.css +3 -0
  93. data/vendor/assets/stylesheets/select2.css +424 -0
  94. metadata +190 -40
  95. data/lib/redmine_crm/rcrm_acts_as_viewed.rb +0 -287
  96. data/lib/redmine_crm/rcrm_acts_as_voter.rb +0 -27
  97. data/lib/redmine_crm/tag.rb +0 -81
  98. data/lib/redmine_crm/tag_list.rb +0 -112
  99. data/lib/redmine_crm/tagging.rb +0 -20
  100. data/test/fixtures/issue.rb +0 -14
  101. data/test/tag_test.rb +0 -64
  102. data/test/tagging_test.rb +0 -14
  103. data/test/votable_test.rb +0 -17
@@ -0,0 +1,274 @@
1
+ # Copyright (c) 2008 Damian Martinelli
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+ module RedmineCrm
22
+ module ActsAsViewed #:nodoc:
23
+ # == acts_as_viewed
24
+ # Adds views count capabilities to any ActiveRecord object.
25
+ # It has the ability to work with objects that have or don't special fields to keep a tally of the
26
+ # viewings for each object.
27
+ # In addition it will by default use the User model as the viewer object and keep the viewings per-user.
28
+ # It can be configured to use another class.
29
+ # The IP address are used to not repeat views from the same ip. Only one view are count by user or IP.
30
+ #
31
+ # Special methods are provided to create the viewings table and if needed, to add the special fields needed
32
+ # to keep per-objects viewings fast for access to viewed objects. Can be easily used in migrations.
33
+ #
34
+ # == Example of usage:
35
+ #
36
+ # class Video < ActiveRecord::Base
37
+ # acts_as_viewed
38
+ # end
39
+ #
40
+ # In a controller:
41
+ #
42
+ # bill = User.find_by_name 'bill'
43
+ # batman = Video.find_by_title 'Batman'
44
+ # toystory = Video.find_by_title 'Toy Story'
45
+ #
46
+ # batman.view request.remote_addr, bill
47
+ # toystory.view request.remote_addr, bill
48
+ #
49
+ # batman.view_count # => 1
50
+ #
51
+ #
52
+ module Viewed
53
+ class ViewedError < RuntimeError; end
54
+
55
+ def self.included(base) #:nodoc:
56
+ base.extend(ClassMethods)
57
+ end
58
+
59
+ module ClassMethods
60
+ # Make the model viewable.
61
+ # The Viewing model, holding the details of the viewings, will be created dynamically if it doesn't exist.
62
+ #
63
+ # * Adds a <tt>has_many :viewings</tt> association to the model for easy retrieval of the detailed viewings.
64
+ # * Adds a <tt>has_many :viewers</tt> association to the object.
65
+ # * Adds a <tt>has_many :viewings</tt> associations to the viewer class.
66
+ #
67
+ # === Options
68
+ # * <tt>:viewing_class</tt> -
69
+ # class of the model used for the viewings. Defaults to Viewing. This class will be dynamically created if not already defined.
70
+ # If the class is predefined, it must have in it the following definitions:
71
+ # <tt>belongs_to :viewed, :polymorphic => true</tt>
72
+ # <tt>belongs_to :viewer, :class_name => 'User', :foreign_key => :viewer_id</tt> replace user with the viewer class if needed.
73
+ # * <tt>:viewer_class</tt> -
74
+ # class of the model that creates the viewing.
75
+ # Defaults to User This class will NOT be created, so it must be defined in the app.
76
+ # Use the IP address to prevent multiple viewings from the same client.
77
+ #
78
+ def rcrm_acts_as_viewed(options = {})
79
+ # don't allow multiple calls
80
+ return if self.included_modules.include?(ActsAsViewed::Viewed::ViewMethods)
81
+ send :include, ActsAsViewed::Viewed::ViewMethods
82
+
83
+ # Create the model for ratings if it doesn't yet exist
84
+ viewing_class = options[:viewing_class] || 'Viewing'
85
+ viewer_class = options[:viewer_class] || 'User'
86
+
87
+ unless Object.const_defined?(viewing_class)
88
+ Object.class_eval <<-EOV
89
+ class #{viewing_class} < ActiveRecord::Base
90
+ belongs_to :viewed, polymorphic: true
91
+ belongs_to :viewer, class_name: '#{viewer_class}', foreign_key: :viewer_id
92
+ end
93
+ EOV
94
+ end
95
+
96
+ # Rails < 3
97
+ # write_inheritable_attribute( :acts_as_viewed_options ,
98
+ # { :viewing_class => viewing_class,
99
+ # :viewer_class => viewer_class } )
100
+ # class_inheritable_reader :acts_as_viewed_options
101
+
102
+ # Rails >= 3
103
+ class_attribute :acts_as_viewed_options
104
+ self.acts_as_viewed_options = { :viewing_class => viewing_class,
105
+ :viewer_class => viewer_class }
106
+ class_eval do
107
+ has_many :viewings, as: :viewed, dependent: :delete_all, class_name: viewing_class.to_s
108
+ has_many :viewers, through: :viewings, class_name: viewer_class.to_s
109
+
110
+ before_create :init_viewing_fields
111
+ end
112
+
113
+ # Add to the User (or whatever the viewer is) a has_many viewings
114
+ viewer_as_class = viewer_class.constantize
115
+ return if viewer_as_class.instance_methods.include?('find_in_viewings')
116
+ viewer_as_class.class_eval do
117
+ has_many :viewings, foreign_key: :viewer_id, class_name: viewing_class.to_s, dependent: :delete_all
118
+ end
119
+ end
120
+ end
121
+
122
+ module ViewMethods
123
+ def self.included(base) #:nodoc:
124
+ base.extend ClassMethods
125
+ end
126
+
127
+ # Is this object viewed already?
128
+ def viewed?
129
+ return (!self.views.nil? && self.views > 0) if attributes.has_key? 'views'
130
+ !viewings.first.nil?
131
+ end
132
+
133
+ # Get the number of viewings for this object based on the views field,
134
+ # or with a SQL query if the viewed objects doesn't have the views field
135
+ def view_count
136
+ return ("#{self.total_views}(#{self.views})" || 0) if attributes.has_key? 'views'
137
+ viewings.count
138
+ end
139
+
140
+ # Change views count (total_views and views) if it's existing in object
141
+ # If options[:only_total] == true count of unique views doesn't change
142
+ def increase_views_count(options)
143
+ if attributes.has_key?('views') && attributes.has_key?('total_views')
144
+ target = self
145
+ target.views = ((target.views || 0) + 1) unless options[:only_total]
146
+ target.total_views = ((target.total_views || 0) + 1)
147
+ target.record_timestamps = false
148
+ target.save(:validate => false, :touch => false)
149
+ end
150
+ end
151
+
152
+ # View the object with or without a viewer - create new or update as needed
153
+ #
154
+ # * <tt>ip</tt> - the viewer ip
155
+ # * <tt>viewer</tt> - an object of the viewer class. Must be valid and with an id to be used. Or nil
156
+ def view(ip, viewer = nil)
157
+ # Sanity checks for the parameters
158
+ viewing_class = acts_as_viewed_options[:viewing_class].constantize
159
+ if viewer && !(acts_as_viewed_options[:viewer_class].constantize === viewer)
160
+ raise ViewedError, "the viewer object must be the one used when defining acts_as_viewed (or a descendent of it). other objects are not acceptable"
161
+ end
162
+
163
+ viewing_class.transaction do
164
+ if !viewed_by? ip, viewer
165
+ view = viewing_class.new
166
+ view.viewer_id = viewer.id if viewer && !viewer.id.nil?
167
+ view.ip = ip
168
+ viewings << view
169
+ view.save
170
+ increase_views_count(:only_total => false)
171
+ else
172
+ increase_views_count(:only_total => true)
173
+ end
174
+ true
175
+ end
176
+ end
177
+
178
+ # Check if an item was already viewed by the given viewer
179
+ def viewed_by?(ip, viewer = nil)
180
+ if viewer && !viewer.nil? && !(acts_as_viewed_options[:viewer_class].constantize === viewer)
181
+ raise ViewedError, "the viewer object must be the one used when defining acts_as_viewed (or a descendent of it). other objects are not acceptable"
182
+ end
183
+ if viewer && !viewer.id.nil? && !viewer.anonymous?
184
+ return viewings.where("viewer_id = '#{viewer.id}'").any?
185
+ else
186
+ return viewings.where("ip = '#{ip}'").any?
187
+ end
188
+ end
189
+
190
+ private
191
+
192
+ def init_viewing_fields #:nodoc:
193
+ self.views ||= 0 if attributes.has_key?('views')
194
+ end
195
+ end
196
+
197
+ module ClassMethods
198
+ # Generate the viewings columns on a table, to be used when creating the table
199
+ # in a migration. This is the preferred way to do in a migration that creates
200
+ # new tables as it will make it as part of the table creation, and not generate
201
+ # ALTER TABLE calls after the fact
202
+ def generate_viewings_columns(table)
203
+ table.column :views, :integer # uniq views
204
+ table.column :total_views, :integer
205
+ end
206
+
207
+ # Create the needed columns for acts_as_viewed.
208
+ # To be used during migration, but can also be used in other places.
209
+ def add_viewings_columns
210
+ if !self.content_columns.find { |c| 'views' == c.name }
211
+ self.connection.add_column table_name, :views, :integer, :default => '0'
212
+ self.connection.add_column table_name, :total_views, :integer, :default => '0'
213
+ self.reset_column_information
214
+ end
215
+ end
216
+
217
+ # Remove the acts_as_viewed specific columns added with add_viewings_columns
218
+ # To be used during migration, but can also be used in other places
219
+ def remove_viewings_columns
220
+ if self.content_columns.find { |c| 'views' == c.name }
221
+ self.connection.remove_column table_name, :views
222
+ self.connection.remove_column table_name, :total_views
223
+ self.reset_column_information
224
+ end
225
+ end
226
+
227
+ # Create the viewings table
228
+ # === Options hash:
229
+ # * <tt>:table_name</tt> - use a table name other than viewings
230
+ # To be used during migration, but can also be used in other places
231
+ def create_viewings_table(options = {})
232
+ name = options[:table_name] || :viewings
233
+ if !self.connection.table_exists?(name)
234
+ self.connection.create_table(name) do |t|
235
+ t.column :viewer_id, :integer
236
+ t.column :viewed_id, :integer
237
+ t.column :viewed_type, :string
238
+ t.column :ip, :string, :limit => '24'
239
+ t.column :created_at, :datetime
240
+ end
241
+
242
+ self.connection.add_index(name, :viewer_id)
243
+ self.connection.add_index(name, [:viewed_type, :viewed_id])
244
+ end
245
+ end
246
+
247
+ # Drop the viewings table.
248
+ # === Options hash:
249
+ # * <tt>:table_name</tt> - the name of the viewings table, defaults to viewings
250
+ # To be used during migration, but can also be used in other places
251
+ def drop_viewings_table(options = {})
252
+ name = options[:table_name] || :viewings
253
+ if self.connection.table_exists?(name)
254
+ self.connection.drop_table(name)
255
+ end
256
+ end
257
+
258
+ # Find all viewings for a specific viewer.
259
+ def find_viewed_by(viewer)
260
+ viewing_class = acts_as_viewed_options[:viewing_class].constantize
261
+ if !(acts_as_viewed_options[:viewer_class].constantize === viewer)
262
+ raise ViewedError, "The viewer object must be the one used when defining acts_as_viewed (or a descendent of it). other objects are not acceptable"
263
+ end
264
+ raise ViewedError, 'Viewer must be a valid and existing object' if viewer.nil? || viewer.id.nil?
265
+ raise ViewedError, 'Viewer must be a valid viewer' if !viewing_class.column_names.include?('viewer_id')
266
+ conds = ['viewed_type = ? AND viewer_id = ?', self.name, viewer.id]
267
+ acts_as_viewed_options[:viewing_class].constantize.where(conds).collect { |r| r.viewed_type.constantize.find_by_id r.viewed.id }
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
273
+
274
+ ActiveRecord::Base.send :include, RedmineCrm::ActsAsViewed::Viewed
@@ -3,32 +3,30 @@ require 'active_record'
3
3
  module RedmineCrm
4
4
  module ActsAsVotable #:nodoc:
5
5
  module Votable #:nodoc:
6
-
7
6
  def votable?
8
7
  false
9
8
  end
10
9
 
11
10
  def rcrm_acts_as_votable
12
- require 'redmine_crm/votable'
13
- include ActsAsVotable::Votable
11
+ require 'redmine_crm/acts_as_votable/votable'
12
+ include RedmineCrm::ActsAsVotable::Votable
14
13
 
15
14
  class_eval do
16
15
  def self.votable?
17
16
  true
18
17
  end
19
18
  end
20
-
21
19
  end
22
20
 
23
21
  def create_index(table_name, column_name)
24
22
  return if self.connection.index_exists?(table_name, column_name)
25
-
23
+
26
24
  self.connection.add_index table_name, column_name
27
25
  end
28
26
 
29
- def create_votable_table options = {}
27
+ def create_votable_table(options = {})
30
28
  votes_name_table = options[:votes] || :votes
31
-
29
+
32
30
  if !self.connection.table_exists?(votes_name_table)
33
31
  self.connection.create_table(votes_name_table) do |t|
34
32
  t.references :votable, :polymorphic => true
@@ -37,6 +35,7 @@ module RedmineCrm
37
35
  t.column :vote_flag, :boolean
38
36
  t.column :vote_scope, :string
39
37
  t.column :vote_weight, :integer
38
+ t.column :vote_ip, :string
40
39
 
41
40
  t.timestamps
42
41
  end
@@ -48,7 +47,8 @@ module RedmineCrm
48
47
  :voter_type => :string,
49
48
  :vote_flag => :boolean,
50
49
  :vote_scope => :string,
51
- :vote_weight => :integer
50
+ :vote_weight => :integer,
51
+ :vote_ip => :string
52
52
  }
53
53
  fields.each do |name, type|
54
54
  if !self.connection.column_exists?(votes_name_table, name)
@@ -59,21 +59,22 @@ module RedmineCrm
59
59
  end
60
60
 
61
61
  if self.parent::VERSION::MAJOR < 4
62
- create_index votes_name_table, [:votable_id, :votable_type]
63
- create_index votes_name_table, [:voter_id, :voter_type]
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
64
  end
65
65
 
66
66
  create_index votes_name_table, [:voter_id, :voter_type, :vote_scope]
67
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]
68
70
  end
69
71
 
70
- def drop_votable_table options = {}
71
- votes_name_table = options[:votes] || :votes
72
+ def drop_votable_table(options = {})
73
+ votes_name_table = options[:votes] || :votes
72
74
  if self.connection.table_exists?(votes_name_table)
73
75
  self.connection.drop_table votes_name_table
74
76
  end
75
77
  end
76
-
77
78
  end
78
79
  end
79
- end
80
+ end
@@ -0,0 +1,20 @@
1
+ module RedmineCrm
2
+ module ActsAsVotable
3
+ module Voter
4
+ def voter?
5
+ false
6
+ end
7
+
8
+ def rcrm_acts_as_voter(*args)
9
+ require 'redmine_crm/acts_as_votable/voter'
10
+ include RedmineCrm::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
@@ -3,11 +3,9 @@ require 'redmine_crm/helpers/vote_helper'
3
3
  module RedmineCrm
4
4
  module ActsAsVotable
5
5
  module Votable
6
-
7
6
  include ActsAsVotable::Helpers::Words
8
7
 
9
- def self.included base
10
-
8
+ def self.included(base)
11
9
  # allow the user to define these himself
12
10
  aliases = {
13
11
 
@@ -45,14 +43,13 @@ module RedmineCrm
45
43
  alias_method(new_method, method)
46
44
  end
47
45
  end
48
-
49
46
  end
50
47
  end
51
48
 
52
49
  attr_accessor :vote_registered
53
50
 
54
51
  def vote_registered?
55
- return self.vote_registered
52
+ self.vote_registered
56
53
  end
57
54
 
58
55
  def default_conditions
@@ -63,8 +60,7 @@ module RedmineCrm
63
60
  end
64
61
 
65
62
  # voting
66
- def vote_by args = {}
67
-
63
+ def vote_by(args = {})
68
64
  options = {
69
65
  :vote => true,
70
66
  :vote_scope => nil
@@ -72,71 +68,68 @@ module RedmineCrm
72
68
 
73
69
  self.vote_registered = false
74
70
 
75
- if options[:voter].nil?
76
- return false
77
- end
71
+ return false if options[:voter].nil?
78
72
 
79
73
  # 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
- })
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)
85
77
 
86
- if _votes_.count == 0 or options[:duplicate]
78
+ if votes_for.count == 0 || options[:duplicate]
87
79
  # 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
- )
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 = RedmineCrm::ActsAsVotable::Vote.new(vote_params)
93
83
  else
94
84
  # this voter is potentially changing his vote
95
- vote = _votes_.last
85
+ vote = votes_for.last
96
86
  end
97
87
 
98
- last_update = vote.updated_at
99
-
100
88
  vote.vote_flag = votable_words.meaning_of(options[:vote])
101
89
 
102
- #Allowing for a vote_weight to be associated with every vote. Could change with every voter object
90
+ # Allowing for a vote_weight to be associated with every vote. Could change with every voter object
103
91
  vote.vote_weight = (options[:vote_weight].to_i if options[:vote_weight].present?) || 1
104
92
 
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
93
+ return false if vote.invalid?
113
94
 
95
+ self.vote_registered = vote.changed?
96
+ vote.save!(validate: false)
97
+ update_cached_votes(options[:vote_scope])
98
+ vote
114
99
  end
115
100
 
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)
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)
119
106
 
120
- return true if _votes_.size == 0
121
- _votes_.each(&:destroy)
107
+ return true if votes_for.empty?
108
+ votes_for.each(&:destroy)
122
109
  update_cached_votes args[:vote_scope]
123
110
  self.vote_registered = false if votes_for.count == 0
124
- return true
111
+ true
125
112
  end
126
113
 
127
- def vote_up voter, options={}
128
- self.vote_by :voter => voter, :vote => true, :vote_scope => options[:vote_scope], :vote_weight => options[:vote_weight]
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]
129
118
  end
130
119
 
131
- def vote_down voter, options={}
132
- self.vote_by :voter => voter, :vote => false, :vote_scope => options[:vote_scope], :vote_weight => options[:vote_weight]
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]
133
124
  end
134
125
 
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
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]
137
130
  end
138
131
 
139
- def scope_cache_field field, vote_scope
132
+ def scope_cache_field(field, vote_scope)
140
133
  return field if vote_scope.nil?
141
134
 
142
135
  case field
@@ -172,8 +165,7 @@ module RedmineCrm
172
165
  end
173
166
 
174
167
  # caching
175
- def update_cached_votes vote_scope = nil
176
-
168
+ def update_cached_votes(vote_scope = nil)
177
169
  updates = {}
178
170
 
179
171
  if self.respond_to?(:cached_votes_total=)
@@ -239,55 +231,52 @@ module RedmineCrm
239
231
  updates[scope_cache_field :cached_weighted_average, vote_scope] = weighted_average(true, vote_scope)
240
232
  end
241
233
  end
242
-
234
+ self.record_timestamps = false
243
235
  if (::ActiveRecord::VERSION::MAJOR == 3) && (::ActiveRecord::VERSION::MINOR != 0)
244
- self.update_attributes(updates, :without_protection => true) if updates.size > 0
236
+ self.update_attributes(updates, :without_protection => true) if !updates.empty?
245
237
  else
246
- self.update_attributes(updates) if updates.size > 0
238
+ self.update_attributes(updates) if !updates.empty?
247
239
  end
248
-
249
240
  end
250
241
 
251
-
252
242
  # results
253
- def find_votes_for extra_conditions = {}
243
+ def find_votes_for(extra_conditions = {})
254
244
  votes_for.where(extra_conditions)
255
245
  end
256
246
 
257
- def get_up_votes options={}
247
+ def get_up_votes(options = {})
258
248
  vote_scope_hash = scope_or_empty_hash(options[:vote_scope])
259
249
  find_votes_for({:vote_flag => true}.merge(vote_scope_hash))
260
250
  end
261
251
 
262
- def get_down_votes options={}
252
+ def get_down_votes(options = {})
263
253
  vote_scope_hash = scope_or_empty_hash(options[:vote_scope])
264
- find_votes_for({:vote_flag => false}.merge(vote_scope_hash))
254
+ find_votes_for({ :vote_flag => false }.merge(vote_scope_hash))
265
255
  end
266
256
 
267
-
268
257
  # counting
269
- def count_votes_total skip_cache = false, vote_scope = nil
258
+ def count_votes_total(skip_cache = false, vote_scope = nil)
270
259
  if !skip_cache && self.respond_to?(scope_cache_field :cached_votes_total, vote_scope)
271
260
  return self.send(scope_cache_field :cached_votes_total, vote_scope)
272
261
  end
273
262
  find_votes_for(scope_or_empty_hash(vote_scope)).count
274
263
  end
275
264
 
276
- def count_votes_up skip_cache = false, vote_scope = nil
265
+ def count_votes_up(skip_cache = false, vote_scope = nil)
277
266
  if !skip_cache && self.respond_to?(scope_cache_field :cached_votes_up, vote_scope)
278
267
  return self.send(scope_cache_field :cached_votes_up, vote_scope)
279
268
  end
280
269
  get_up_votes(:vote_scope => vote_scope).count
281
270
  end
282
271
 
283
- def count_votes_down skip_cache = false, vote_scope = nil
272
+ def count_votes_down(skip_cache = false, vote_scope = nil)
284
273
  if !skip_cache && self.respond_to?(scope_cache_field :cached_votes_down, vote_scope)
285
274
  return self.send(scope_cache_field :cached_votes_down, vote_scope)
286
275
  end
287
276
  get_down_votes(:vote_scope => vote_scope).count
288
277
  end
289
278
 
290
- def weighted_total skip_cache = false, vote_scope = nil
279
+ def weighted_total(skip_cache = false, vote_scope = nil)
291
280
  if !skip_cache && self.respond_to?(scope_cache_field :cached_weighted_total, vote_scope)
292
281
  return self.send(scope_cache_field :cached_weighted_total, vote_scope)
293
282
  end
@@ -296,7 +285,7 @@ module RedmineCrm
296
285
  ups + downs
297
286
  end
298
287
 
299
- def weighted_score skip_cache = false, vote_scope = nil
288
+ def weighted_score(skip_cache = false, vote_scope = nil)
300
289
  if !skip_cache && self.respond_to?(scope_cache_field :cached_weighted_score, vote_scope)
301
290
  return self.send(scope_cache_field :cached_weighted_score, vote_scope)
302
291
  end
@@ -305,7 +294,7 @@ module RedmineCrm
305
294
  ups - downs
306
295
  end
307
296
 
308
- def weighted_average skip_cache = false, vote_scope = nil
297
+ def weighted_average(skip_cache = false, vote_scope = nil)
309
298
  if !skip_cache && self.respond_to?(scope_cache_field :cached_weighted_average, vote_scope)
310
299
  return self.send(scope_cache_field :cached_weighted_average, vote_scope)
311
300
  end
@@ -319,7 +308,7 @@ module RedmineCrm
319
308
  end
320
309
 
321
310
  # voters
322
- def voted_on_by? voter
311
+ def voted_on_by?(voter)
323
312
  votes = find_votes_for :voter_id => voter.id, :voter_type => voter.class.base_class.name
324
313
  votes.count > 0
325
314
  end
@@ -3,28 +3,26 @@ require 'redmine_crm/helpers/vote_helper'
3
3
  module RedmineCrm
4
4
  module ActsAsVotable
5
5
  class Vote < ActiveRecord::Base
6
-
7
6
  include Helpers::Words
8
7
 
9
8
  if defined?(ProtectedAttributes) || ::ActiveRecord::VERSION::MAJOR < 4
10
9
  attr_accessible :votable_id, :votable_type,
11
10
  :voter_id, :voter_type,
12
11
  :votable, :voter,
13
- :vote_flag, :vote_scope
12
+ :vote_flag, :vote_scope,
13
+ :vote_ip
14
14
  end
15
15
 
16
16
  belongs_to :votable, :polymorphic => true
17
17
  belongs_to :voter, :polymorphic => true
18
18
 
19
- scope :up, lambda{ where(:vote_flag => true) }
20
- scope :down, lambda{ where(:vote_flag => false) }
21
- scope :for_type, lambda{ |klass| where(:votable_type => klass) }
22
- scope :by_type, lambda{ |klass| where(:voter_type => klass) }
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
23
 
24
24
  validates_presence_of :votable_id
25
25
  validates_presence_of :voter_id
26
-
27
26
  end
28
-
29
27
  end
30
28
  end