erp_tech_svcs 4.0.0 → 4.2.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.
- 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
|