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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -24
  3. data/app/controllers/api/v1/audit_log_items_controller.rb +33 -0
  4. data/app/controllers/api/v1/audit_logs_controller.rb +32 -0
  5. data/app/controllers/api/v1/capabilities_controller.rb +160 -0
  6. data/app/controllers/api/v1/file_assets_controller.rb +40 -0
  7. data/app/controllers/api/v1/groups_controller.rb +236 -0
  8. data/app/controllers/api/v1/security_roles_controller.rb +276 -0
  9. data/app/controllers/api/v1/users_controller.rb +262 -0
  10. data/app/controllers/erp_tech_svcs/session_controller.rb +8 -5
  11. data/app/controllers/erp_tech_svcs/user_controller.rb +14 -15
  12. data/app/mailers/user_mailer.rb +8 -5
  13. data/app/models/audit_log.rb +111 -36
  14. data/app/models/audit_log_item.rb +30 -0
  15. data/app/models/audit_log_item_type.rb +1 -0
  16. data/app/models/audit_log_type.rb +19 -0
  17. data/app/models/capability.rb +22 -6
  18. data/app/models/extensions/tracked_status_type.rb +3 -0
  19. data/app/models/file_asset.rb +245 -20
  20. data/app/models/file_asset_holder.rb +20 -0
  21. data/app/models/group.rb +38 -25
  22. data/app/models/notification.rb +32 -13
  23. data/app/models/notification_type.rb +13 -0
  24. data/app/models/security_role.rb +17 -4
  25. data/app/models/user.rb +116 -29
  26. data/app/validators/password_strength_validator.rb +1 -1
  27. data/app/views/user_mailer/activation_needed_email.html.erb +293 -15
  28. data/app/views/user_mailer/reset_password_email.html.erb +268 -13
  29. data/config/initializers/logger.rb +19 -0
  30. data/config/initializers/sorcery.rb +2 -0
  31. data/config/initializers/wickedpdf.rb +4 -0
  32. data/config/routes.rb +64 -0
  33. data/db/data_migrations/20110802200222_schedule_delete_expired_sessions_job.rb +1 -5
  34. data/db/data_migrations/20150819140550_create_job_tracker_for_notification.rb +14 -0
  35. data/db/migrate/20080805000010_base_tech_services.rb +99 -39
  36. data/db/migrate/20150414151421_add_nested_set_columns_to_security_role.rb +13 -0
  37. data/db/migrate/20150609003216_update_user_for_sorcery.rb +11 -0
  38. data/db/migrate/20150819135108_add_custom_fields_to_notifications.rb +5 -0
  39. data/db/migrate/20160122155402_add_description_to_file_asset.rb +13 -0
  40. data/db/migrate/20160310163060_add_created_by_updated_by_to_erp_tech_svcs.rb +35 -0
  41. data/db/migrate/20160313161611_add_tenant_id_to_audit_log.rb +16 -0
  42. data/lib/erp_tech_svcs.rb +6 -10
  43. data/lib/erp_tech_svcs/config.rb +7 -2
  44. data/lib/erp_tech_svcs/delayed_jobs/delete_expired_sessions_job.rb +49 -0
  45. data/lib/erp_tech_svcs/delayed_jobs/notification_job.rb +50 -0
  46. data/lib/erp_tech_svcs/engine.rb +0 -1
  47. data/lib/erp_tech_svcs/erp_tech_svcs_audit_log.rb +12 -6
  48. data/lib/erp_tech_svcs/extensions.rb +0 -1
  49. data/lib/erp_tech_svcs/extensions/active_record/has_capability_accessors.rb +57 -29
  50. data/lib/erp_tech_svcs/extensions/active_record/has_file_assets.rb +57 -31
  51. data/lib/erp_tech_svcs/extensions/active_record/has_security_roles.rb +12 -4
  52. data/lib/erp_tech_svcs/extensions/active_record/is_json.rb +22 -15
  53. data/lib/erp_tech_svcs/extensions/active_record/scoped_by.rb +16 -13
  54. data/lib/erp_tech_svcs/extensions/compass_ae/erp_base_erp_svcs/controllers/api/parties_controller.rb +15 -0
  55. data/lib/erp_tech_svcs/file_support.rb +1 -0
  56. data/lib/erp_tech_svcs/file_support/file_system_manager.rb +77 -44
  57. data/lib/erp_tech_svcs/file_support/manager.rb +12 -3
  58. data/lib/erp_tech_svcs/file_support/railties/compass_ae_resolver.rb +49 -0
  59. data/lib/erp_tech_svcs/file_support/s3_manager.rb +73 -51
  60. data/lib/erp_tech_svcs/utils/compass_access_negotiator.rb +11 -2
  61. data/lib/erp_tech_svcs/utils/default_nested_set_methods.rb +238 -46
  62. data/lib/erp_tech_svcs/version.rb +1 -1
  63. data/lib/tasks/erp_tech_svcs_tasks.rake +43 -5
  64. metadata +73 -42
  65. data/app/models/user_defined_data.rb +0 -6
  66. data/app/models/user_defined_field.rb +0 -8
  67. data/config/initializers/pdfkit.rb +0 -18
  68. data/db/data_migrations/20121130212146_note_capabilities.rb +0 -23
  69. data/db/migrate/20121116151510_create_groups.rb +0 -18
  70. data/db/migrate/20121126171612_upgrade_security.rb +0 -53
  71. data/db/migrate/20121126173506_upgrade_security2.rb +0 -274
  72. data/db/migrate/20130410135419_add_queue_to_delayed_jobs.rb +0 -13
  73. data/db/migrate/20130610163240_create_notifications.rb +0 -37
  74. data/db/migrate/20130725212647_add_party_id_idx_to_users.rb +0 -9
  75. data/db/migrate/20131113213843_add_audit_log_item_old_value.rb +0 -13
  76. data/db/migrate/20131113213844_add_erp_tech_svcs_missing_indexes.rb +0 -31
  77. data/db/migrate/20131129203603_add_user_defined_fields.rb +0 -43
  78. data/db/migrate/20141013060204_add_custom_fields_to_notifications.rb +0 -12
  79. data/db/migrate/20141108182427_add_scoped_by_to_file_assets.rb +0 -14
  80. data/lib/erp_tech_svcs/extensions/active_record/has_user_defined_data.rb +0 -147
  81. data/lib/erp_tech_svcs/sessions/delete_expired_sessions_job.rb +0 -47
  82. data/lib/erp_tech_svcs/sessions/delete_expired_sessions_service.rb +0 -15
  83. 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, :text => starting_path.split('/').last, :children => []} if paths.select{|path| path.split('/')[1] == starting_path.split('/')[1]}.empty?
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
- folders = node[:children].select{|item| !item[:leaf]}
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
- AWS.config(
15
- :logger => Rails.logger,
16
- :log_level => :info
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
- # Rails.logger.info "renaming from #{path} to #{new_path}"
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
- if options[:force] or bucket.as_tree(:prefix => path).children.count <= 1 # aws-sdk includes the folder itself as a child (like . is current dir), this needs revisited as <= 1 is scary
136
- bucket.objects.with_prefix(path).delete_all
137
- message = "File was deleted successfully"
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
- parent = {:text => path.split('/').pop, :leaf => false, :id => path, :children => []}
185
-
186
- tree = bucket.as_tree(:prefix => path)
187
- tree.children.each do |node|
188
- if node.leaf?
189
- #ignore current path that comes as leaf from s3
190
- next if node.key == path + '/'
191
-
192
- leaf_hash = {
193
- :text => node.key.split('/').pop,
194
- :downloadPath => "/#{node.key}",
195
- :id => "/#{node.key}",
196
- :leaf => true
197
- }
198
-
199
- if options[:file_asset_holder]
200
- files = options[:file_asset_holder].files
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
- parent[:children] << leaf_hash
219
- else
220
- parent[:children] << {
221
- :iconCls => "icon-content",
222
- :text => node.prefix.split('/').pop,
223
- :id => "/#{node.prefix}".chop,
224
- :leaf => false
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
- module Utils
3
- module DefaultNestedSetMethods
4
- def self.included(base)
5
- base.extend(ClassMethods)
6
- end
2
+ module Utils
3
+ module DefaultNestedSetMethods
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
7
 
8
- def to_label
9
- description
10
- end
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
- def leaf
13
- children.size == 0
14
- end
19
+ unless node.leaf?
20
+ to_all_representation(node, container_arr, (level + 1))
21
+ end
15
22
 
16
- def to_json_with_leaf(options = {})
17
- self.to_json_without_leaf(options.merge(:methods => :leaf))
18
- end
19
- alias_method_chain :to_json, :leaf
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
- def to_tree_hash(options={})
22
- options = options.merge({
23
- :text => self.to_label,
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
- self.to_hash(options)
29
- end
35
+ container_arr
36
+ end
30
37
 
31
- module ClassMethods
32
- def find_roots
33
- where("parent_id = nil")
34
- end
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
- def find_children(parent_id = nil)
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
- iids.each do |iid|
50
- if (iid == iids.first)
51
- node = where("parent_id is null and internal_identifier = ?",iid).first
52
- else
53
- node = where("parent_id = ? and internal_identifier = ?",node.id,iid).first
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
- return node
63
+
64
+ node
58
65
  end
59
-
60
- end
61
66
 
62
- end #DefaultNestedSetMethods
63
- end #Utils
64
- end #ErpTechSvcs
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