external_sync.rb 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class ExternalSync < ApplicationModel
  3. store :last_payload
  4. class << self
  5. def changed?(object:, current_changes:, previous_changes: {})
  6. changed = false
  7. previous_changes ||= {}
  8. current_changes.each do |attribute, value|
  9. next if !object.attributes.key?(attribute.to_s)
  10. next if object[attribute] == value
  11. next if object[attribute].present? && object[attribute] != previous_changes[attribute]
  12. begin
  13. object[attribute] = value
  14. changed ||= true
  15. rescue => e
  16. Rails.logger.error "Unable to assign attribute #{attribute} to object #{object.class.name}: #{e.inspect}"
  17. end
  18. end
  19. changed
  20. end
  21. def map(source:, mapping: {})
  22. information_source = if source.is_a?(Hash)
  23. source.deep_symbolize_keys
  24. else
  25. source.clone
  26. end
  27. result = {}
  28. mapping.each do |remote_key, local_key|
  29. local_key_sym = local_key.to_sym
  30. next if result[local_key_sym].present?
  31. value = extract(remote_key, information_source)
  32. next if value.blank?
  33. result[local_key_sym] = value
  34. end
  35. result
  36. end
  37. def migrate(object, from_id, to_id)
  38. where(
  39. object: object,
  40. o_id: from_id,
  41. ).update_all( # rubocop:disable Rails/SkipsModelValidations
  42. o_id: to_id,
  43. )
  44. end
  45. private
  46. def extract(remote_key, structure)
  47. return if !structure
  48. information_source = structure.clone
  49. result = nil
  50. information_path = remote_key.split('.')
  51. storable_classes = %w[String Integer Float Bool Array]
  52. information_path.each do |segment|
  53. segment_sym = segment.to_sym
  54. if information_source.is_a?(Hash)
  55. value = information_source[segment_sym]
  56. elsif information_source.respond_to?(segment_sym)
  57. # prevent accessing non-attributes (e.g. destroy)
  58. break if information_source.respond_to?(:attributes) && !information_source.attributes.key?(segment)
  59. value = information_source.send(segment_sym)
  60. end
  61. break if !value
  62. storable = value.class.ancestors.any? do |ancestor|
  63. storable_classes.include?(ancestor.to_s)
  64. end
  65. if storable
  66. result = value
  67. break
  68. end
  69. information_source = value
  70. end
  71. result
  72. end
  73. end
  74. end