yeshoua_crm 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7911333d07dd647683d67343499323913dee8ffc
4
+ data.tar.gz: 4cd4d6d6dca81d5fe09cbf401e20f06ed0385eae
5
+ SHA512:
6
+ metadata.gz: 39a0b962036d228f4d74bbb01c485eca46a0541eae9c9d93f69f2863106ead851b55544fb006de21c50364493248ace151ba14d4137c4b433b530ebc5b12f030
7
+ data.tar.gz: dd2d6bd7a1187e7b1c0b8c05afc6672e4845050d22c45ab72dc25f3f32052f174f26d0bbd86fab717569250eb94a871d17aa3665df28016eca14f662657d297e
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ task default: :test
5
+
6
+ desc 'Run tests'
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.libs << 'test'
9
+ t.pattern = 'test/**/*_test.rb'
10
+ t.verbose = true
11
+ end
@@ -0,0 +1,87 @@
1
+ require 'active_record'
2
+ require 'action_view'
3
+
4
+ require 'yeshoua_crm/version'
5
+
6
+ require 'yeshoua_crm/acts_as_list/list'
7
+ require 'yeshoua_crm/acts_as_taggable/tag'
8
+ require 'yeshoua_crm/acts_as_taggable/tag_list'
9
+ require 'yeshoua_crm/acts_as_taggable/tagging'
10
+ require 'yeshoua_crm/acts_as_taggable/rcrm_acts_as_taggable'
11
+ require 'yeshoua_crm/acts_as_viewed/rcrm_acts_as_viewed'
12
+ require 'yeshoua_crm/acts_as_votable/rcrm_acts_as_votable'
13
+ require 'yeshoua_crm/acts_as_votable/rcrm_acts_as_voter'
14
+ require 'yeshoua_crm/acts_as_votable/vote'
15
+ require 'yeshoua_crm/acts_as_votable/voter'
16
+ require 'yeshoua_crm/acts_as_draftable/rcrm_acts_as_draftable'
17
+ require 'yeshoua_crm/acts_as_draftable/draft'
18
+
19
+ require 'yeshoua_crm/currency'
20
+ require 'yeshoua_crm/helpers/tags_helper'
21
+ require 'yeshoua_crm/money_helper'
22
+
23
+ require 'liquid'
24
+ require 'yeshoua_crm/liquid/filters/base'
25
+ require 'yeshoua_crm/liquid/filters/arrays'
26
+ require 'yeshoua_crm/liquid/drops/issues_drop'
27
+ require 'yeshoua_crm/liquid/drops/news_drop'
28
+ require 'yeshoua_crm/liquid/drops/cells_drop'
29
+ require 'yeshoua_crm/liquid/drops/users_drop'
30
+
31
+ require 'yeshoua_crm/helpers/external_assets_helper'
32
+ require 'yeshoua_crm/helpers/form_tag_helper'
33
+ require 'yeshoua_crm/assets_manager'
34
+
35
+ if defined?(ActiveRecord::Base)
36
+ ActiveRecord::Base.send :include, YeshouaCrm::ActsAsList::List
37
+ ActiveRecord::Base.extend(YeshouaCrm::ActsAsVotable::Voter)
38
+ ActiveRecord::Base.extend(YeshouaCrm::ActsAsVotable::Votable)
39
+ end
40
+
41
+ YeshouaCrm::AssetsManager.install_assets
42
+
43
+ if defined?(ActionView::Base)
44
+ ActionView::Base.send :include, YeshouaCrm::ExternalAssetsHelper
45
+ ActionView::Base.send :include, YeshouaCrm::FormTagHelper
46
+ end
47
+
48
+ def requires_yeshoua_crm(arg)
49
+ def compare_versions(requirement, current)
50
+ raise ArgumentError.new('wrong version format') unless check_version_format(requirement)
51
+ requirement = requirement.split('.').collect(&:to_i)
52
+ requirement <=> current.slice(0, requirement.size)
53
+ end
54
+
55
+ def check_version_format(version)
56
+ version =~ /^\d+.?\d*.?\d*$/m
57
+ end
58
+
59
+ arg = {:version_or_higher => arg} unless arg.is_a?(Hash)
60
+ arg.assert_valid_keys(:version, :version_or_higher)
61
+
62
+ current = YeshouaCrm::VERSION.split('.').map { |x| x.to_i }
63
+ arg.each do |k, req|
64
+ case k
65
+ when :version_or_higher
66
+ raise ArgumentError.new(':version_or_higher accepts a version string only') unless req.is_a?(String)
67
+ unless compare_versions(req, current) <= 0
68
+ Rails.logger.error "\033[31m[ERROR]\033[0m Yeshoua requires yeshoua_crm gem version #{req} or higher (you're using #{YeshouaCrm::VERSION}).\n\033[31m[ERROR]\033[0m Please update with 'bundle update yeshoua_crm'." if Rails.logger
69
+ abort "\033[31mYeshoua requires yeshoua_crm gem version #{req} or higher (you're using #{YeshouaCrm::VERSION}).\nPlease update with 'bundle update yeshoua_crm'.\033[0m"
70
+ end
71
+ when :version
72
+ req = [req] if req.is_a?(String)
73
+ if req.is_a?(Array)
74
+ unless req.detect { |ver| compare_versions(ver, current) == 0 }
75
+ abort "\033[31mYeshoua requires yeshoua_crm gem version #{req} (you're using #{YeshouaCrm::VERSION}).\nPlease update with 'bundle update yeshoua_crm'.\033[0m"
76
+ end
77
+ elsif req.is_a?(Range)
78
+ unless compare_versions(req.first, current) <= 0 && compare_versions(req.last, current) >= 0
79
+ abort "\033[31mYeshoua requires yeshoua_crm gem version between #{req.first} and #{req.last} (you're using #{YeshouaCrm::VERSION}).\nPlease update with 'bundle update yeshoua_crm'.\033[0m"
80
+ end
81
+ else
82
+ raise ArgumentError.new(':version option accepts a version string, an array or a range of versions')
83
+ end
84
+ end
85
+ end
86
+ true
87
+ end
@@ -0,0 +1,40 @@
1
+ # The MIT License (MIT)
2
+
3
+ # Copyright (c) 2016-2018 Georg Ledermann
4
+
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ module YeshouaCrm
24
+ module ActsAsDraftable
25
+ class Draft < ActiveRecord::Base
26
+ belongs_to :user
27
+ belongs_to :parent, polymorphic: true
28
+
29
+ validates_presence_of :data, :target_type
30
+
31
+ def restore
32
+ target_type.constantize.from_draft(self)
33
+ end
34
+
35
+ def self.restore_all
36
+ find_each.map(&:restore)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,154 @@
1
+ # The MIT License (MIT)
2
+
3
+ # Copyright (c) 2016-2018 Georg Ledermann
4
+
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ module YeshouaCrm
24
+ module ActsAsDraftable #:nodoc: all
25
+ module Base
26
+ ALLOWED_DRAFT_OPTIONS = [:parent]
27
+
28
+ def draftable?
29
+ false
30
+ end
31
+
32
+ def rcrm_acts_as_draftable(options = {})
33
+ raise ArgumentError unless options.is_a?(Hash)
34
+ raise ArgumentError unless options.keys.all? { |k| ALLOWED_DRAFT_OPTIONS.include?(k) }
35
+
36
+ class_attribute :draft_parent
37
+
38
+ if options[:parent]
39
+ parent_class = self.reflect_on_all_associations(:belongs_to).find { |a| a.name == options[:parent] }.try(:klass)
40
+ raise ArgumentError unless parent_class
41
+
42
+ unless parent_class.method_defined?(:drafts)
43
+ parent_class.class_eval do
44
+ def drafts(user)
45
+ Draft.where(user: user, parent: self)
46
+ end
47
+
48
+ def self.child_drafts(user)
49
+ Draft.where(user: user, parent_type: self.base_class.name)
50
+ end
51
+ end
52
+ end
53
+
54
+ self.draft_parent = options[:parent]
55
+ end
56
+
57
+ attr_accessor :draft_id
58
+ after_create :clear_draft
59
+
60
+ extend YeshouaCrm::ActsAsDraftable::ClassMethods
61
+ include YeshouaCrm::ActsAsDraftable::InstanceMethods
62
+ end
63
+ end # Base
64
+
65
+ module ClassMethods
66
+ def draftable?
67
+ true
68
+ end
69
+
70
+ def from_draft(draft_or_id)
71
+ draft = draft_or_id.is_a?(Draft) ? draft_or_id : Draft.find(draft_or_id)
72
+ raise ArgumentError unless draft.target_type == name
73
+
74
+ target = draft.target_type.constantize.new
75
+ target.load_from_draft(draft.data)
76
+
77
+ target.send("#{draft_parent}=", draft.parent) if draft_parent
78
+ target.draft_id = draft.id
79
+ target
80
+ end
81
+
82
+ def drafts(user)
83
+ Draft.where(user: user, target_type: name)
84
+ end
85
+ end # ClassMethods
86
+
87
+ module InstanceMethods
88
+ def save_draft(user = nil)
89
+ return false unless self.new_record?
90
+
91
+ draft = Draft.find_by_id(self.draft_id) || Draft.new
92
+
93
+ draft.data = dump_to_draft
94
+ draft.target_type = self.class.name
95
+ draft.user = user
96
+ draft.parent = self.send(self.class.draft_parent) if self.class.draft_parent
97
+
98
+ result = draft.save
99
+ self.draft_id = draft.id if result
100
+ result
101
+ end
102
+
103
+ def update_draft(user, attributes)
104
+ with_transaction_returning_status do
105
+ assign_attributes(attributes)
106
+ save_draft(user)
107
+ end
108
+ end
109
+
110
+ def dump_to_draft
111
+ Marshal.dump(instance_values)
112
+ end
113
+
114
+ def load_from_draft(string)
115
+ values = Marshal.load(string)
116
+
117
+ values.each do |name, value|
118
+ instance_variable_set("@#{name}", value)
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ def clear_draft
125
+ if draft = Draft.find_by_id(self.draft_id)
126
+ self.draft_id = nil if draft.destroy
127
+ end
128
+ end
129
+ end # InstanceMethods
130
+
131
+ module Migration
132
+ def create_drafts_table
133
+ return if connection.table_exists?(:drafts)
134
+
135
+ connection.create_table :drafts do |t|
136
+ t.string :target_type, limit: 150, null: false
137
+ t.references :user
138
+ t.references :parent, polymorphic: true, index: true
139
+ t.binary :data, limit: 16777215, null: false
140
+ t.datetime :updated_at, null: false
141
+ end
142
+
143
+ connection.add_index :drafts, [:user_id, :target_type]
144
+ end
145
+
146
+ def drop_drafts_table
147
+ connection.drop_table :drafts if connection.table_exists?(:drafts)
148
+ end
149
+ end # Migration
150
+ end
151
+ end
152
+
153
+ ActiveRecord::Base.extend YeshouaCrm::ActsAsDraftable::Base
154
+ ActiveRecord::Base.extend YeshouaCrm::ActsAsDraftable::Migration
@@ -0,0 +1,282 @@
1
+ module YeshouaCrm
2
+ module ActsAsList
3
+ module List
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list.
9
+ # The class that has this specified needs to have a +position+ column defined as an integer on
10
+ # the mapped database table.
11
+ #
12
+ # Todo list example:
13
+ #
14
+ # class TodoList < ActiveRecord::Base
15
+ # has_many :todo_items, :order => "position"
16
+ # end
17
+ #
18
+ # class TodoItem < ActiveRecord::Base
19
+ # belongs_to :todo_list
20
+ # acts_as_list :scope => :todo_list
21
+ # end
22
+ #
23
+ # todo_list.first.move_to_bottom
24
+ # todo_list.last.move_higher
25
+ module ClassMethods
26
+ # Configuration options are:
27
+ #
28
+ # * +column+ - specifies the column name to use for keeping the position integer (default: +position+)
29
+ # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
30
+ # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
31
+ # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
32
+ # Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
33
+ def rcrm_acts_as_list(options = {})
34
+
35
+ configuration = { :column => "position", :scope => "1 = 1" }
36
+ configuration.update(options) if options.is_a?(Hash)
37
+
38
+ configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
39
+
40
+ if configuration[:scope].is_a?(Symbol)
41
+ scope_condition_method = %(
42
+ def scope_condition
43
+ if #{configuration[:scope].to_s}.nil?
44
+ "#{configuration[:scope].to_s} IS NULL"
45
+ else
46
+ "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
47
+ end
48
+ end
49
+ )
50
+ else
51
+ scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
52
+ end
53
+
54
+ class_eval <<-EOV
55
+ include YeshouaCrm::ActsAsList::List::InstanceMethods
56
+
57
+ def rcrm_acts_as_list_class
58
+ ::#{self.name}
59
+ end
60
+
61
+ def position_column
62
+ '#{configuration[:column]}'
63
+ end
64
+
65
+ #{scope_condition_method}
66
+
67
+ before_destroy :remove_from_list
68
+ before_create :add_to_list_bottom
69
+ EOV
70
+ end
71
+ end
72
+
73
+ # All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
74
+ # by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
75
+ # lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return +true+ if that chapter is
76
+ # the first in the list of all chapters.
77
+ module InstanceMethods
78
+ # Insert the item at the given position (defaults to the top position of 1).
79
+ def insert_at(position = 1)
80
+ insert_at_position(position)
81
+ end
82
+
83
+ # Swap positions with the next lower item, if one exists.
84
+ def move_lower
85
+ return unless lower_item
86
+
87
+ rcrm_acts_as_list_class.transaction do
88
+ lower_item.decrement_position
89
+ increment_position
90
+ end
91
+ end
92
+
93
+ # Swap positions with the next higher item, if one exists.
94
+ def move_higher
95
+ return unless higher_item
96
+
97
+ rcrm_acts_as_list_class.transaction do
98
+ higher_item.increment_position
99
+ decrement_position
100
+ end
101
+ end
102
+
103
+ # Move to the bottom of the list. If the item is already in the list, the items below it have their
104
+ # position adjusted accordingly.
105
+ def move_to_bottom
106
+ return unless in_list?
107
+ rcrm_acts_as_list_class.transaction do
108
+ decrement_positions_on_lower_items
109
+ assume_bottom_position
110
+ end
111
+ end
112
+
113
+ # Move to the top of the list. If the item is already in the list, the items above it have their
114
+ # position adjusted accordingly.
115
+ def move_to_top
116
+ return unless in_list?
117
+ rcrm_acts_as_list_class.transaction do
118
+ increment_positions_on_higher_items
119
+ assume_top_position
120
+ end
121
+ end
122
+
123
+ # Move to the given position
124
+ def move_to=(pos)
125
+ case pos.to_s
126
+ when 'highest'
127
+ move_to_top
128
+ when 'higher'
129
+ move_higher
130
+ when 'lower'
131
+ move_lower
132
+ when 'lowest'
133
+ move_to_bottom
134
+ end
135
+ reset_positions_in_list
136
+ end
137
+
138
+ def reset_positions_in_list
139
+ rcrm_acts_as_list_class.where(scope_condition).reorder("#{position_column} ASC, id ASC").each_with_index do |item, i|
140
+ unless item.send(position_column) == (i + 1)
141
+ rcrm_acts_as_list_class.where({:id => item.id}).
142
+ update_all({position_column => (i + 1)})
143
+ end
144
+ end
145
+ end
146
+
147
+ # Removes the item from the list.
148
+ def remove_from_list
149
+ if in_list?
150
+ decrement_positions_on_lower_items
151
+ update_attribute position_column, nil
152
+ end
153
+ end
154
+
155
+ # Increase the position of this item without adjusting the rest of the list.
156
+ def increment_position
157
+ return unless in_list?
158
+ update_attribute position_column, self.send(position_column).to_i + 1
159
+ end
160
+
161
+ # Decrease the position of this item without adjusting the rest of the list.
162
+ def decrement_position
163
+ return unless in_list?
164
+ update_attribute position_column, self.send(position_column).to_i - 1
165
+ end
166
+
167
+ # Return +true+ if this object is the first in the list.
168
+ def first?
169
+ return false unless in_list?
170
+ self.send(position_column) == 1
171
+ end
172
+
173
+ # Return +true+ if this object is the last in the list.
174
+ def last?
175
+ return false unless in_list?
176
+ self.send(position_column) == bottom_position_in_list
177
+ end
178
+
179
+ # Return the next higher item in the list.
180
+ def higher_item
181
+ return nil unless in_list?
182
+ rcrm_acts_as_list_class.where(
183
+ "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
184
+ ).first
185
+ end
186
+
187
+ # Return the next lower item in the list.
188
+ def lower_item
189
+ return nil unless in_list?
190
+ rcrm_acts_as_list_class.where(
191
+ "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
192
+ ).first
193
+ end
194
+
195
+ # Test if this record is in a list
196
+ def in_list?
197
+ !send(position_column).nil?
198
+ end
199
+
200
+ private
201
+
202
+ def add_to_list_top
203
+ increment_positions_on_all_items
204
+ end
205
+
206
+ def add_to_list_bottom
207
+ self[position_column] = bottom_position_in_list.to_i + 1
208
+ end
209
+
210
+ # Overwrite this method to define the scope of the list changes
211
+ def scope_condition() "1" end
212
+
213
+ # Returns the bottom position number in the list.
214
+ # bottom_position_in_list # => 2
215
+ def bottom_position_in_list(except = nil)
216
+ item = bottom_item(except)
217
+ item ? item.send(position_column) : 0
218
+ end
219
+
220
+ # Returns the bottom item
221
+ def bottom_item(except = nil)
222
+ conditions = scope_condition
223
+ conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
224
+ rcrm_acts_as_list_class.where(conditions).reorder("#{position_column} DESC").first
225
+ end
226
+
227
+ # Forces item to assume the bottom position in the list.
228
+ def assume_bottom_position
229
+ update_attribute(position_column, bottom_position_in_list(self).to_i + 1)
230
+ end
231
+
232
+ # Forces item to assume the top position in the list.
233
+ def assume_top_position
234
+ update_attribute(position_column, 1)
235
+ end
236
+
237
+ # This has the effect of moving all the higher items up one.
238
+ def decrement_positions_on_higher_items(position)
239
+ rcrm_acts_as_list_class.
240
+ where("#{scope_condition} AND #{position_column} <= #{position}").
241
+ update_all("#{position_column} = (#{position_column} - 1)")
242
+ end
243
+
244
+ # This has the effect of moving all the lower items up one.
245
+ def decrement_positions_on_lower_items
246
+ return unless in_list?
247
+ rcrm_acts_as_list_class.
248
+ where("#{scope_condition} AND #{position_column} > #{send(position_column).to_i}").
249
+ update_all("#{position_column} = (#{position_column} - 1)")
250
+ end
251
+
252
+ # This has the effect of moving all the higher items down one.
253
+ def increment_positions_on_higher_items
254
+ return unless in_list?
255
+ rcrm_acts_as_list_class.
256
+ where("#{scope_condition} AND #{position_column} < #{send(position_column).to_i}").
257
+ update_all("#{position_column} = (#{position_column} + 1)")
258
+ end
259
+
260
+ # This has the effect of moving all the lower items down one.
261
+ def increment_positions_on_lower_items(position)
262
+ rcrm_acts_as_list_class.
263
+ where("#{scope_condition} AND #{position_column} >= #{position}").
264
+ update_all("#{position_column} = (#{position_column} + 1)")
265
+ end
266
+
267
+ # Increments position (<tt>position_column</tt>) of all items in the list.
268
+ def increment_positions_on_all_items
269
+ rcrm_acts_as_list_class.
270
+ where("#{scope_condition}").
271
+ update_all("#{position_column} = (#{position_column} + 1)")
272
+ end
273
+
274
+ def insert_at_position(position)
275
+ remove_from_list
276
+ increment_positions_on_lower_items(position)
277
+ self.update_attribute(position_column, position)
278
+ end
279
+ end
280
+ end
281
+ end
282
+ end