yeshoua_crm 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Rakefile +11 -0
- data/lib/yeshoua_crm.rb +87 -0
- data/lib/yeshoua_crm/acts_as_draftable/draft.rb +40 -0
- data/lib/yeshoua_crm/acts_as_draftable/rcrm_acts_as_draftable.rb +154 -0
- data/lib/yeshoua_crm/acts_as_list/list.rb +282 -0
- data/lib/yeshoua_crm/acts_as_taggable/rcrm_acts_as_taggable.rb +350 -0
- data/lib/yeshoua_crm/acts_as_taggable/tag.rb +81 -0
- data/lib/yeshoua_crm/acts_as_taggable/tag_list.rb +111 -0
- data/lib/yeshoua_crm/acts_as_taggable/tagging.rb +16 -0
- data/lib/yeshoua_crm/acts_as_viewed/rcrm_acts_as_viewed.rb +274 -0
- data/lib/yeshoua_crm/acts_as_votable/rcrm_acts_as_votable.rb +80 -0
- data/lib/yeshoua_crm/acts_as_votable/rcrm_acts_as_voter.rb +20 -0
- data/lib/yeshoua_crm/acts_as_votable/votable.rb +323 -0
- data/lib/yeshoua_crm/acts_as_votable/vote.rb +28 -0
- data/lib/yeshoua_crm/acts_as_votable/voter.rb +131 -0
- data/lib/yeshoua_crm/assets_manager.rb +43 -0
- data/lib/yeshoua_crm/currency.rb +439 -0
- data/lib/yeshoua_crm/currency/formatting.rb +224 -0
- data/lib/yeshoua_crm/currency/heuristics.rb +151 -0
- data/lib/yeshoua_crm/currency/loader.rb +24 -0
- data/lib/yeshoua_crm/helpers/external_assets_helper.rb +17 -0
- data/lib/yeshoua_crm/helpers/form_tag_helper.rb +123 -0
- data/lib/yeshoua_crm/helpers/tags_helper.rb +13 -0
- data/lib/yeshoua_crm/helpers/vote_helper.rb +35 -0
- data/lib/yeshoua_crm/liquid/drops/cells_drop.rb +86 -0
- data/lib/yeshoua_crm/liquid/drops/issues_drop.rb +66 -0
- data/lib/yeshoua_crm/liquid/drops/news_drop.rb +54 -0
- data/lib/yeshoua_crm/liquid/drops/users_drop.rb +72 -0
- data/lib/yeshoua_crm/liquid/filters/arrays.rb +177 -0
- data/lib/yeshoua_crm/liquid/filters/base.rb +208 -0
- data/lib/yeshoua_crm/money_helper.rb +65 -0
- data/lib/yeshoua_crm/version.rb +3 -0
- data/test/acts_as_draftable/draft_test.rb +29 -0
- data/test/acts_as_draftable/rcrm_acts_as_draftable_test.rb +185 -0
- data/test/acts_as_taggable/rcrm_acts_as_taggable_test.rb +345 -0
- data/test/acts_as_taggable/tag_list_test.rb +34 -0
- data/test/acts_as_taggable/tag_test.rb +72 -0
- data/test/acts_as_taggable/tagging_test.rb +15 -0
- data/test/acts_as_viewed/rcrm_acts_as_viewed_test.rb +47 -0
- data/test/acts_as_votable/rcrm_acts_as_votable_test.rb +19 -0
- data/test/acts_as_votable/rcrm_acts_as_voter_test.rb +14 -0
- data/test/acts_as_votable/votable_test.rb +507 -0
- data/test/acts_as_votable/voter_test.rb +296 -0
- data/test/currency_test.rb +292 -0
- data/test/liquid/drops/issues_drop_test.rb +34 -0
- data/test/liquid/drops/news_drop_test.rb +38 -0
- data/test/liquid/drops/projects_drop_test.rb +44 -0
- data/test/liquid/drops/uses_drop_test.rb +36 -0
- data/test/liquid/filters/arrays_filter_test.rb +24 -0
- data/test/liquid/filters/base_filter_test.rb +63 -0
- data/test/liquid/liquid_helper.rb +32 -0
- data/test/models/issue.rb +14 -0
- data/test/models/news.rb +3 -0
- data/test/models/project.rb +8 -0
- data/test/models/user.rb +11 -0
- data/test/models/vote_classes.rb +33 -0
- data/test/money_helper_test.rb +12 -0
- data/test/schema.rb +121 -0
- data/test/tags_helper_test.rb +29 -0
- data/test/test_helper.rb +66 -0
- data/test/vote_helper_test.rb +28 -0
- data/yeshoua_crm.gemspec +28 -0
- 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
data/lib/yeshoua_crm.rb
ADDED
@@ -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
|