yeshoua_crm 1.0.0

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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +11 -0
  3. data/lib/yeshoua_crm.rb +87 -0
  4. data/lib/yeshoua_crm/acts_as_draftable/draft.rb +40 -0
  5. data/lib/yeshoua_crm/acts_as_draftable/rcrm_acts_as_draftable.rb +154 -0
  6. data/lib/yeshoua_crm/acts_as_list/list.rb +282 -0
  7. data/lib/yeshoua_crm/acts_as_taggable/rcrm_acts_as_taggable.rb +350 -0
  8. data/lib/yeshoua_crm/acts_as_taggable/tag.rb +81 -0
  9. data/lib/yeshoua_crm/acts_as_taggable/tag_list.rb +111 -0
  10. data/lib/yeshoua_crm/acts_as_taggable/tagging.rb +16 -0
  11. data/lib/yeshoua_crm/acts_as_viewed/rcrm_acts_as_viewed.rb +274 -0
  12. data/lib/yeshoua_crm/acts_as_votable/rcrm_acts_as_votable.rb +80 -0
  13. data/lib/yeshoua_crm/acts_as_votable/rcrm_acts_as_voter.rb +20 -0
  14. data/lib/yeshoua_crm/acts_as_votable/votable.rb +323 -0
  15. data/lib/yeshoua_crm/acts_as_votable/vote.rb +28 -0
  16. data/lib/yeshoua_crm/acts_as_votable/voter.rb +131 -0
  17. data/lib/yeshoua_crm/assets_manager.rb +43 -0
  18. data/lib/yeshoua_crm/currency.rb +439 -0
  19. data/lib/yeshoua_crm/currency/formatting.rb +224 -0
  20. data/lib/yeshoua_crm/currency/heuristics.rb +151 -0
  21. data/lib/yeshoua_crm/currency/loader.rb +24 -0
  22. data/lib/yeshoua_crm/helpers/external_assets_helper.rb +17 -0
  23. data/lib/yeshoua_crm/helpers/form_tag_helper.rb +123 -0
  24. data/lib/yeshoua_crm/helpers/tags_helper.rb +13 -0
  25. data/lib/yeshoua_crm/helpers/vote_helper.rb +35 -0
  26. data/lib/yeshoua_crm/liquid/drops/cells_drop.rb +86 -0
  27. data/lib/yeshoua_crm/liquid/drops/issues_drop.rb +66 -0
  28. data/lib/yeshoua_crm/liquid/drops/news_drop.rb +54 -0
  29. data/lib/yeshoua_crm/liquid/drops/users_drop.rb +72 -0
  30. data/lib/yeshoua_crm/liquid/filters/arrays.rb +177 -0
  31. data/lib/yeshoua_crm/liquid/filters/base.rb +208 -0
  32. data/lib/yeshoua_crm/money_helper.rb +65 -0
  33. data/lib/yeshoua_crm/version.rb +3 -0
  34. data/test/acts_as_draftable/draft_test.rb +29 -0
  35. data/test/acts_as_draftable/rcrm_acts_as_draftable_test.rb +185 -0
  36. data/test/acts_as_taggable/rcrm_acts_as_taggable_test.rb +345 -0
  37. data/test/acts_as_taggable/tag_list_test.rb +34 -0
  38. data/test/acts_as_taggable/tag_test.rb +72 -0
  39. data/test/acts_as_taggable/tagging_test.rb +15 -0
  40. data/test/acts_as_viewed/rcrm_acts_as_viewed_test.rb +47 -0
  41. data/test/acts_as_votable/rcrm_acts_as_votable_test.rb +19 -0
  42. data/test/acts_as_votable/rcrm_acts_as_voter_test.rb +14 -0
  43. data/test/acts_as_votable/votable_test.rb +507 -0
  44. data/test/acts_as_votable/voter_test.rb +296 -0
  45. data/test/currency_test.rb +292 -0
  46. data/test/liquid/drops/issues_drop_test.rb +34 -0
  47. data/test/liquid/drops/news_drop_test.rb +38 -0
  48. data/test/liquid/drops/projects_drop_test.rb +44 -0
  49. data/test/liquid/drops/uses_drop_test.rb +36 -0
  50. data/test/liquid/filters/arrays_filter_test.rb +24 -0
  51. data/test/liquid/filters/base_filter_test.rb +63 -0
  52. data/test/liquid/liquid_helper.rb +32 -0
  53. data/test/models/issue.rb +14 -0
  54. data/test/models/news.rb +3 -0
  55. data/test/models/project.rb +8 -0
  56. data/test/models/user.rb +11 -0
  57. data/test/models/vote_classes.rb +33 -0
  58. data/test/money_helper_test.rb +12 -0
  59. data/test/schema.rb +121 -0
  60. data/test/tags_helper_test.rb +29 -0
  61. data/test/test_helper.rb +66 -0
  62. data/test/vote_helper_test.rb +28 -0
  63. data/yeshoua_crm.gemspec +28 -0
  64. metadata +206 -0
@@ -0,0 +1,208 @@
1
+ require 'uri'
2
+ require 'rack'
3
+ require 'json'
4
+ require 'date'
5
+ require 'liquid'
6
+
7
+ module YeshouaCrm
8
+ module Liquid
9
+ module Filters
10
+ module Base
11
+ include YeshouaCrm::MoneyHelper
12
+
13
+ def textilize(input)
14
+ RedCloth3.new(input).to_html
15
+ end
16
+
17
+ def default(input, value)
18
+ input.blank? ? value : input
19
+ end
20
+
21
+ def underscore(input)
22
+ input.to_s.gsub(' ', '_').gsub('/', '_').underscore
23
+ end
24
+
25
+ def dasherize(input)
26
+ input.to_s.gsub(' ', '-').gsub('/', '-').dasherize
27
+ end
28
+
29
+ def shuffle(array)
30
+ array.to_a.shuffle
31
+ end
32
+
33
+ def random(input)
34
+ rand(input.to_i)
35
+ end
36
+
37
+ def md5(input)
38
+ Digest::MD5.hexdigest(input) unless input.blank?
39
+ end
40
+
41
+ # example:
42
+ # {{ "http:://www.example.com?key=hello world" | encode }}
43
+ #
44
+ # => http%3A%3A%2F%2Fwww.example.com%3Fkey%3Dhello+world
45
+ def encode(input)
46
+ ::Rack::Utils.escape(input)
47
+ end
48
+
49
+ # example:
50
+ # {{ today | plus_days: 2 }}
51
+ def plus_days(input, distanse)
52
+ return '' if input.nil?
53
+ days = distanse.to_i
54
+ input.to_date + days.days rescue 'Invalid date'
55
+ end
56
+
57
+ # example:
58
+ # {{ today | date_range: '2015-12-12' }}
59
+ def date_range(input, distanse)
60
+ return '' if input.nil?
61
+ (input.to_date - distanse.to_date).to_i rescue 'Invalid date'
62
+ end
63
+
64
+ # example:
65
+ # {{ now | utc }}
66
+ def utc(input)
67
+ return '' if input.nil?
68
+ input.to_time.utc rescue 'Invalid date'
69
+ end
70
+
71
+ def modulo(input, operand)
72
+ apply_operation(input, operand, :%)
73
+ end
74
+
75
+ def round(input, n = 0)
76
+ result = to_number(input).round(to_number(n))
77
+ result = result.to_f if result.is_a?(BigDecimal)
78
+ result = result.to_i if n == 0
79
+ result
80
+ end
81
+
82
+ def ceil(input)
83
+ to_number(input).ceil.to_i
84
+ end
85
+
86
+ def floor(input)
87
+ to_number(input).floor.to_i
88
+ end
89
+
90
+ def currency(input, currency_code = 'BRL')
91
+ price_to_currency(input, currency_code, :converted => false)
92
+ end
93
+
94
+ def call_method(input, method_name)
95
+ if input.respond_to?(method_name)
96
+ input.method(method_name).call
97
+ end
98
+ end
99
+
100
+ def custom_field(input, field_name)
101
+ if input.respond_to?(:custom_fields)
102
+ input.custom_fields[field_name]
103
+ end
104
+ end
105
+
106
+ def attachment(input, file_name)
107
+ if input.respond_to?(:attachments)
108
+ if input.attachments.is_a?(Hash)
109
+ attachment = input.attachments[file_name]
110
+ else
111
+ attachment = input.attachments.detect{|a| a.file_name == file_name}
112
+ end
113
+ AttachmentDrop.new attachment if attachment
114
+ end
115
+ end
116
+
117
+ protected
118
+
119
+ # Convert an array of properties ('key:value') into a hash
120
+ # Ex: ['width:50', 'height:100'] => { :width => '50', :height => '100' }
121
+ def args_to_options(*args)
122
+ options = {}
123
+ args.flatten.each do |a|
124
+ if (a =~ /^(.*):(.*)$/)
125
+ options[$1.to_sym] = $2
126
+ end
127
+ end
128
+ options
129
+ end
130
+
131
+ # Write options (Hash) into a string according to the following pattern:
132
+ # <key1>="<value1>", <key2>="<value2", ...etc
133
+ def inline_options(options = {})
134
+ return '' if options.empty?
135
+ (options.stringify_keys.sort.to_a.collect { |a, b| "#{a}=\"#{b}\"" }).join(' ') << ' '
136
+ end
137
+
138
+ def sort_input(input, property, order)
139
+ input.sort do |apple, orange|
140
+ apple_property = item_property(apple, property)
141
+ orange_property = item_property(orange, property)
142
+
143
+ if !apple_property.nil? && orange_property.nil?
144
+ - order
145
+ elsif apple_property.nil? && !orange_property.nil?
146
+ + order
147
+ else
148
+ apple_property <=> orange_property
149
+ end
150
+ end
151
+ end
152
+
153
+ def time(input)
154
+ case input
155
+ when Time
156
+ input.clone
157
+ when Date
158
+ input.to_time
159
+ when String
160
+ Time.parse(input) rescue Time.at(input.to_i)
161
+ when Numeric
162
+ Time.at(input)
163
+ else
164
+ raise Errors::InvalidDateError,
165
+ "Invalid Date: '#{input.inspect}' is not a valid datetime."
166
+ end.localtime
167
+ end
168
+
169
+ def groupable?(element)
170
+ element.respond_to?(:group_by)
171
+ end
172
+
173
+ def item_property(item, property)
174
+ if item.respond_to?(:to_liquid)
175
+ item.to_liquid[property.to_s]
176
+ elsif item.respond_to?(:data)
177
+ item.data[property.to_s]
178
+ else
179
+ item[property.to_s]
180
+ end
181
+ end
182
+
183
+ def as_liquid(item)
184
+ case item
185
+ when Hash
186
+ pairs = item.map { |k, v| as_liquid([k, v]) }
187
+ Hash[pairs]
188
+ when Array
189
+ item.map { |i| as_liquid(i) }
190
+ else
191
+ if item.respond_to?(:to_liquid)
192
+ liquidated = item.to_liquid
193
+ # prevent infinite recursion for simple types (which return `self`)
194
+ if liquidated == item
195
+ item
196
+ else
197
+ as_liquid(liquidated)
198
+ end
199
+ else
200
+ item
201
+ end
202
+ end
203
+ end
204
+ end
205
+ ::Liquid::Template.register_filter(YeshouaCrm::Liquid::Filters::Base)
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,65 @@
1
+ require 'action_view'
2
+
3
+ module YeshouaCrm
4
+ module MoneyHelper
5
+
6
+ def object_price(obj, price_field = :price, options = {})
7
+ options.merge!({:symbol => true})
8
+ price_to_currency(obj.try(price_field), obj.currency, options).to_s if obj.respond_to?(:currency)
9
+ end
10
+
11
+ def prices_collection_by_currency(prices_collection, options={})
12
+ return [] if prices_collection.blank? || prices_collection == 0
13
+ prices = prices_collection
14
+ prices.reject!{|k, v| v.to_i == 0} if options[:hide_zeros]
15
+ prices.collect{|k, v| content_tag(:span, price_to_currency(v, k, :symbol => true), :style => "white-space: nowrap;")}.compact
16
+ end
17
+
18
+ def deal_currency_icon(currency)
19
+ case currency.to_s.upcase
20
+ when 'EUR'
21
+ "icon-money-euro"
22
+ when 'GBP'
23
+ "icon-money-pound"
24
+ when 'JPY'
25
+ "icon-money-yen"
26
+ else
27
+ "icon-money-dollar"
28
+ end
29
+ end
30
+
31
+ def collection_for_currencies_select(default_currency = 'BRL', major_currencies = %w(BRL USD EUR GBP RUB CHF))
32
+ currencies = []
33
+ currencies << default_currency.to_s unless default_currency.blank?
34
+ currencies |= major_currencies
35
+ currencies.map do |c|
36
+ currency = YeshouaCrm::Currency.find(c)
37
+ ["#{currency.iso_code} (#{currency.symbol})", currency.iso_code] if currency
38
+ end.compact.uniq
39
+ end
40
+
41
+ def all_currencies
42
+ Currency.table.inject([]) do |array, (id, attributes)|
43
+ array ||= []
44
+ array << ["#{attributes[:name]}" + (attributes[:symbol].blank? ? "" : " (#{attributes[:symbol]})"), attributes[:iso_code]]
45
+ array
46
+ end.sort{|x, y| x[0] <=> y[0]}
47
+ end
48
+
49
+ def price_to_currency(price, currency="BRL", options={})
50
+ return '' if price.blank?
51
+
52
+ currency = "BRL" unless currency.is_a?(String)
53
+ currency = YeshouaCrm::Currency.find(currency)
54
+
55
+ return YeshouaCrm::Currency.format(price.to_f, currency, options)# if currency
56
+ price.to_s
57
+ end
58
+
59
+
60
+ end
61
+ end
62
+
63
+ unless ActionView::Base.included_modules.include?(YeshouaCrm::MoneyHelper)
64
+ ActionView::Base.send(:include, YeshouaCrm::MoneyHelper)
65
+ end
@@ -0,0 +1,3 @@
1
+ module YeshouaCrm
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,29 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ class DraftTest < ActiveSupport::TestCase
4
+ def test_restore
5
+ issue = Issue.new(subject: 'some subject', description: 'some description')
6
+
7
+ issue.save_draft(current_user)
8
+ restored_issue = YeshouaCrm::ActsAsDraftable::Draft.find(issue.draft_id).restore
9
+
10
+ assert_equal issue.subject, restored_issue.subject
11
+ assert_equal issue.description, restored_issue.description
12
+ end
13
+
14
+ def test_restore_all
15
+ first_issue = Issue.new(subject: 'first subject')
16
+ first_issue.save_draft(current_user)
17
+ second_issue = Issue.new(subject: 'second subject')
18
+ second_issue.save_draft(current_user)
19
+
20
+ restored_issues = YeshouaCrm::ActsAsDraftable::Draft.restore_all
21
+ assert_equal [first_issue.subject, second_issue.subject], restored_issues.map(&:subject).sort
22
+ end
23
+
24
+ private
25
+
26
+ def current_user
27
+ users(:sam)
28
+ end
29
+ end
@@ -0,0 +1,185 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ class RcrmActsAsDraftableTest < ActiveSupport::TestCase
4
+ def test_rcrm_acts_as_draftable_without_arguments
5
+ assert_nothing_raised do
6
+ Cell.rcrm_acts_as_draftable
7
+ end
8
+ end
9
+
10
+ class SomeClass < ActiveRecord::Base; end
11
+ def test_rcrm_acts_as_draftable_with_parent
12
+ assert_nothing_raised do
13
+ News.rcrm_acts_as_draftable(parent: :author)
14
+ end
15
+ assert User.new.respond_to?(:drafts)
16
+ end
17
+
18
+ def test_rcrm_acts_as_draftable_with_non_existing_parent
19
+ assert_raises do
20
+ Issue.rcrm_acts_as_draftable(parent: :foo)
21
+ end
22
+ end
23
+
24
+ def test_rcrm_acts_as_draftable_with_non_hash_argument
25
+ assert_raises do
26
+ Issue.rcrm_acts_as_draftable('bar')
27
+ end
28
+ end
29
+
30
+ def test_rcrm_acts_as_draftable_with_invalid_hash_key
31
+ assert_raises do
32
+ Issue.rcrm_acts_as_draftable(baz: 'qux')
33
+ end
34
+ end
35
+
36
+ def test_drafts
37
+ issue = Issue.new
38
+ issue.save_draft(current_user)
39
+
40
+ assert_equal 1, Issue.drafts(current_user).count
41
+ assert_equal YeshouaCrm::ActsAsDraftable::Draft, Issue.drafts(current_user).first.class
42
+ end
43
+
44
+ def test_from_draft
45
+ issue = Issue.new(subject: 'subject')
46
+ issue.save_draft(current_user)
47
+
48
+ issue_from_draft = Issue.from_draft(issue.draft_id)
49
+
50
+ assert_equal Issue, issue_from_draft.class
51
+ assert_equal true, issue_from_draft.new_record?
52
+ assert_equal issue.draft_id, issue_from_draft.draft_id
53
+
54
+ assert_equal issue.subject, issue_from_draft.subject
55
+ end
56
+
57
+ def test_draft_deleted_after_save
58
+ issue = Issue.new(subject: 'subject')
59
+ issue.save_draft(current_user)
60
+
61
+ issue_from_draft = Issue.from_draft(issue.draft_id)
62
+
63
+ assert_difference 'Issue.count', 1 do
64
+ assert_difference 'YeshouaCrm::ActsAsDraftable::Draft.count', -1 do
65
+ issue_from_draft.save!
66
+ end
67
+ end
68
+ assert_nil issue_from_draft.draft_id
69
+ end
70
+
71
+ def test_from_draft_with_non_existing_draft_id
72
+ assert_raises do
73
+ Issue.from_draft(999)
74
+ end
75
+ end
76
+
77
+ def test_from_draft_with_wrong_type
78
+ Cell.rcrm_acts_as_draftable
79
+
80
+ cell = Cell.new
81
+ cell.save_draft(current_user)
82
+
83
+ assert_raises do
84
+ Issue.from_draft(cell.draft_id)
85
+ end
86
+ end
87
+
88
+ def test_dump_to_draft
89
+ assert_equal String, Issue.new.dump_to_draft.class
90
+ end
91
+
92
+ def test_load_to_draft
93
+ attributes = {subject: 'subject', description: 'description'}
94
+ draft = Issue.new(attributes).dump_to_draft
95
+
96
+ issue = Issue.new
97
+ issue.load_from_draft(draft)
98
+
99
+ assert_equal attributes[:subject], issue.subject
100
+ assert_equal attributes[:description], issue.description
101
+ end
102
+
103
+ def test_save_draft
104
+ attributes = {subject: 'subject'}
105
+ issue = Issue.new(attributes)
106
+
107
+ assert_difference 'Issue.count', 0 do
108
+ assert_difference 'YeshouaCrm::ActsAsDraftable::Draft.count', 1 do
109
+ result = issue.save_draft(current_user)
110
+ assert_equal true, result
111
+ end
112
+ end
113
+ assert_equal Fixnum, issue.draft_id.class
114
+
115
+
116
+ draft = YeshouaCrm::ActsAsDraftable::Draft.find(issue.draft_id)
117
+ assert_equal 'Issue', draft.target_type
118
+ assert_equal current_user.id, draft.user_id
119
+ assert_equal attributes[:subject], draft.restore.subject
120
+ end
121
+
122
+ def test_save_draft_without_user
123
+ issue = Issue.new
124
+
125
+ assert_difference 'Issue.count', 0 do
126
+ assert_difference 'YeshouaCrm::ActsAsDraftable::Draft.count', 1 do
127
+ result = issue.save_draft
128
+ assert_equal true, result
129
+ end
130
+ end
131
+
132
+ draft = YeshouaCrm::ActsAsDraftable::Draft.find(issue.draft_id)
133
+ assert_nil draft.user_id
134
+ end
135
+
136
+ def test_save_draft_with_associations
137
+ issue = Issue.new(cell: cells(:second_cell))
138
+ issue.save_draft(current_user)
139
+
140
+ draft = YeshouaCrm::ActsAsDraftable::Draft.find(issue.draft_id)
141
+ assert_equal issue.cell.name, draft.restore.cell.name
142
+ end
143
+
144
+ def test_save_draft_updates_existing_draft
145
+ issue = Issue.new
146
+ issue.save_draft(current_user)
147
+
148
+ issue.subject = 'changed subject'
149
+ assert_difference 'Issue.count', 0 do
150
+ assert_difference 'YeshouaCrm::ActsAsDraftable::Draft.count', 0 do
151
+ issue.save_draft(current_user)
152
+ end
153
+ end
154
+
155
+ draft = YeshouaCrm::ActsAsDraftable::Draft.find(issue.draft_id)
156
+ assert_equal issue.subject, draft.restore.subject
157
+ end
158
+
159
+ def test_save_draft_does_not_save_persisted_model
160
+ issue = Issue.new
161
+ issue.save_draft(current_user)
162
+ issue.save!
163
+ assert_equal false, issue.save_draft(current_user)
164
+ end
165
+
166
+ def test_update_draft
167
+ issue = Issue.new(subject: 'subject')
168
+ issue.save_draft(current_user)
169
+
170
+ assert_difference 'Issue.count', 0 do
171
+ assert_difference 'YeshouaCrm::ActsAsDraftable::Draft.count', 0 do
172
+ issue.update_draft(current_user, subject: 'updated_subject')
173
+ end
174
+ end
175
+
176
+ draft = YeshouaCrm::ActsAsDraftable::Draft.find(issue.draft_id)
177
+ assert_equal issue.subject, draft.restore.subject
178
+ end
179
+
180
+ private
181
+
182
+ def current_user
183
+ users(:sam)
184
+ end
185
+ end