erp_tech_svcs 4.0.0 → 4.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +0 -24
- data/app/controllers/api/v1/audit_log_items_controller.rb +33 -0
- data/app/controllers/api/v1/audit_logs_controller.rb +32 -0
- data/app/controllers/api/v1/capabilities_controller.rb +160 -0
- data/app/controllers/api/v1/file_assets_controller.rb +40 -0
- data/app/controllers/api/v1/groups_controller.rb +236 -0
- data/app/controllers/api/v1/security_roles_controller.rb +276 -0
- data/app/controllers/api/v1/users_controller.rb +262 -0
- data/app/controllers/erp_tech_svcs/session_controller.rb +8 -5
- data/app/controllers/erp_tech_svcs/user_controller.rb +14 -15
- data/app/mailers/user_mailer.rb +8 -5
- data/app/models/audit_log.rb +111 -36
- data/app/models/audit_log_item.rb +30 -0
- data/app/models/audit_log_item_type.rb +1 -0
- data/app/models/audit_log_type.rb +19 -0
- data/app/models/capability.rb +22 -6
- data/app/models/extensions/tracked_status_type.rb +3 -0
- data/app/models/file_asset.rb +245 -20
- data/app/models/file_asset_holder.rb +20 -0
- data/app/models/group.rb +38 -25
- data/app/models/notification.rb +32 -13
- data/app/models/notification_type.rb +13 -0
- data/app/models/security_role.rb +17 -4
- data/app/models/user.rb +116 -29
- data/app/validators/password_strength_validator.rb +1 -1
- data/app/views/user_mailer/activation_needed_email.html.erb +293 -15
- data/app/views/user_mailer/reset_password_email.html.erb +268 -13
- data/config/initializers/logger.rb +19 -0
- data/config/initializers/sorcery.rb +2 -0
- data/config/initializers/wickedpdf.rb +4 -0
- data/config/routes.rb +64 -0
- data/db/data_migrations/20110802200222_schedule_delete_expired_sessions_job.rb +1 -5
- data/db/data_migrations/20150819140550_create_job_tracker_for_notification.rb +14 -0
- data/db/migrate/20080805000010_base_tech_services.rb +99 -39
- data/db/migrate/20150414151421_add_nested_set_columns_to_security_role.rb +13 -0
- data/db/migrate/20150609003216_update_user_for_sorcery.rb +11 -0
- data/db/migrate/20150819135108_add_custom_fields_to_notifications.rb +5 -0
- data/db/migrate/20160122155402_add_description_to_file_asset.rb +13 -0
- data/db/migrate/20160310163060_add_created_by_updated_by_to_erp_tech_svcs.rb +35 -0
- data/db/migrate/20160313161611_add_tenant_id_to_audit_log.rb +16 -0
- data/lib/erp_tech_svcs.rb +6 -10
- data/lib/erp_tech_svcs/config.rb +7 -2
- data/lib/erp_tech_svcs/delayed_jobs/delete_expired_sessions_job.rb +49 -0
- data/lib/erp_tech_svcs/delayed_jobs/notification_job.rb +50 -0
- data/lib/erp_tech_svcs/engine.rb +0 -1
- data/lib/erp_tech_svcs/erp_tech_svcs_audit_log.rb +12 -6
- data/lib/erp_tech_svcs/extensions.rb +0 -1
- data/lib/erp_tech_svcs/extensions/active_record/has_capability_accessors.rb +57 -29
- data/lib/erp_tech_svcs/extensions/active_record/has_file_assets.rb +57 -31
- data/lib/erp_tech_svcs/extensions/active_record/has_security_roles.rb +12 -4
- data/lib/erp_tech_svcs/extensions/active_record/is_json.rb +22 -15
- data/lib/erp_tech_svcs/extensions/active_record/scoped_by.rb +16 -13
- data/lib/erp_tech_svcs/extensions/compass_ae/erp_base_erp_svcs/controllers/api/parties_controller.rb +15 -0
- data/lib/erp_tech_svcs/file_support.rb +1 -0
- data/lib/erp_tech_svcs/file_support/file_system_manager.rb +77 -44
- data/lib/erp_tech_svcs/file_support/manager.rb +12 -3
- data/lib/erp_tech_svcs/file_support/railties/compass_ae_resolver.rb +49 -0
- data/lib/erp_tech_svcs/file_support/s3_manager.rb +73 -51
- data/lib/erp_tech_svcs/utils/compass_access_negotiator.rb +11 -2
- data/lib/erp_tech_svcs/utils/default_nested_set_methods.rb +238 -46
- data/lib/erp_tech_svcs/version.rb +1 -1
- data/lib/tasks/erp_tech_svcs_tasks.rake +43 -5
- metadata +73 -42
- data/app/models/user_defined_data.rb +0 -6
- data/app/models/user_defined_field.rb +0 -8
- data/config/initializers/pdfkit.rb +0 -18
- data/db/data_migrations/20121130212146_note_capabilities.rb +0 -23
- data/db/migrate/20121116151510_create_groups.rb +0 -18
- data/db/migrate/20121126171612_upgrade_security.rb +0 -53
- data/db/migrate/20121126173506_upgrade_security2.rb +0 -274
- data/db/migrate/20130410135419_add_queue_to_delayed_jobs.rb +0 -13
- data/db/migrate/20130610163240_create_notifications.rb +0 -37
- data/db/migrate/20130725212647_add_party_id_idx_to_users.rb +0 -9
- data/db/migrate/20131113213843_add_audit_log_item_old_value.rb +0 -13
- data/db/migrate/20131113213844_add_erp_tech_svcs_missing_indexes.rb +0 -31
- data/db/migrate/20131129203603_add_user_defined_fields.rb +0 -43
- data/db/migrate/20141013060204_add_custom_fields_to_notifications.rb +0 -12
- data/db/migrate/20141108182427_add_scoped_by_to_file_assets.rb +0 -14
- data/lib/erp_tech_svcs/extensions/active_record/has_user_defined_data.rb +0 -147
- data/lib/erp_tech_svcs/sessions/delete_expired_sessions_job.rb +0 -47
- data/lib/erp_tech_svcs/sessions/delete_expired_sessions_service.rb +0 -15
- data/lib/erp_tech_svcs/utils/compass_logger.rb +0 -87
@@ -53,13 +53,17 @@ module ErpTechSvcs
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def build_file_assets_tree_for_model(model, starting_path)
|
56
|
-
files = model.files
|
56
|
+
files = model.files.where('directory like ?', starting_path + '%')
|
57
57
|
|
58
58
|
node_tree = [{:text => root, :leaf => false, :id => root, :children => []}]
|
59
59
|
|
60
60
|
paths = files.collect{|file| File.join(file.directory,file.name)}
|
61
61
|
|
62
|
-
node_tree.first[:children] << {:id => starting_path,
|
62
|
+
node_tree.first[:children] << {:id => starting_path,
|
63
|
+
:text => starting_path.split('/').last,
|
64
|
+
leaf: true,
|
65
|
+
iconCls: 'icon-document',
|
66
|
+
:children => []} if paths.select{|path| path.split('/')[1] == starting_path.split('/')[1]}.empty?
|
63
67
|
|
64
68
|
nesting_depth = paths.collect{|item| item.split('/').count}.max
|
65
69
|
unless nesting_depth.nil?
|
@@ -117,7 +121,12 @@ module ErpTechSvcs
|
|
117
121
|
file_asset_nodes.each do |child_asset_node|
|
118
122
|
node = find_node(File.join(self.root,child_asset_node[:id]))
|
119
123
|
unless node.nil?
|
120
|
-
|
124
|
+
if node[:leaf]
|
125
|
+
folders = []
|
126
|
+
else
|
127
|
+
folders = node[:children].select{|item| !item[:leaf]}
|
128
|
+
end
|
129
|
+
|
121
130
|
child_asset_node[:children] = [] if child_asset_node[:children].nil? && !folders.empty?
|
122
131
|
folders.each do |folder|
|
123
132
|
folder[:id].gsub!(self.root,'')
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module ActionView
|
2
|
+
class CompassAeFileResolver < OptimizedFileSystemResolver
|
3
|
+
def cached(key, path_info, details, locals) #:nodoc:
|
4
|
+
name, prefix, partial = path_info
|
5
|
+
locals = locals.map { |x| x.to_s }.sort!
|
6
|
+
|
7
|
+
if key && caching?
|
8
|
+
if @cached[key][name][prefix][partial][locals].nil? or @cached[key][name][prefix][partial][locals].empty?
|
9
|
+
@cached[key][name][prefix][partial][locals] = decorate(yield, path_info, details, locals)
|
10
|
+
else
|
11
|
+
@cached[key][name][prefix][partial][locals].each do |template|
|
12
|
+
#check if the file still exists
|
13
|
+
if File.exists? template.identifier
|
14
|
+
last_update = mtime(template.identifier)
|
15
|
+
if last_update > template.updated_at
|
16
|
+
@cached[key][name][prefix][partial][locals].delete_if{|item| item.identifier == template.identifier}
|
17
|
+
@cached[key][name][prefix][partial][locals] << build_template(template.identifier, template.virtual_path, (details[:formats] || [:html] if template.formats.empty?), template.locals)
|
18
|
+
end
|
19
|
+
else
|
20
|
+
@cached[key][name][prefix][partial][locals].delete_if{|item| item.identifier == template.identifier}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
@cached[key][name][prefix][partial][locals]
|
24
|
+
end
|
25
|
+
else
|
26
|
+
fresh = decorate(yield, path_info, details, locals)
|
27
|
+
return fresh unless key
|
28
|
+
|
29
|
+
scope = @cached[key][name][prefix][partial]
|
30
|
+
cache = scope[locals]
|
31
|
+
mtime = cache && cache.map(&:updated_at).max
|
32
|
+
|
33
|
+
if !mtime || fresh.empty? || fresh.any? { |t| t.updated_at > mtime }
|
34
|
+
scope[locals] = fresh
|
35
|
+
else
|
36
|
+
cache
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def build_template(p, virtual_path, formats, locals=nil)
|
44
|
+
handler, format = extract_handler_and_format(p, formats)
|
45
|
+
contents = File.open(p, "rb") { |io| io.read }
|
46
|
+
Template.new(contents, p, handler, :virtual_path => virtual_path, :format => format, :updated_at => mtime(p), :locals => locals)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -11,10 +11,10 @@ module ErpTechSvcs
|
|
11
11
|
@@configuration = YAML::load_file(File.join(Rails.root, 'config', 's3.yml'))[Rails.env]
|
12
12
|
|
13
13
|
# S3 debug logging
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
AWS.config(
|
15
|
+
:logger => Rails.logger,
|
16
|
+
:log_level => :info
|
17
|
+
)
|
18
18
|
|
19
19
|
@@s3_connection = AWS::S3.new(
|
20
20
|
:access_key_id => @@configuration['access_key_id'],
|
@@ -106,7 +106,7 @@ module ErpTechSvcs
|
|
106
106
|
options = (file.nil? ? {} : {:acl => acl})
|
107
107
|
path = path.sub(%r{^/}, '')
|
108
108
|
new_path = new_path.sub(%r{^/}, '')
|
109
|
-
|
109
|
+
|
110
110
|
old_object = bucket.objects[path]
|
111
111
|
if new_object = old_object.move_to(new_path, options)
|
112
112
|
message = "#{old_name} was renamed to #{name} successfully"
|
@@ -132,13 +132,9 @@ module ErpTechSvcs
|
|
132
132
|
result = false
|
133
133
|
message = nil
|
134
134
|
begin
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
result = true
|
139
|
-
else
|
140
|
-
message = FOLDER_IS_NOT_EMPTY
|
141
|
-
end
|
135
|
+
bucket.objects.with_prefix(path).delete_all
|
136
|
+
message = "File was deleted successfully"
|
137
|
+
result = true
|
142
138
|
rescue => ex
|
143
139
|
result = false
|
144
140
|
message = ex
|
@@ -178,57 +174,83 @@ module ErpTechSvcs
|
|
178
174
|
end
|
179
175
|
|
180
176
|
def find_node(path, options={})
|
177
|
+
parent = {
|
178
|
+
:text => path.split('/').pop,
|
179
|
+
:iconCls => "icon-content",
|
180
|
+
:leaf => false,
|
181
|
+
:id => path,
|
182
|
+
:children => []
|
183
|
+
}
|
184
|
+
|
181
185
|
#remove proceeding slash for s3
|
182
186
|
path.sub!(%r{^/}, '')
|
183
187
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
parent_directories = leaf_hash[:id].split('/')
|
203
|
-
parent_directories.pop
|
204
|
-
parent_directory = parent_directories.join('/')
|
205
|
-
|
206
|
-
file = files.find { |file| file.directory == parent_directory and file.name == leaf_hash[:text] }
|
207
|
-
unless file.nil?
|
208
|
-
leaf_hash[:isSecured] = file.is_secured?
|
209
|
-
leaf_hash[:roles] = file.roles.collect { |r| r.internal_identifier }
|
210
|
-
leaf_hash[:iconCls] = 'icon-document_lock' if leaf_hash[:isSecured]
|
211
|
-
leaf_hash[:size] = file.data_file_size
|
212
|
-
leaf_hash[:width] = file.width
|
213
|
-
leaf_hash[:height] = file.height
|
214
|
-
leaf_hash[:url] = file.url
|
188
|
+
if File.extname(path).blank?
|
189
|
+
tree = bucket.as_tree(:prefix => path)
|
190
|
+
|
191
|
+
tree.children.each do |node|
|
192
|
+
if node.leaf?
|
193
|
+
#ignore current path that comes as leaf from s3
|
194
|
+
next if node.key == path + '/'
|
195
|
+
|
196
|
+
leaf_hash = {
|
197
|
+
:text => node.key.split('/').pop,
|
198
|
+
:downloadPath => "/#{node.key.split('/')[0..-2].join('/')}",
|
199
|
+
:id => "/#{node.key}",
|
200
|
+
:iconCls => 'icon-document',
|
201
|
+
:leaf => true
|
202
|
+
}
|
203
|
+
|
204
|
+
if options[:file_asset_holder]
|
205
|
+
leaf_hash = apply_file_asset_properties(options[:file_asset_holder], leaf_hash)
|
215
206
|
end
|
207
|
+
|
208
|
+
parent[:children] << leaf_hash
|
209
|
+
else
|
210
|
+
parent[:children] << {
|
211
|
+
:iconCls => "icon-content",
|
212
|
+
:text => node.prefix.split('/').pop,
|
213
|
+
:id => "/#{node.prefix}".chop,
|
214
|
+
:leaf => false
|
215
|
+
}
|
216
216
|
end
|
217
|
+
end
|
217
218
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
}
|
219
|
+
else
|
220
|
+
parent[:iconCls] = 'icon-document'
|
221
|
+
parent[:leaf] = true
|
222
|
+
parent[:downloadPath] = "/#{path.split('/')[0..-2].join('/')}"
|
223
|
+
|
224
|
+
if options[:file_asset_holder]
|
225
|
+
parent = apply_file_asset_properties(options[:file_asset_holder], parent)
|
226
226
|
end
|
227
227
|
end
|
228
228
|
|
229
|
+
# add preceding slash back to parent
|
230
|
+
parent[:id] = "/#{parent[:id]}"
|
231
|
+
|
229
232
|
parent
|
230
233
|
end
|
231
234
|
|
235
|
+
private
|
236
|
+
|
237
|
+
def apply_file_asset_properties(file_asset_holder, hash)
|
238
|
+
files = file_asset_holder.files
|
239
|
+
|
240
|
+
file = files.find { |file| file.directory == hash[:downloadPath] and file.name == hash[:text] }
|
241
|
+
unless file.nil?
|
242
|
+
hash[:isSecured] = file.is_secured?
|
243
|
+
hash[:roles] = file.roles.collect { |r| r.internal_identifier }
|
244
|
+
hash[:iconCls] = 'icon-document_lock' if hash[:isSecured]
|
245
|
+
hash[:size] = file.data_file_size
|
246
|
+
hash[:width] = file.width
|
247
|
+
hash[:height] = file.height
|
248
|
+
hash[:url] = file.data.url
|
249
|
+
end
|
250
|
+
|
251
|
+
hash
|
252
|
+
end
|
253
|
+
|
232
254
|
end #S3Manager
|
233
255
|
end #FileSupport
|
234
256
|
end #ErpTechSvcs
|
@@ -5,15 +5,24 @@ module ErpTechSvcs
|
|
5
5
|
# pass in (capability_type_iid, class name) or (capability_type_iid, any class instance)
|
6
6
|
# Example: can user upload files? user.has_capability?('upload', 'FileAsset')
|
7
7
|
# Example: can download this file? user.has_capability?('download', file_asset)
|
8
|
-
def has_capability?(capability_type_iid, klass)
|
8
|
+
def has_capability?(capability_type_iid, klass, instance_id = nil)
|
9
9
|
capability_type_iid = capability_type_iid.to_s if capability_type_iid.is_a?(Symbol)
|
10
|
-
if klass.is_a?(String)
|
10
|
+
if klass.is_a?(String) and instance_id.nil?
|
11
11
|
scope_type = ScopeType.find_by_internal_identifier('class')
|
12
12
|
capability = Capability.joins(:capability_type).
|
13
13
|
where(:capability_resource_type => klass).
|
14
14
|
where(:scope_type_id => scope_type.id).
|
15
15
|
where(:capability_types => {:internal_identifier => capability_type_iid}).first
|
16
16
|
return nil if capability.nil? # capability not found so return nil
|
17
|
+
elsif klass.is_a?(String)
|
18
|
+
scope_type = ScopeType.find_by_internal_identifier('instance')
|
19
|
+
|
20
|
+
capability = Capability.joins(:capability_type).
|
21
|
+
where(:capability_resource_type => klass).
|
22
|
+
where(:capability_resource_id => instance_id).
|
23
|
+
where(:scope_type_id => scope_type.id).
|
24
|
+
where(:capability_types => {:internal_identifier => capability_type_iid}).first
|
25
|
+
return !klass.constantize.protect_all_instances if capability.nil?
|
17
26
|
else
|
18
27
|
scope_type = ScopeType.find_by_internal_identifier('instance')
|
19
28
|
capability = klass.capabilities.joins(:capability_type).
|
@@ -1,65 +1,257 @@
|
|
1
1
|
module ErpTechSvcs
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
module Utils
|
3
|
+
module DefaultNestedSetMethods
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
module ClassMethods
|
9
|
+
# returns an array of hashes which represent all nodes in nested set order,
|
10
|
+
# each of which consists of the node's id, internal identifier and representation
|
11
|
+
# if a parent is passed it starts there in the tree
|
12
|
+
def to_all_representation(parent=nil, container_arr=[], level=0, ancestors=nil)
|
13
|
+
if parent
|
14
|
+
parent.children.each do |node|
|
15
|
+
container_arr << {id: node.id,
|
16
|
+
description: node.to_representation(level),
|
17
|
+
internal_identifier: node.internal_identifier}
|
11
18
|
|
12
|
-
|
13
|
-
|
14
|
-
|
19
|
+
unless node.leaf?
|
20
|
+
to_all_representation(node, container_arr, (level + 1))
|
21
|
+
end
|
15
22
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
end
|
24
|
+
else
|
25
|
+
ancestors = (ancestors || self.roots)
|
26
|
+
ancestors.each do |root|
|
27
|
+
container_arr << {id: root.id,
|
28
|
+
description: root.to_representation(level),
|
29
|
+
internal_identifier: root.internal_identifier}
|
20
30
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
:leaf => self.leaf,
|
25
|
-
:children => self.children.collect{|child| child.to_tree_hash(options)}
|
26
|
-
})
|
31
|
+
to_all_representation(root, container_arr, (level + 1))
|
32
|
+
end
|
33
|
+
end
|
27
34
|
|
28
|
-
|
29
|
-
|
35
|
+
container_arr
|
36
|
+
end
|
30
37
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
38
|
+
def find_roots
|
39
|
+
where("parent_id is null")
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_children(parent_id = nil)
|
43
|
+
parent_id.to_i == 0 ? self.roots : find(parent_id).children
|
44
|
+
end
|
35
45
|
|
36
|
-
|
37
|
-
parent_id.to_i == 0 ? self.roots : find(parent_id).children
|
38
|
-
end
|
39
|
-
|
40
|
-
# find_by_ancestor_iids
|
46
|
+
# find_by_ancestor_iids
|
41
47
|
# allows you to find a nested set element by the internal_identifiers in its ancestry
|
42
48
|
# for example, to find a GlAccount whose internal_identifier is “site_4”, and whose parent’s internal_identifier is “nightly_room_charge”
|
43
49
|
# and whose grandparent’s internal_identifier is “charge”, you would make this call:
|
44
50
|
# gl_account = GlAccount.find_by_iids(['charge', 'nightly_room_charge', "site_4"])
|
45
51
|
def find_by_ancestor_iids(iids)
|
46
|
-
return nil unless iids.is_a? Array
|
47
|
-
|
48
52
|
node = nil
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
53
|
+
|
54
|
+
if iids.is_a? Array
|
55
|
+
iids.each do |iid|
|
56
|
+
if (iid == iids.first)
|
57
|
+
node = where("parent_id is null and internal_identifier = ?", iid).first
|
58
|
+
else
|
59
|
+
node = where("parent_id = ? and internal_identifier = ?", node.id, iid).first
|
60
|
+
end
|
54
61
|
end
|
55
|
-
return nil if node.nil?
|
56
62
|
end
|
57
|
-
|
63
|
+
|
64
|
+
node
|
58
65
|
end
|
59
|
-
|
60
|
-
end
|
61
66
|
|
62
|
-
|
63
|
-
|
64
|
-
|
67
|
+
# find existing node or create it and return it. Parent can be passed
|
68
|
+
# which will scope this node by the parent
|
69
|
+
def find_or_create(iid, description, parent=nil)
|
70
|
+
# look for it
|
71
|
+
record = if parent
|
72
|
+
parent.children.find_by_internal_identifier(iid)
|
73
|
+
else
|
74
|
+
find_by_internal_identifier(iid)
|
75
|
+
end
|
76
|
+
|
77
|
+
unless record
|
78
|
+
record = create(description: description, internal_identifier: iid)
|
79
|
+
|
80
|
+
if parent
|
81
|
+
record.move_to_child_of(parent)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
record
|
86
|
+
end
|
87
|
+
|
88
|
+
# Build a tree based on internal identifiers passed
|
89
|
+
#
|
90
|
+
# @param [Array] Array of internal identifiers
|
91
|
+
# @return [Array] Tree from nodes
|
92
|
+
def build_tree_from_nodes(node_iids)
|
93
|
+
tree = []
|
94
|
+
|
95
|
+
# first we convert the nodes to a hash based array
|
96
|
+
node_iids.each do |node_iid|
|
97
|
+
node = self.iid(node_iid)
|
98
|
+
|
99
|
+
tree << node.to_hash({only: [{id: :record_id}, :parent_id, :internal_identifier],
|
100
|
+
leaf: node.leaf?,
|
101
|
+
text: node.to_label,
|
102
|
+
children: []})
|
103
|
+
end
|
104
|
+
|
105
|
+
# next we need to build the tree structure based on the nodes
|
106
|
+
sorted_tree = tree.dup
|
107
|
+
tree.each do |node_hash|
|
108
|
+
node = self.find(node_hash[:record_id])
|
109
|
+
|
110
|
+
parent_node = nil
|
111
|
+
if node.parent
|
112
|
+
parent_node = find_parent_in_tree(sorted_tree, node.parent.id)
|
113
|
+
end
|
114
|
+
|
115
|
+
if parent_node
|
116
|
+
# add to children of parent
|
117
|
+
parent_node[:children] << node_hash
|
118
|
+
|
119
|
+
# remove from updated tree
|
120
|
+
sorted_tree.delete_if { |item| node_hash[:record_id] == item[:record_id] }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
sorted_tree
|
125
|
+
end
|
126
|
+
|
127
|
+
# Delete nodes from a tree based on passed ids
|
128
|
+
#
|
129
|
+
# @param [Integer, Array] Either an Id or an array of ids to remove
|
130
|
+
# @return [Array] Tree with items removed
|
131
|
+
def delete_from_tree(tree, id)
|
132
|
+
if id.is_a? Array
|
133
|
+
id.each do |_id|
|
134
|
+
delete_from_tree(tree, _id)
|
135
|
+
end
|
136
|
+
else
|
137
|
+
tree.each do |node|
|
138
|
+
if node[:record_id] == id
|
139
|
+
tree.delete(node)
|
140
|
+
end
|
141
|
+
|
142
|
+
if node[:children]
|
143
|
+
delete_from_tree(node[:children], id)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
tree
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
65
152
|
|
153
|
+
def find_parent_in_tree(tree, id)
|
154
|
+
parent = nil
|
155
|
+
|
156
|
+
tree.each do |node|
|
157
|
+
if node[:record_id] == id
|
158
|
+
parent = node
|
159
|
+
break
|
160
|
+
end
|
161
|
+
|
162
|
+
if node[:children]
|
163
|
+
parent = find_parent_in_tree(node[:children], id)
|
164
|
+
|
165
|
+
if parent
|
166
|
+
break
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
parent
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
def to_label
|
177
|
+
description
|
178
|
+
end
|
179
|
+
|
180
|
+
def leaf
|
181
|
+
children.size == 0
|
182
|
+
end
|
183
|
+
|
184
|
+
def to_json_with_leaf(options = {})
|
185
|
+
self.to_json_without_leaf(options.merge(:methods => :leaf))
|
186
|
+
end
|
187
|
+
|
188
|
+
alias_method_chain :to_json, :leaf
|
189
|
+
|
190
|
+
def to_tree_hash(options={})
|
191
|
+
options = {
|
192
|
+
only: [:parent_id, :internal_identifier],
|
193
|
+
leaf: self.leaf?,
|
194
|
+
text: self.to_label,
|
195
|
+
children: self.children.collect { |child| child.to_tree_hash(options) }
|
196
|
+
}.merge(options)
|
197
|
+
|
198
|
+
self.to_hash(options)
|
199
|
+
end
|
200
|
+
|
201
|
+
def children_to_tree_hash(options={})
|
202
|
+
self.children.collect { |child| child.to_tree_hash(options) }
|
203
|
+
end
|
204
|
+
|
205
|
+
def to_representation(level)
|
206
|
+
# returns a string that consists of 1) a number of dashes equal to
|
207
|
+
# the category's level and 2) the category's description attr
|
208
|
+
rep = ''
|
209
|
+
|
210
|
+
if level > 0
|
211
|
+
level.times { rep << '-' }
|
212
|
+
rep += ' '
|
213
|
+
end
|
214
|
+
|
215
|
+
rep << description
|
216
|
+
end
|
217
|
+
|
218
|
+
def to_record_representation(root = self.class.root)
|
219
|
+
# returns a string of category descriptions like
|
220
|
+
# 'main_category > sub_category n > ... > this category instance'
|
221
|
+
if root?
|
222
|
+
description
|
223
|
+
else
|
224
|
+
crawl_up_from(self, root).split('///').compact.reverse.join(' > ')
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def is_descendant_of?(type)
|
229
|
+
type = self.class.iid(type) if (type.is_a? String)
|
230
|
+
parent = self.parent
|
231
|
+
|
232
|
+
if type.id == self.id
|
233
|
+
result = true
|
234
|
+
elsif parent.nil?
|
235
|
+
result = false
|
236
|
+
elsif parent.id == type.id
|
237
|
+
result = true
|
238
|
+
else
|
239
|
+
result = parent.is_descendant_of? type
|
240
|
+
end
|
241
|
+
result
|
242
|
+
end
|
243
|
+
|
244
|
+
private
|
245
|
+
|
246
|
+
def crawl_up_from(node, to_node = self.class.root)
|
247
|
+
unless node.nil?
|
248
|
+
# returns a string that is a '///'-separated list of nodes
|
249
|
+
# from child node to root
|
250
|
+
"#{node.description}///#{crawl_up_from(node.parent, to_node) if node != to_node}"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
end #DefaultNestedSetMethods
|
256
|
+
end #Utils
|
257
|
+
end #ErpTechSvcs
|