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.
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