Advertisement
Guest User

Untitled

a guest
Jul 15th, 2016
71
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 37.08 KB | None | 0 0
  1. require 'dm-aggregates'
  2. require 'lib/sbfapi/data_mapper_db'
  3. require 'lib/sbfapi/errors/sbf_error'
  4. require 'v2/lib/sbf_error'
  5. require 'v2/services/lib/base'
  6. require 'lib/sbfapi/log'
  7. require 'wisper'
  8.  
  9.  
  10. module V2
  11. # A base service to provide helper and utility methods to service classes
  12. class OrmService < V2::BaseService
  13. extend Wisper::Publisher
  14. @entity_class = nil
  15.  
  16. # Get a reference to the entity class associated with this controller (assuming things are named correctly)
  17. #
  18. def self.entity_class
  19. @entity_class ||= const_get(name.deconstantize).const_get(name.demodulize.rpartition('Service').first)
  20. end
  21. private_class_method :entity_class
  22.  
  23. def self.default_tenant
  24. consumer_id = RequestSession[:consumer_id] || 0
  25. profile_id = RequestSession[:profile_id] || 0
  26. is_admin = RequestSession[:is_admin] || false
  27.  
  28. V2::Tenant.new(consumer_id: consumer_id, profile_id: profile_id, is_admin: is_admin)
  29. end
  30. private_class_method :default_tenant
  31.  
  32. # rubocop:disable BlockComments
  33. =begin
  34. # TODO:
  35. #
  36. # Yes, commented out code is BAD. However we have intentions of implmenting a generic route
  37. # in the future and we believe this code is that foundation that the code will be built on.
  38. # Therefore we are leaving the commented out code here so that we don't have to figure this
  39. # all out again in the future.
  40.  
  41. def self.rank(field = nil, filter = nil, order = nil)
  42. collection = entity_class.all
  43. unless filter.empty?
  44. filter = parse_filter(filter)
  45. collection = recursively_apply_filter(filter, collection)
  46. conditions_statement, bind_values = collection.query.repository.adapter.send(:conditions_statement, collection.query.conditions, true)
  47. end
  48.  
  49. collection = collection.by_sql(entity_class) { |entity, entity2|
  50. bind_variables = []
  51. statement = []
  52.  
  53. fields = field.split('.')
  54. if fields.count == 1
  55. entity2.model_class.
  56. tmp = "SELECT (COUNT(*) FROM #{entity2} WHERE (#{conditions_statement}) AND
  57. #{entity2.send(fields[0].to_sym)} > #{entity.send(fields[0].to_sym)}) + 1 AS rank
  58. FROM #{entity} WHERE #{conditions_statement}"
  59. statement << tmp
  60. elsif fields.count == 2
  61.  
  62. else
  63. raise V2::SBFBadRequestError, "Improper field was passed in. Can only rank one field deep."
  64. end
  65.  
  66. # Join on participant to get the team captain.
  67. inner_join, inner_binds = team.model_class.build_inner_join([:event])
  68. statement << inner_join
  69. bind_variables.push(*inner_binds)
  70.  
  71. bind_variables.push(*bind_values) unless bind_values.nil?
  72.  
  73. #statement << "WHERE (#{conditions_statement})"
  74. #statement << "ORDER BY #{team.team_name} ASC"
  75. [statement.join(' '), *bind_variables]
  76. }
  77.  
  78. collection
  79. end
  80. =end
  81. # rubocop:enable BlockComments
  82.  
  83. # Creates an object using the input data
  84. #
  85. # @option data [Hash] Hash of parameters to be updated
  86. #
  87. # @raise [V2::SBFMalformedRequestError] if one of the required params is not passed in
  88. # @raise [V2::SBFBadRequestError] if one of the params passed in is not good
  89. # @raise [V2::SBFSaveError] if the object could not be created
  90. #
  91. def self.create(params, skip_deduplicator = false)
  92. # Prune out relationship information
  93. parent_data, child_data = prune_parents_and_children(params)
  94.  
  95. # Make sure the params are valid
  96. ensure_valid_create_params(params, parent_data)
  97.  
  98. # Create new class instance of the entity
  99. entity = entity_class.new(params)
  100.  
  101. entity_class.transaction do
  102. # Create/update relationships and set them on the entity
  103. prepare_parent_relationships(entity, parent_data)
  104.  
  105. # Ensure the policy allows tenant to create the entity
  106. authorize_create(entity, default_tenant)
  107.  
  108. # Call generic save method
  109. entity = skip_deduplicator ? V2::ProfileService.save(entity, skip_deduplicator) : save(entity)
  110.  
  111. # Create/update relationships and set them on the entity
  112. prepare_child_relationships(entity, child_data)
  113.  
  114. # Ensure the policy allows tenant to update the entity
  115. authorize_update(entity, default_tenant)
  116.  
  117. # Call save again in case the setting of relationship made the entity dirty
  118. skip_deduplicator ? V2::ProfileService.save(entity, skip_deduplicator) : save(entity)
  119. end
  120. end
  121.  
  122. # Returns true if the params suggest that the entity already exists
  123. # (Checks to see if the key fields have been specified)
  124. #
  125. def self.needs_create?(params, clazz = entity_class)
  126. !params_present_and_set?(clazz.keys, params)
  127. end
  128.  
  129. # Errors if required fields are missing or an invalid column is found
  130. #
  131. def self.ensure_valid_create_params(params, pruned = {}, allow_serial = false)
  132. # Don't allow a user to set any serial fields
  133. params.reject! { |k, _| entity_class.serial?(k) } unless allow_serial
  134.  
  135. # We need to allow for required parameters which have a default value configured
  136. required_properties_without_defaults = entity_class.requireds - entity_class.columns_with_default_values
  137.  
  138. # Make sure all required parameters are present
  139. missing = required_properties_without_defaults.select { |param| !params.include?(param) }
  140.  
  141. #If a required property is not present but the association it is related to is
  142. special_properties = missing.select do |param|
  143. parts = param.to_s.split('_')
  144. parts.pop
  145. property = parts.join('_').to_sym
  146. pruned.key?(property) && !(pruned[property].nil?)
  147. end
  148. missing -= special_properties
  149.  
  150. raise(V2::SBFMalformedRequestError, details: 'Missing required parameter(s)', errors: missing.to_a) unless missing.empty?
  151.  
  152. # Make sure all required parameters are non-empty
  153. unset = required_properties_without_defaults.select { |param| params[param].empty? }
  154. unset -= special_properties
  155. raise(V2::SBFBadRequestError, details: 'Required parameter(s) must be set', errors: unset.to_a) unless unset.empty?
  156.  
  157. # Also make sure that all parameters are valid columns
  158. check_params_valid(entity_class.:columns, params)
  159. end
  160. private_class_method :ensure_valid_create_params
  161.  
  162. # Saves the entity and properly formats any errors
  163. #
  164. def self.save(entity, skip_deduplication = false)
  165. # Make sure we have changed something
  166. unless entity.dirty?
  167. LOG.info { "Ignoring save on #{entity.class.name.demodulize} because no attributes have changed" }
  168. return entity
  169. end
  170.  
  171. LOG.debug {
  172. dirty_attributes = entity.dirty_attributes.map { |k, v| "#{k.name} => #{v}" }
  173. "Saving #{entity.class.name.demodulize}, fields [ #{dirty_attributes.join(', ')} ]"
  174. }
  175.  
  176. # Execute the create in a transaction
  177. begin
  178. entity.new? ? authorize_create(entity, default_tenant) : authorize_update(entity, default_tenant)
  179. # Save will return false if it fails for any reason
  180. unless skip_deduplication ? V2::ProfileService.save(entity, skip_deduplication) : entity.save
  181. raise V2::SBFBadRequestError, "Could not save #{entity.class.name.demodulize}: #{entity.collect_errors.join('. ')}"
  182. end
  183.  
  184. rescue V2::SBFError => sbfe
  185. raise sbfe
  186.  
  187. rescue => e
  188. LOG.error { "Error saving database entity: #{e}\n#{e.backtrace[0..10].join("\n")}" }
  189. raise V2::SBFSaveError, "Could not save #{entity.class.name.demodulize}"
  190. end
  191.  
  192. # Reload the entity to get the value of any defaulted parameters
  193. entity.reload
  194. end
  195.  
  196. # Gets an object using the input data
  197. #
  198. # @option key_field_hash [Hash] {key_column_name_1: key_column_value_1, ..n} All key fields for the object
  199. #
  200. # @raise [V2::SBFMalformedRequestError] if one of the required params is not passed in
  201. # @raise [V2::SBFBadRequestError] if one of the params passed in is not good
  202. #
  203. # @return [Hash] A hash of the data for that object
  204. #
  205. def self.get(key_field_hash)
  206. # Find any combination of input keys which represent a unique entry
  207. key_columns = entity_class.key_columns(*key_field_hash.keys)
  208. raise V2::SBFInternalServerError if key_columns.nil?
  209.  
  210. # Select only the key fields - in case the user gave us extra
  211. key_values = key_columns.map { |k| key_field_hash[k] }
  212. entity_class.get(*key_values)
  213. end
  214.  
  215. def self.find(filter = nil, order = nil, limit = nil, offset = nil, total = false)
  216. # Start a blank select * query
  217. query = entity_class.all
  218.  
  219. # Parse order and apply (if an order was given)
  220. unless order.empty?
  221. parsed_order = parse_order(order)
  222. query = query.all(parsed_order)
  223. end
  224.  
  225. # Apply filter
  226. unless filter.empty?
  227. # Back up the links because recursively applying the filter is going to wipe them out. :(
  228. links = Set.new(query.instance_variable_get('@query').instance_variable_get('@links').to_a)
  229.  
  230. # Parse the filter
  231. parsed_filter = parse_filter(filter)
  232.  
  233. # Filter will be an array of nested arrays and/or hashes. Arrays serve to define parenthetical expressions.
  234. # Hashes serve to define query parameters.
  235. query = recursively_apply_filter(parsed_filter, query)
  236.  
  237. # Need to replace the original links b/c they are not preserved.
  238. query_instance = query.instance_variable_get('@query')
  239. if query_instance
  240. links.merge(query_instance.instance_variable_get('@links').to_a)
  241. query_instance.instance_variable_set('@links', links.to_a)
  242. end
  243. end
  244.  
  245. # Add the limit and offset if they are both set
  246. #
  247. # NOTE: You can't specify ONLY an offset
  248. if !limit.empty? && !offset.empty?
  249. query = query.all(limit: limit, offset: offset)
  250.  
  251. # Add the limit if it is set
  252. elsif !limit.empty?
  253. query = query.all(limit: limit)
  254.  
  255. end
  256.  
  257. # Calculate total also, if requested
  258. if total
  259. # Just return the current length if it is < than the limit (since there can't be any more)
  260. return [query, query.length] if limit.nil? || query.length < limit && offset.to_i <= 0
  261.  
  262. # Force a count distinct.
  263. parsed_filter = [{}] if parsed_filter.empty?
  264. recursively_add_unique_filter(parsed_filter)
  265.  
  266. # Perform a count query using the same filter
  267. return [query, recursively_apply_filter(parsed_filter, entity_class.all).count]
  268. end
  269.  
  270. query
  271. end
  272.  
  273. # Recursive to find the first hash and add a 'unique' boolean key
  274. #
  275. def self.recursively_add_unique_filter(filter)
  276. if filter.is_a?(Hash)
  277. filter[:unique] = true
  278. return true
  279.  
  280. elsif filter.is_a?(Array)
  281. filter.each do |item|
  282. return true if recursively_add_unique_filter(item)
  283. end
  284. end
  285. end
  286.  
  287. # Calls list with the given filter but returns the first entity or nil.
  288. #
  289. def self.find_first(filter, order = nil)
  290. find(filter, order, 1).first
  291. end
  292.  
  293. # This method joins queries and subqueries in a recursive fashion and returns the result
  294. #
  295. def self.recursively_apply_filter(filters, base_query)
  296. return base_query if filters.empty?
  297.  
  298. query = nil
  299. condition = nil
  300. (0..filters.length).step(2) {|index|
  301. filter = filters[index]
  302.  
  303. if filter.is_a?(Hash)
  304. # If the filter is a has we can apply it immediately
  305. filtered_query = base_query.all(filter)
  306.  
  307. elsif filter.is_a?(Array)
  308. # If the filter is an array we need to call this method with it
  309. filtered_query = recursively_apply_filter(filter, base_query)
  310.  
  311. else
  312. # Must have a properly formatted filter
  313. LOG.info { "Invalid filter entry #{filter} in filter #{filters}" }
  314. raise V2::SBFBadRequestError, errors: 'filter'
  315.  
  316. end
  317.  
  318. if index == 0
  319. # If this is our first entry, set the query to it without joining with any other conditional
  320. query = filtered_query
  321.  
  322. else
  323. # All other even entries will need to be joined with the conditional
  324. condition = filters[index - 1]
  325. unless entity_class.valid_condition?(condition)
  326. LOG.info { "Invalid filter condition #{condition} in filter #{filters}" }
  327. raise V2::SBFBadRequestError,
  328. errors: 'filter'
  329.  
  330. end
  331. query = query.send(condition, filtered_query)
  332.  
  333. end
  334. }
  335.  
  336. query
  337. end
  338. private_class_method :recursively_apply_filter
  339.  
  340. def self.parse_filter(input_filter)
  341. # Return if input filter is nil or empty
  342. return nil if input_filter.empty?
  343.  
  344. # Outermost part of the filter should be an array
  345. input_filter = [input_filter] if input_filter.is_a?(Hash)
  346. raise(V2::SBFBadRequestError, errors: 'filter') unless input_filter.is_a?(Array)
  347.  
  348. # Loop over the filter array and parse. If we encounter an array, assume it is
  349. # a nested filter that needs to be recursively parsed
  350. input_filter.map do |filter_entry|
  351. if filter_entry.is_a?(Hash)
  352. {}.tap do |filter_hash|
  353. recursively_parse_filter_operands(filter_entry) do |operand, value|
  354. # If we specify multiple filter_hash operands which are identical,
  355. # put in an array so an 'in' query is performed
  356. existing_value = filter_hash[operand]
  357. if existing_value
  358. if existing_value.is_a?(Array)
  359. # value could be a single thing or an array so we have to be careful to add all of the things
  360. existing_value.push(*Array(value))
  361. else
  362. # value could be a single thing or an array so we have to be careful to add all of the things
  363. filter_hash[operand] = Array(existing_value).push(*Array(value))
  364. end
  365. else
  366. filter_hash[operand] = value
  367. end
  368. end
  369. end
  370.  
  371. elsif filter_entry.is_a?(Array)
  372. # Entry is a sub-filter. Recursively parse it.
  373. parse_filter(filter_entry)
  374.  
  375. elsif entity_class.valid_condition?(filter_entry.to_sym)
  376. # Entry is a condition. Add it back unmodified
  377. filter_entry.to_sym
  378.  
  379. else
  380. LOG.info { "Invalid filter entry #{filter_entry} in filter #{input_filter}" }
  381. raise V2::SBFBadRequestError, errors: 'filter'
  382.  
  383. end
  384. end
  385. end
  386. private_class_method :parse_filter
  387.  
  388. # rubocop:disable MethodLength, CyclomaticComplexity
  389. def self.recursively_parse_filter_operands(entry, current_model = entity_class, operands = [], &block)
  390. entry.each do |key, value|
  391. # If the value is not an enumerable, there is no more parsing to do.
  392. #
  393. # Also, if the value is an array and none of it's values are enumerable,
  394. # then we must be specifying a range, so we are also done.
  395. if !value.is_a?(Enumerable) || (value.is_a?(Array) && !value.empty? && !value.any? { |it| it.is_a?(Enumerable) })
  396. # If the key is an operator, combine the operands and create the query operator object.
  397. if current_model.operators.include?(key)
  398. # :eql and :in are assumed and it will error if you set them
  399. operand = operands.join('.').to_sym
  400. operand = operand.send(key) unless key == :eql || key == :in
  401. prop_name = operands.last.to_sym
  402.  
  403. # Otherwise, the key must be a column name. Add it as an operand.
  404. elsif current_model.valid_column?(key)
  405. operand = operands.empty? ? key.to_sym : "#{operands.join('.')}.#{key}".to_sym
  406. prop_name = key.to_sym
  407.  
  408. else
  409. LOG.info { "Invalid filter key #{key}" }
  410. raise V2::SBFBadRequestError, errors: 'filter'
  411.  
  412. end
  413.  
  414. # Parse values to their appropriate types. This is mainly to facilitate with date searches.
  415. property_class = current_model.properties.values_at(prop_name).first
  416. if property_class.methods.include?(:typecast_to_primitive) && !value.is_a?(Enumerable)
  417. value = property_class.send(:typecast_to_primitive, value)
  418. end
  419.  
  420. yield [operand, value]
  421.  
  422. # This is a weird edge case to handle the way roles are transformed by the view layer.
  423. # I really can't think of a more elegant solution.
  424. #
  425. # Handles a case like: {roles [{id: 1}, {id: 2}]}
  426. elsif value.is_a?(Array) && value.all? { |it| it.is_a?(Enumerable) }
  427. sub_model = current_model.association_model(key)
  428. sub_operand = operands.dup << key
  429. value.each do |it|
  430. recursively_parse_filter_operands(it, sub_model, sub_operand, &block)
  431. end
  432.  
  433. # If the key is an association, make a recursive call with the updated values for current model and operand
  434. elsif current_model.valid_association?(key)
  435. sub_model = current_model.association_model(key)
  436. sub_operand = operands.dup << key
  437. recursively_parse_filter_operands(value, sub_model, sub_operand, &block)
  438.  
  439. # If we have gotten here and the key is a column, that must mean that we have a complex value. Use recursion to parse it.
  440. # Handles cases like {id: {gt: 1, lt: 100}}
  441. elsif current_model.valid_column?(key)
  442. sub_operand = operands.dup << key
  443. recursively_parse_filter_operands(value, current_model, sub_operand, &block)
  444.  
  445. else
  446. LOG.info { "Invalid filter entry #{key}, #{value}" }
  447. raise V2::SBFBadRequestError, errors: 'filter'
  448.  
  449. end
  450. end
  451. end
  452. # rubocop:enable MethodLength, CyclomaticComplexity
  453. private_class_method :recursively_parse_filter_operands
  454.  
  455. def self.parse_order(input_order)
  456. # Return if input order is nil or empty
  457. return nil if input_order.empty?
  458.  
  459. # Order should be a Hash
  460. raise(V2::SBFBadRequestError, errors: 'order') unless input_order.is_a?(Hash)
  461.  
  462. # Start with a blank query object
  463. query = entity_class.all.query
  464.  
  465. # Create the order array
  466. orders = Set.new
  467. links = Set.new
  468.  
  469. # Recursively iterate through the input and
  470. recursively_parse_order_operands(input_order) do |order, link|
  471. orders << order
  472. links |= link
  473. end
  474.  
  475. query.instance_variable_set('@order', orders.to_a)
  476. query.instance_variable_set('@links', links.to_a)
  477.  
  478. # return the query order object
  479. query
  480. end
  481. private_class_method :parse_order
  482.  
  483. def self.recursively_parse_order_operands(orders, current_model = entity_class, links = [], &block)
  484. orders.each do |key, value|
  485. # If value is not enumerable, then we are done recursing
  486. if !value.is_a?(Enumerable)
  487. # Make sure the key is a valid column
  488. if current_model.valid_column?(key)
  489. operand = current_model.properties.values_at(key).first
  490.  
  491. else
  492. LOG.info { "Invalid order column #{key}" }
  493. raise V2::SBFBadRequestError, errors: 'order'
  494.  
  495. end
  496.  
  497. # Make sure the value is a valid direction
  498. value = value.to_sym
  499. if current_model.orders.include?(value)
  500. yield [DataMapper::Query::Direction.new(operand, value), links]
  501. else
  502. LOG.info { "Invalid order direction #{value}" }
  503. raise V2::SBFBadRequestError, errors: 'order'
  504.  
  505. end
  506.  
  507. elsif current_model.valid_association?(key)
  508. sub_model = current_model.association_model(key)
  509. sub_links = links.dup << current_model.relationships[key.to_s].inverse
  510. recursively_parse_order_operands(value, sub_model, sub_links, &block)
  511.  
  512. else
  513. LOG.info { "Invalid order key #{key}" }
  514. raise V2::SBFBadRequestError, errors: 'order'
  515.  
  516. end
  517. end
  518. end
  519. private_class_method :recursively_parse_order_operands
  520.  
  521. def self.aggregate(filter, aggregate)
  522. # Start a blank select * query
  523. query = entity_class.all
  524.  
  525. # Apply filter
  526. unless filter.empty?
  527. # Parse the filter
  528. parsed_filter = parse_filter(filter)
  529.  
  530. # Filter will be an array of nested arrays and/or hashes. Arrays serve to define parenthetical expressions.
  531. # Hashes serve to define query parameters.
  532. query = recursively_apply_filter(parsed_filter, query)
  533. end
  534.  
  535. # Format aggregates appropriately
  536. raise V2::SBFMalformedRequestError, 'At least one aggregation must be specified' if aggregate.empty?
  537. parsed_aggregate = aggregate.map { |k, v|
  538. v = v.to_sym if v.is_a?(String)
  539. raise V2::SBFMalformedRequestError, "Invalid aggregate #{v}" unless entity_class.valid_aggregate?(v)
  540. k.send(v)
  541. }
  542.  
  543. # Perform aggregate query
  544. result = query.aggregate(*parsed_aggregate)
  545.  
  546. # Normalize the results
  547. result = [result] unless result.is_a?(Array)
  548.  
  549. # Loop over results and map then back to their column name, returning the result
  550. {}.tap do |hsh|
  551. result.each_with_index do |it, index|
  552. hsh[parsed_aggregate[index].target] = it
  553. end
  554. end
  555. end
  556.  
  557. # Updates an existing object using the input data
  558. #
  559. # @option entity [Object] Entity object to be updated
  560. # @option data [Hash] Hash of parameters to be updated
  561. #
  562. # @raise [V2::SBFMalformedRequestError] if one of the required params is not passed in
  563. # @raise [V2::SBFBadRequestError] if one of the params passed in is not good
  564. # @raise [V2::SBFSaveError] if the existing object is not found
  565. #
  566. def self.update(entity, data = {}, skip_deduplicator = false)
  567. # Prune out relationship information
  568. parent_data, child_data = prune_parents_and_children(data)
  569.  
  570. # Make sure the params are valid
  571. ensure_valid_update_params(data)
  572.  
  573. # Create the relationships and relate them to the base entity. Then save the entity (all in one transaction)
  574. entity_class.transaction do
  575. # Create entities for parent relationships
  576. prepare_parent_relationships(entity, parent_data)
  577.  
  578. # Set attributes to what was specified
  579. entity.attributes = data unless data.empty?
  580.  
  581. # Ensure the policy allows tenant to update the entity
  582. authorize_update(entity, default_tenant)
  583.  
  584. # Call generic save method
  585. skip_deduplicator ? V2::ProfileService.save(entity, skip_deduplicator) : save(entity)
  586.  
  587. # Create child relationships
  588. prepare_child_relationships(entity, child_data)
  589.  
  590. # Ensure the policy allows tenant to update the entity
  591. authorize_update(entity, default_tenant)
  592.  
  593. # Call save again in case the setting of relationship made the entity dirty
  594. skip_deduplicator ? V2::ProfileService.save(entity, skip_deduplicator) : save(entity)
  595. end
  596. end
  597.  
  598. # Errors if an invalid column is found
  599. #
  600. def self.ensure_valid_update_params(params)
  601. # Don't allow a user to set any serial fields
  602. params.reject! { |k, _| entity_class.serial?(k) } # Throw error?
  603.  
  604. # We need to allow for required parameters which have a default value configured
  605. required_properties_without_defaults = entity_class.requireds - entity_class.columns_with_default_values
  606.  
  607. # If a required parameter is being updated it must not be null
  608. check_params_set_if_present(required_properties_without_defaults, params)
  609.  
  610. # Make sure that all parameters are valid columns
  611. check_params_valid(entity_class.columns, params)
  612. end
  613. private_class_method :ensure_valid_update_params
  614.  
  615. # Deletes an object
  616. #
  617. # @raise [V2::SBFBadRequestError] if there is a problem destroying object
  618. # @raise [V2::SBFError] if there is a general error
  619. # @raise [V2::SBFDeleteError] if deleting the object fails
  620. #
  621. # @return [Array] The object that was just deleted
  622. #
  623. def self.delete(entity)
  624. begin
  625. # Ensure the policy allows tenant to destroy the entity
  626. authorize_destroy(entity, default_tenant)
  627. unless entity.destroy
  628. error_string = entity.collect_errors.join('. ')
  629. if error_string.empty?
  630. error_string = 'Delete failed'
  631. entity.send(:relationships).each do |relationship|
  632. next unless relationship.class.name.include?('OneToMany') || relationship.class.name.include?('OneToOne')
  633. next unless relationship.respond_to?(:enforce_destroy_constraint)
  634. error_string << ", related #{relationship.name}" unless relationship.enforce_destroy_constraint(entity)
  635. end
  636. end
  637.  
  638. # TODO: May need to also iterate over any relationships we are changing...
  639. raise V2::SBFBadRequestError, "Could not delete #{entity.class.name.demodulize}: #{error_string}"
  640. end
  641.  
  642. nil
  643.  
  644. rescue V2::SBFError => sbfe
  645. raise sbfe
  646.  
  647. rescue => e
  648. LOG.error { "Error deleting database entity: #{e}\n#{e.backtrace[0..10].join("\n")}" }
  649. raise V2::SBFDeleteError, "Could not delete #{entity.class.name.demodulize}"
  650. end
  651.  
  652. # Return the entity
  653. entity
  654. end
  655.  
  656. def self.do_delete_procedure(entity, input_fields = nil)
  657. # Ensure the policy allows tenant to destroy the entity
  658. authorize_destroy(entity, default_tenant)
  659.  
  660. # Manually kick off the before destroy hooks for the entity
  661. entity.send(:execute_hooks_for, :before, :destroy)
  662.  
  663. begin
  664.  
  665. # Allow for a user to specify the input fields - if they don't, default to the keys for the entity
  666. input_fields = (entity.class.keys + entity.class.pseudo_keys.to_a) if input_fields.nil?
  667. input_fields = Array(input_fields)
  668.  
  669. # Grab fields and current user as agruments.
  670. arguments = input_fields.map { |key_field| entity.send(key_field) }
  671. arguments << RequestSession[:profile_id].to_i
  672.  
  673. # Stored procedure is probably as follows:
  674. name = "uspDelete#{entity.class.name.demodulize}"
  675.  
  676. # Execute the procedure
  677. execution_status = do_procedure(name, arguments)
  678. unless execution_status == 0
  679. LOG.error { "Stored procedure returned #{execution_status} which was interpreted as a failure." }
  680. raise V2::SBFDeleteError, "Could not delete #{entity.class.name.demodulize}"
  681. end
  682.  
  683. # Manually kick off the before destroy hooks for the entity
  684. entity.send(:execute_hooks_for, :after, :destroy)
  685.  
  686. rescue V2::SBFError => sbfe
  687. raise sbfe
  688.  
  689. rescue => e
  690. LOG.error { "Error calling stored procedure #{name} with arguments #{arguments}: #{e}\n#{e.backtrace[0..10].join("\n")}" }
  691. raise V2::SBFDeleteError, "Could not delete #{entity.class.name.demodulize}"
  692. end
  693.  
  694. entity
  695. end
  696.  
  697. def self.do_procedure(name, arguments)
  698. host = SETTINGS['db_writeonly_hostname']
  699. database = SETTINGS['db_writeonly_name']
  700. user = SETTINGS['db_writeonly_username']
  701. password = SETTINGS['db_writeonly_password']
  702. client = Mysql2::Client.new(
  703. host: host,
  704. database: database,
  705. username: user,
  706. password: password,
  707. flags: Mysql2::Client::MULTI_STATEMENTS
  708. )
  709. # utf encode the input and escape any potentially dangerous characters.
  710. arguments.map! { |argument| escape(client, argument) }
  711. # Build the stored procedure call
  712. command = "call #{name}('#{arguments.join("','")}');"
  713. LOG.debug { "Calling stored procedure as [ #{command} ]." }
  714. result = client.query(command)
  715. # Clear all remaining data off the socket
  716. client.store_result while client.next_result
  717.  
  718. # First entry in result should contain a hash of the execution status
  719. result.first['ExecutionStatus']
  720. ensure
  721. client.close if client
  722. end
  723.  
  724. def self.escape(db, value)
  725. return value unless value.is_a? String
  726.  
  727. db.escape(utf_encode(value))
  728. end
  729. private_class_method :escape
  730.  
  731. # Takes in an array of relationship names and verifies that the value of all key fields in the passed in data matches
  732. # the current value of those keys on the entity. This should ensure that only updates are happening to those objects
  733. # and not any creates
  734. def self.disallow_relationship_move(entity, data, relationship_names)
  735. Array(relationship_names).each do |relationship_name|
  736. next unless data.key?(relationship_name)
  737. passed_in_relationship = data[relationship_name]
  738.  
  739. relationship = entity.class.relationships.entries.select { |r| r.name == relationship_name }[0]
  740. relationship.parent_key.to_ary.each_with_index do |prop, index|
  741. if passed_in_relationship[prop.name] != entity.send(relationship.child_key.to_ary[index].name)
  742. raise V2::SBFBadRequestError, "Changing #{relationship_name}s is not allowed via this route"
  743. end
  744. end
  745. end
  746. end
  747.  
  748. ##############################################
  749. ################ DEPRECATED ##################
  750. # USE THE NEW METHODS WHICH DISTINGUISH #
  751. # BETWEEN PARENTS AND CHILDREN #
  752. ##############################################
  753. # This method removes the relationship data from the original data and stores it in it's own hash
  754. def self.prune_relationship_data(data)
  755. {}.tap do |hsh|
  756. entity_class.associations.each do |k, _|
  757. hsh[k] = data.delete(k) if data.key?(k)
  758. end
  759. end
  760. end
  761. private_class_method :prune_relationship_data
  762.  
  763. # This method should create or update the inter-relationships for the entity
  764. #
  765. def self.prepare_entity_relationships(entity, params)
  766. prepare_parent_relationships(entity, params)
  767. end
  768. private_class_method :prepare_entity_relationships
  769. ##############################################
  770. ##############################################
  771. ##############################################
  772.  
  773. # This method removes the relationship data from the original data and stores it in it's own hash
  774. def self.prune_parents_and_children(data)
  775. parents = {}
  776. children = {}
  777.  
  778. entity_class.associations.each do |k, _|
  779. next unless data.key?(k)
  780.  
  781. if entity_class.many_to_one_associations.include?(k)
  782. parents[k] = data.delete(k)
  783. else
  784. children[k] = data.delete(k)
  785. end
  786. end
  787.  
  788. [parents, children]
  789. end
  790. private_class_method :prune_relationship_data
  791.  
  792. # This method should create or update the inter-relationships for the entity
  793. #
  794. def self.prepare_parent_relationships(entity, params)
  795. params.each do |k, v|
  796. # We want to continue if we have no changes, but check for enumerable class to handle the case
  797. # where we want to set the relationship to nil
  798. next if v.is_a?(Enumerable) && v.empty?
  799.  
  800. relationship_class = entity_class.associations[k]
  801.  
  802. # If the value is an array, then the relationship is a collection and we need
  803. # call create_or_update for each item in the collection
  804. if v.is_a?(Array)
  805. value = v.map { |it| create_or_update(relationship_class, it) }
  806. else
  807. value = create_or_update(relationship_class, v)
  808. end
  809.  
  810. entity.send("#{k}=", value)
  811. end
  812. end
  813. private_class_method :prepare_parent_relationships
  814.  
  815. # This method should create or update the inter-relationships for the entity
  816. #
  817. def self.prepare_child_relationships(entity, params)
  818. params.each do |k, v|
  819. # We want to continue if we have no changes, but check for enumerable class to handle the case
  820. # where we want to set the relationship to nil
  821. next if v.is_a?(Hash) && v.empty?
  822.  
  823. relationship_class = entity_class.associations[k]
  824.  
  825. # We need to pull the parent fields from the entity and place them in the data to create
  826. parent_key_values = parent_entity_key_field_values(entity, k)
  827.  
  828. # If the value is an array, then the relationship is a collection and we need
  829. # call create_or_update for each item in the collection
  830. if v.is_a?(Array)
  831. value = v.map { |it| create_or_update(relationship_class, it.merge(parent_key_values)) }
  832. elsif v.respond_to?(:merge)
  833. value = create_or_update(relationship_class, v.merge(parent_key_values))
  834. end
  835.  
  836. entity.send("#{k}=", value)
  837. end
  838. end
  839. private_class_method :prepare_child_relationships
  840.  
  841. # If the hash contains the key fields for the given entity class, this method will perform a get
  842. # using those key fields and then perform an update of the entity using the input data.
  843. #
  844. # If the hash does not contain all primary key fields, then this method will attempt to do a create
  845. # for the given entity class using the input data
  846. #
  847. # Finally, this method returns the updated/created entity
  848. #
  849. def self.create_or_update(relationship_class, data)
  850. # We only care if data has been passed in
  851. return nil if data.empty?
  852. # Dynamically figure out the service class
  853. entity_service_class = const_get(name.deconstantize).const_get("#{relationship_class.to_s.demodulize}Service")
  854.  
  855. # Perform create if key fields are not specified
  856. if entity_service_class.needs_create?(data, relationship_class)
  857. LOG.debug { "Creating new #{relationship_class}" }
  858. return entity_service_class.create(data)
  859. end
  860.  
  861. # Probably an update, then. Look up the entity using the key fields
  862. LOG.debug { "Looking up #{entity_service_class} using #{data}" }
  863. entity = entity_service_class.get(data)
  864.  
  865. # So I'm not sure if this is what we want or not. The problem is that for several entities the keys are derived
  866. # from other tables and there is no auto-generated id field. This means there is no real way to tell if
  867. # the user was trying to update or create the entity. I believe it makes sense to assume if the user passed in
  868. # information that has key fields (PLUS additional data) but does not exist, then that means we want to create it.
  869. if entity.nil?
  870. # If the entity has serial fields (auto-increment) or they passed in the exact key fields (and there are other fields on the entity),
  871. # assume they were attempting to just link the entities and do not try to do a create
  872. if !relationship_class.serials.empty? ||
  873. ((data.keys - relationship_class.keys).empty? && !(relationship_class.columns - relationship_class.keys).empty?)
  874. raise V2::SBFBadRequestError, "#{relationship_class.to_s.demodulize} #{data.values.join(', ')} does not exist"
  875. end
  876.  
  877. LOG.debug { "Creating new #{relationship_class}" }
  878. return entity_service_class.create(data)
  879. end
  880.  
  881. # Update entity using the passed in data
  882. entity_service_class.update(entity, data)
  883. end
  884. private_class_method :create_or_update
  885.  
  886. # Loop over the parent keys, get the values of the properties they represent,
  887. # then create a hash of them using they child key names which they map to
  888. def self.parent_entity_key_field_values(entity, relationship_name)
  889. {}.tap do |hsh|
  890. relationship = entity_class.relationships.find { |it| it.name == relationship_name }
  891.  
  892. # TODO: So this is a work around for the time being. I'm not 100% sure of exactly what needs to
  893. # happen if this is a M2M relationship b/c you have to take the through/via layer into account.
  894. # I believe this can be accomplished using the "links" method but I don't need it at the moment
  895. # and so I'm going to just leave this work-around for now until we have a better idea of what is needed.
  896. next if relationship.is_a? DataMapper::Associations::ManyToMany::Relationship
  897.  
  898. parent_keys = relationship.parent_key.to_ary
  899. child_keys = relationship.child_key.to_ary
  900.  
  901. parent_keys.each_with_index do |parent_property, index|
  902. child_property = child_keys[index]
  903. value = entity.send(parent_property.name)
  904. hsh[child_property.name] = value unless value.nil?
  905. end
  906.  
  907. LOG.debug { "Updating #{entity.class.name} entity with #{hsh}" }
  908. end
  909. end
  910. private_class_method :parent_entity_key_field_values
  911.  
  912. def self.authorize_create(entity, tenant = default_tenant)
  913. unless V2::SecurityHelper.__security_bypass__
  914. if V2::Policy.defined_for?(entity)
  915. unless V2::Policy.find(entity, tenant).create_allowed?
  916. values = entity.keys.map { |it| "#{it}: #{entity.send(it) || '<not created>'}" }
  917. raise V2::SBFUnauthorizedError, "Unauthorized to create #{entity.class.name.demodulize} (#{values.join(', ')})"
  918. end
  919. end
  920. end
  921. end
  922. private_class_method :authorize_create
  923.  
  924. def self.authorize_update(entity, tenant = default_tenant)
  925. unless V2::SecurityHelper.__security_bypass__
  926. if V2::Policy.defined_for?(entity)
  927. unless V2::Policy.find(entity, tenant).update_allowed?
  928. values = entity.keys.map { |it| "#{it}: #{entity.send(it)}" }
  929. raise V2::SBFUnauthorizedError, "Unauthorized to update #{entity.class.name.demodulize} (#{values.join(', ')})"
  930. end
  931. end
  932. end
  933. end
  934. private_class_method :authorize_update
  935.  
  936. def self.authorize_destroy(entity, tenant = default_tenant)
  937. unless V2::SecurityHelper.__security_bypass__
  938. if V2::Policy.defined_for?(entity)
  939. unless V2::Policy.find(entity, tenant).destroy_allowed?
  940. values = entity.keys.map { |it| "#{it}: #{entity.send(it)}" }
  941. raise V2::SBFUnauthorizedError, "Unauthorized to destroy #{entity.class.name.demodulize} (#{values.join(', ')})"
  942. end
  943. end
  944. end
  945. end
  946. private_class_method :authorize_destroy
  947. end
  948. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement