test_redmine_vz 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/Gemfile +4 -0
  4. data/README.md +71 -0
  5. data/Rakefile +20 -0
  6. data/config/currency_iso.json +2532 -0
  7. data/doc/CHANGELOG +55 -0
  8. data/doc/LICENSE.txt +339 -0
  9. data/lib/redmine_crm.rb +67 -0
  10. data/lib/redmine_crm/currency.rb +439 -0
  11. data/lib/redmine_crm/currency/formatting.rb +227 -0
  12. data/lib/redmine_crm/currency/heuristics.rb +151 -0
  13. data/lib/redmine_crm/currency/loader.rb +24 -0
  14. data/lib/redmine_crm/helpers/tags_helper.rb +15 -0
  15. data/lib/redmine_crm/helpers/vote_helper.rb +38 -0
  16. data/lib/redmine_crm/liquid/drops/issues_drop.rb +61 -0
  17. data/lib/redmine_crm/liquid/drops/news_drop.rb +45 -0
  18. data/lib/redmine_crm/liquid/drops/projects_drop.rb +78 -0
  19. data/lib/redmine_crm/liquid/drops/users_drop.rb +59 -0
  20. data/lib/redmine_crm/liquid/filters.rb +85 -0
  21. data/lib/redmine_crm/money_helper.rb +67 -0
  22. data/lib/redmine_crm/rcrm_acts_as_taggable.rb +342 -0
  23. data/lib/redmine_crm/rcrm_acts_as_viewed.rb +287 -0
  24. data/lib/redmine_crm/rcrm_acts_as_votable.rb +79 -0
  25. data/lib/redmine_crm/rcrm_acts_as_voter.rb +27 -0
  26. data/lib/redmine_crm/tag.rb +81 -0
  27. data/lib/redmine_crm/tag_list.rb +112 -0
  28. data/lib/redmine_crm/tagging.rb +20 -0
  29. data/lib/redmine_crm/version.rb +3 -0
  30. data/lib/redmine_crm/votable.rb +334 -0
  31. data/lib/redmine_crm/vote.rb +30 -0
  32. data/lib/redmine_crm/voter.rb +136 -0
  33. data/redmine_crm.gemspec +22 -0
  34. data/test/acts_as_taggable_test.rb +384 -0
  35. data/test/currency_test.rb +292 -0
  36. data/test/database.yml +17 -0
  37. data/test/fixtures/issue.rb +14 -0
  38. data/test/fixtures/issues.yml +12 -0
  39. data/test/fixtures/taggings.yml +32 -0
  40. data/test/fixtures/tags.yml +11 -0
  41. data/test/fixtures/user.rb +7 -0
  42. data/test/fixtures/users.yml +5 -0
  43. data/test/fixtures/votable_caches.yml +2 -0
  44. data/test/fixtures/votables.yml +4 -0
  45. data/test/fixtures/vote_classes.rb +54 -0
  46. data/test/fixtures/voters.yml +6 -0
  47. data/test/liquid_test.rb +80 -0
  48. data/test/money_helper_test.rb +12 -0
  49. data/test/schema.rb +100 -0
  50. data/test/tag_test.rb +63 -0
  51. data/test/tagging_test.rb +14 -0
  52. data/test/tags_helper_test.rb +29 -0
  53. data/test/test_helper.rb +118 -0
  54. data/test/viewed_test.rb +45 -0
  55. data/test/votable_model_test.rb +478 -0
  56. data/test/votable_test.rb +17 -0
  57. data/test/vote_helper_test.rb +28 -0
  58. data/test/voter_model_test.rb +296 -0
  59. metadata +141 -0
@@ -0,0 +1,85 @@
1
+ module RedmineCrm
2
+ module Liquid
3
+ module Filters
4
+ # include ApplicationHelper
5
+ # include Rails.application.routes.url_helpers
6
+
7
+ def textilize(input)
8
+ RedCloth3.new(input).to_html
9
+ end
10
+
11
+ # example:
12
+ # {{ today | plus_days: 2 }}
13
+ def plus_days(input, distanse)
14
+ return '' if input.nil?
15
+ days = distanse.to_i
16
+ input.to_date + days.days rescue 'Invalid date'
17
+ end
18
+
19
+ # example:
20
+ # {{ today | date_range: '2015-12-12' }}
21
+ def date_range(input, distanse)
22
+ return '' if input.nil?
23
+ (input.to_date - distanse.to_date).to_i rescue 'Invalid date'
24
+ end
25
+
26
+ # example:
27
+ # {{ now | utc }}
28
+ def utc(input)
29
+ return '' if input.nil?
30
+ input.to_time.utc rescue 'Invalid date'
31
+ end
32
+
33
+ def default(input, value)
34
+ input.blank? ? value : input
35
+ end
36
+
37
+ def underscore(input)
38
+ input.to_s.gsub(' ', '_').gsub('/', '_').underscore
39
+ end
40
+
41
+ def dasherize(input)
42
+ input.to_s.gsub(' ', '-').gsub('/', '-').dasherize
43
+ end
44
+
45
+ def shuffle(array)
46
+ array.to_a.shuffle
47
+ end
48
+
49
+ def random(input)
50
+ rand(input.to_i)
51
+ end
52
+
53
+ # example:
54
+ # {{ "http:://www.example.com?key=hello world" | encode }}
55
+ #
56
+ # => http%3A%3A%2F%2Fwww.example.com%3Fkey%3Dhello+world
57
+ def encode(input)
58
+ Rack::Utils.escape(input)
59
+ end
60
+
61
+ protected
62
+
63
+ # Convert an array of properties ('key:value') into a hash
64
+ # Ex: ['width:50', 'height:100'] => { :width => '50', :height => '100' }
65
+ def args_to_options(*args)
66
+ options = {}
67
+ args.flatten.each do |a|
68
+ if a =~ /^(.*):(.*)$/
69
+ options[$1.to_sym] = $2
70
+ end
71
+ end
72
+ options
73
+ end
74
+
75
+ # Write options (Hash) into a string according to the following pattern:
76
+ # <key1>="<value1>", <key2>="<value2", ...etc
77
+ def inline_options(options = {})
78
+ return '' if options.empty?
79
+ (options.stringify_keys.sort.to_a.collect { |a, b| "#{a}=\"#{b}\"" }).join(' ') << ' '
80
+ end
81
+ end
82
+ end
83
+
84
+ ::Liquid::Template.register_filter(RedmineCrm::Liquid::Filters)
85
+ end
@@ -0,0 +1,67 @@
1
+ # byebug
2
+ # require 'action_controller'
3
+ require 'action_view'
4
+
5
+ module RedmineCrm
6
+ module MoneyHelper
7
+
8
+ def object_price(obj, price_field = :price, options = {})
9
+ options.merge!({:symbol => true})
10
+ price_to_currency(obj.try(price_field), obj.currency, options).to_s if obj.respond_to?(:currency)
11
+ end
12
+
13
+ def prices_collection_by_currency(prices_collection, options={})
14
+ return [] if prices_collection.blank? || prices_collection == 0
15
+ prices = prices_collection
16
+ prices.reject!{|k, v| v.to_i == 0} if options[:hide_zeros]
17
+ prices.collect{|k, v| content_tag(:span, price_to_currency(v, k, :symbol => true), :style => "white-space: nowrap;")}.compact
18
+ end
19
+
20
+ def deal_currency_icon(currency)
21
+ case currency.to_s.upcase
22
+ when 'EUR'
23
+ "icon-money-euro"
24
+ when 'GBP'
25
+ "icon-money-pound"
26
+ when 'JPY'
27
+ "icon-money-yen"
28
+ else
29
+ "icon-money-dollar"
30
+ end
31
+ end
32
+
33
+ def collection_for_currencies_select(default_currency = 'USD', major_currencies = %w(USD EUR GBP RUB CHF))
34
+ currencies = []
35
+ currencies << default_currency.to_s unless default_currency.blank?
36
+ currencies |= major_currencies
37
+ currencies.map do |c|
38
+ currency = RedmineCrm::Currency.find(c)
39
+ ["#{currency.iso_code} (#{currency.symbol})", currency.iso_code] if currency
40
+ end.compact.uniq
41
+ end
42
+
43
+ def all_currencies
44
+ Currency.table.inject([]) do |array, (id, attributes)|
45
+ array ||= []
46
+ array << ["#{attributes[:name]}" + (attributes[:symbol].blank? ? "" : " (#{attributes[:symbol]})"), attributes[:iso_code]]
47
+ array
48
+ end.sort{|x, y| x[0] <=> y[0]}
49
+ end
50
+
51
+ def price_to_currency(price, currency="USD", options={})
52
+ return '' if price.blank?
53
+
54
+ currency = "USD" unless currency.is_a?(String)
55
+ currency = RedmineCrm::Currency.find(currency)
56
+
57
+ return RedmineCrm::Currency.format(price.to_f, currency, options)# if currency
58
+ price.to_s
59
+ end
60
+
61
+
62
+ end
63
+ end
64
+
65
+ unless ActionView::Base.included_modules.include?(RedmineCrm::MoneyHelper)
66
+ ActionView::Base.send(:include, RedmineCrm::MoneyHelper)
67
+ end
@@ -0,0 +1,342 @@
1
+ require 'active_record'
2
+
3
+ # module ActiveRecord #:nodoc:
4
+ module RedmineCrm
5
+ module Acts #:nodoc:
6
+ module Taggable #:nodoc:
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ def taggable?
14
+ false
15
+ end
16
+
17
+ def rcrm_acts_as_taggable
18
+ has_many :taggings, :as => :taggable, :dependent => :destroy, :class_name => '::RedmineCrm::Tagging'#, :include => :tag
19
+ has_many :tags, :through => :taggings, :class_name => '::RedmineCrm::Tag'
20
+
21
+ before_save :save_cached_tag_list
22
+
23
+ after_create :save_tags
24
+ after_update :save_tags
25
+
26
+ include RedmineCrm::Acts::Taggable::InstanceMethods
27
+ extend RedmineCrm::Acts::Taggable::SingletonMethods
28
+
29
+ alias_method_chain :reload, :tag_list
30
+
31
+ class_eval do
32
+ def self.taggable?
33
+ true
34
+ end
35
+ end
36
+ end
37
+
38
+ def cached_tag_list_column_name
39
+ "cached_tag_list"
40
+ end
41
+
42
+ def set_cached_tag_list_column_name(value = nil, &block)
43
+ define_attr_method :cached_tag_list_column_name, value, &block
44
+ end
45
+
46
+ # Create the taggable tables
47
+ # === Options hash:
48
+ # * <tt>:table_name</tt> - use a table name other than viewings
49
+ # To be used during migration, but can also be used in other places
50
+ def create_taggable_table options = {}
51
+ tag_name_table = options[:tags] || :tags
52
+
53
+ if !self.connection.table_exists?(tag_name_table)
54
+ self.connection.create_table(tag_name_table) do |t|
55
+ t.column :name, :string
56
+ end
57
+ end
58
+
59
+ taggings_name_table = options[:taggings] || :taggings
60
+ if !self.connection.table_exists?(taggings_name_table)
61
+ self.connection.create_table(taggings_name_table) do |t|
62
+ t.column :tag_id, :integer
63
+ t.column :taggable_id, :integer
64
+
65
+ # You should make sure that the column created is
66
+ # long enough to store the required class names.
67
+ t.column :taggable_type, :string
68
+
69
+ t.column :created_at, :datetime
70
+ end
71
+
72
+ self.connection.add_index :taggings, :tag_id
73
+ self.connection.add_index :taggings, [:taggable_id, :taggable_type]
74
+ end
75
+
76
+ end
77
+
78
+ def drop_taggable_table options = {}
79
+ tag_name_table = options[:tags] || :tags
80
+ if self.connection.table_exists?(tag_name_table)
81
+ self.connection.drop_table tag_name_table
82
+ end
83
+ taggings_name_table = options[:taggings] || :taggings
84
+ if self.connection.table_exists?(taggings_name_table)
85
+ self.connection.drop_table taggings_name_table
86
+ end
87
+
88
+ end
89
+ end
90
+
91
+ module SingletonMethods
92
+ #Return all avalible tags for a project or global
93
+ #Example: Question.available_tags(@project_id )
94
+ def available_tags(project=nil, limit=30)
95
+ scope = Tag.where({})
96
+ class_name = quote_value(base_class.name)
97
+ join = []
98
+ join << "JOIN #{Tagging.table_name} ON #{Tagging.table_name}.tag_id = #{Tag.table_name}.id "
99
+ join << "JOIN #{table_name} ON #{table_name}.id = #{Tagging.table_name}.taggable_id
100
+ AND #{Tagging.table_name}.taggable_type = #{class_name} "
101
+ if self.attribute_names.include? "project_id"
102
+ if project
103
+ join << "JOIN #{Project.table_name} ON #{Project.table_name}.id = #{table_name}.project_id"
104
+ else
105
+ scope = scope.where("#{table_name}.project_id IS NULL")
106
+ end
107
+ end
108
+
109
+ group_fields = ""
110
+ group_fields << ", #{Tag.table_name}.created_at" if Tag.respond_to?(:created_at)
111
+ group_fields << ", #{Tag.table_name}.updated_at" if Tag.respond_to?(:updated_at)
112
+
113
+ scope = scope.joins(join.join(' '))
114
+ scope = scope.select("#{Tag.table_name}.*, COUNT(DISTINCT #{Tagging.table_name}.taggable_id) AS count")
115
+ scope = scope.group("#{Tag.table_name}.id, #{Tag.table_name}.name #{group_fields} HAVING COUNT(*) > 0")
116
+ scope = scope.order("#{Tag.table_name}.name")
117
+ scope = scope.limit(limit) if limit
118
+ scope
119
+ end
120
+ # Returns an array of related tags.
121
+ # Related tags are all the other tags that are found on the models tagged with the provided tags.
122
+ #
123
+ # Pass either a tag, string, or an array of strings or tags.
124
+ #
125
+ # Options:
126
+ # :order - SQL Order how to order the tags. Defaults to "count DESC, tags.name".
127
+ def find_related_tags(tags, options = {})
128
+ tags = tags.is_a?(Array) ? TagList.new(tags.map(&:to_s)) : TagList.from(tags)
129
+
130
+ related_models = find_tagged_with(tags)
131
+
132
+ return [] if related_models.blank?
133
+
134
+ related_ids = related_models.map{|c| c.id }.join(",")
135
+ Tag.select( #find(:all, options.merge({
136
+ "#{Tag.table_name}.*, COUNT(#{Tag.table_name}.id) AS count").joins(
137
+ "JOIN #{Tagging.table_name} ON #{Tagging.table_name}.taggable_type = '#{base_class.name}'
138
+ AND #{Tagging.table_name}.taggable_id IN (#{related_ids})
139
+ AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id").order(
140
+ options[:order] || "count DESC, #{Tag.table_name}.name").group(
141
+ "#{Tag.table_name}.id, #{Tag.table_name}.name HAVING #{Tag.table_name}.name NOT IN (#{tags.map { |n| quote_value(n) }.join(",")})")
142
+ # }))
143
+ end
144
+
145
+ # Pass either a tag, string, or an array of strings or tags.
146
+ #
147
+ # Options:
148
+ # :exclude - Find models that are not tagged with the given tags
149
+ # :match_all - Find models that match all of the given tags, not just one
150
+ # :conditions - A piece of SQL conditions to add to the query
151
+ def find_tagged_with(*args)
152
+ options = find_options_for_find_tagged_with(*args)
153
+ options.blank? ? [] : select(options[:select]).where(options[:conditions]).joins(options[:joins]).order(options[:order]).to_a
154
+ # find(:all, options)
155
+ end
156
+ alias_method :tagged_with, :find_tagged_with
157
+
158
+ def find_options_for_find_tagged_with(tags, options = {})
159
+ tags = tags.is_a?(Array) ? TagList.new(tags.map(&:to_s)) : TagList.from(tags)
160
+ options = options.dup
161
+
162
+ return {} if tags.empty?
163
+
164
+ conditions = []
165
+ conditions << sanitize_sql(options.delete(:conditions)) if options[:conditions]
166
+
167
+ taggings_alias, tags_alias = "#{table_name}_taggings", "#{table_name}_tags"
168
+
169
+ joins = [
170
+ "INNER JOIN #{Tagging.table_name} #{taggings_alias} ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key} AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}",
171
+ "INNER JOIN #{Tag.table_name} #{tags_alias} ON #{tags_alias}.id = #{taggings_alias}.tag_id"
172
+ ]
173
+
174
+ if options.delete(:exclude)
175
+ conditions << <<-END
176
+ #{table_name}.id NOT IN
177
+ (SELECT #{Tagging.table_name}.taggable_id FROM #{Tagging.table_name}
178
+ INNER JOIN #{Tag.table_name} ON #{Tagging.table_name}.tag_id = #{Tag.table_name}.id
179
+ WHERE #{tags_condition(tags)} AND #{Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})
180
+ END
181
+ else
182
+ if options.delete(:match_all)
183
+ joins << joins_for_match_all_tags(tags)
184
+ else
185
+ conditions << tags_condition(tags, tags_alias)
186
+ end
187
+ end
188
+
189
+ { :select => "DISTINCT #{table_name}.* ",
190
+ :joins => joins.join(" "),
191
+ :conditions => conditions.join(" AND ")
192
+ }.reverse_merge!(options)
193
+ end
194
+
195
+ def joins_for_match_all_tags(tags)
196
+ joins = []
197
+
198
+ tags.each_with_index do |tag, index|
199
+ taggings_alias, tags_alias = "taggings_#{index}", "tags_#{index}"
200
+
201
+ join = <<-END
202
+ INNER JOIN #{Tagging.table_name} #{taggings_alias} ON
203
+ #{taggings_alias}.taggable_id = #{table_name}.#{primary_key} AND
204
+ #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}
205
+
206
+ INNER JOIN #{Tag.table_name} #{tags_alias} ON
207
+ #{taggings_alias}.tag_id = #{tags_alias}.id AND
208
+ #{tags_alias}.name = ?
209
+ END
210
+
211
+ joins << sanitize_sql([join, tag])
212
+ end
213
+
214
+ joins.join(" ")
215
+ end
216
+
217
+ # Calculate the tag counts for all tags.
218
+ #
219
+ # See Tag.counts for available options.
220
+ def tag_counts(options = {})
221
+ # Tag.find(:all, find_options_for_tag_counts(options))
222
+ opt = find_options_for_tag_counts(options)
223
+ Tag.select(opt[:select]).where(opt[:conditions]).joins(opt[:joins]).group(opt[:group]).having(opt[:having]).order(opt[:order]).limit(options[:limit])
224
+ end
225
+ alias_method :all_tag_counts, :tag_counts
226
+
227
+ def find_options_for_tag_counts(options = {})
228
+ options = options.dup
229
+ scope = scope_attributes
230
+ # scope(:find)
231
+
232
+ conditions = []
233
+ conditions << send(:sanitize_conditions, options.delete(:conditions)) if options[:conditions]
234
+ conditions << send(:sanitize_conditions, scope) if scope
235
+ conditions << "#{Tagging.table_name}.taggable_type = #{quote_value(base_class.name)}"
236
+ conditions << type_condition unless descends_from_active_record?
237
+ conditions.compact!
238
+ conditions = conditions.join(" AND ")
239
+
240
+ joins = ["INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{Tagging.table_name}.taggable_id"]
241
+ joins << options.delete(:joins) if options[:joins].present?
242
+ # joins << scope[:joins] if scope && scope[:joins].present?
243
+ joins = joins.join(" ")
244
+
245
+ options = { :conditions => conditions, :joins => joins }.update(options)
246
+
247
+ Tag.options_for_counts(options)
248
+ end
249
+
250
+ def caching_tag_list?
251
+ column_names.include?(cached_tag_list_column_name)
252
+ end
253
+
254
+ private
255
+ def quote_value(object)
256
+ sanitize(object)
257
+ end
258
+
259
+ def tags_condition(tags, table_name = Tag.table_name)
260
+ condition = tags.map { |t| sanitize_sql(["#{table_name}.name LIKE ?", t]) }.join(" OR ")
261
+ "(" + condition + ")" unless condition.blank?
262
+ end
263
+
264
+ def merge_conditions(*conditions)
265
+ segments = []
266
+
267
+ conditions.each do |condition|
268
+ unless condition.blank?
269
+ sql = sanitize_sql(condition)
270
+ segments << sql unless sql.blank?
271
+ end
272
+ end
273
+
274
+ "(#{segments.join(') AND (')})" unless segments.empty?
275
+ end
276
+ end
277
+
278
+ module InstanceMethods
279
+ def tag_list
280
+ return @tag_list if @tag_list
281
+
282
+ if self.class.caching_tag_list? and !(cached_value = send(self.class.cached_tag_list_column_name)).nil?
283
+ @tag_list = TagList.from(cached_value)
284
+ else
285
+ @tag_list = TagList.new(*tags.map(&:name))
286
+ end
287
+ end
288
+
289
+ def tag_list=(value)
290
+ @tag_list = TagList.from(value)
291
+ end
292
+
293
+ def save_cached_tag_list
294
+ if self.class.caching_tag_list?
295
+ self[self.class.cached_tag_list_column_name] = tag_list.to_s
296
+ end
297
+ end
298
+
299
+ #build list from related tags
300
+ def all_tags_list
301
+ tags.pluck(:name)
302
+ end
303
+
304
+ def save_tags
305
+ return unless @tag_list
306
+
307
+ new_tag_names = @tag_list - tags.map(&:name)
308
+ old_tags = tags.reject { |tag| @tag_list.include?(tag.name) }
309
+
310
+ self.class.transaction do
311
+ if old_tags.any?
312
+ taggings.where("tag_id IN (?)", old_tags.map(&:id)).each(&:destroy)
313
+ taggings.reset
314
+ end
315
+ new_tag_names.each do |new_tag_name|
316
+ tags << Tag.find_or_create_with_like_by_name(new_tag_name)
317
+ end
318
+ end
319
+
320
+ true
321
+ end
322
+
323
+ # Calculate the tag counts for the tags used by this model.
324
+ #
325
+ # The possible options are the same as the tag_counts class method.
326
+ def tag_counts(options = {})
327
+ return [] if tag_list.blank?
328
+
329
+ options[:conditions] = self.class.send(:merge_conditions, options[:conditions], self.class.send(:tags_condition, tag_list))
330
+ self.class.tag_counts(options)
331
+ end
332
+
333
+ def reload_with_tag_list(*args) #:nodoc:
334
+ @tag_list = nil
335
+ reload_without_tag_list(*args)
336
+ end
337
+ end
338
+ end
339
+ end
340
+ end
341
+
342
+ ActiveRecord::Base.send(:include, RedmineCrm::Acts::Taggable)