Module: HasHelpers::SyncUtils
- Defined in:
- app/utils/has_helpers/sync_utils.rb
Class Method Summary collapse
-
.apply_mapping(resource, data, resource_key:, is_destroyed: false) ⇒ Object
Applies a custom mapping to a resource, updating it with values from the provided data.
-
.deep_transform_keys_to_snake_case(data) ⇒ Object
Transforms the keys of a nested hash or array to snake_case (recursive transformation for deep structures).
-
.dependencies_satisfied?(data, dependencies) ⇒ Boolean
Checks whether all declared sync dependencies are satisfied.
-
.handle_destruction(resource) ⇒ Object
Handles the logic when a resource is destroyed.
-
.save_with_result(resource) ⇒ Object
Attempts to save the resource and returns a success or error message.
-
.touch_dependencies!(data, dependencies) ⇒ Object
Touches declared dependency records so their updated_at reflects related sync changes.
Class Method Details
.apply_mapping(resource, data, resource_key:, is_destroyed: false) ⇒ Object
Applies a custom mapping to a resource, updating it with values from the provided data. It now supports handling resource destruction.
Arguments:
- resource (ApplicationRecord): The record to update, e.g., a User.
- data (Hash): The external data to sync with the resource.
- resource_key (Symbol): The key for the resource in the sync configuration.
- is_destroyed (Boolean): Whether the resource is being destroyed (defaults to false).
Returns:
- The updated resource or nil if no updates are made.
- When the resource is destroyed, it triggers a sync for deletion.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'app/utils/has_helpers/sync_utils.rb', line 50 def self.apply_mapping(resource, data, resource_key:, is_destroyed: false) return unless resource && (data.present? || is_destroyed) if is_destroyed # Logic for when the resource is destroyed, you may want to sync this deletion # Example: Mark the corresponding remote record as deleted return handle_destruction(resource) end # Transform the data keys to snake_case if provided data = self.deep_transform_keys_to_snake_case(data) if data # Fetch the custom mapping for the resource mapping = HasHelpers.config.sync_custom_mapping[resource_key.to_sym] # If mapping is nil, create or update resource directly with the data if mapping.nil? # Update the resource with external data directly resource.assign_attributes(data.deep_symbolize_keys) return save_with_result(resource) end # Symbolize the keys of the external data to match the mapping data = data.deep_symbolize_keys if data attrs = {} # Iterate over the mapping and apply it to the resource mapping.each do |model_key, mapper| # Add the mapped value to the attributes hash case mapper when Proc # Call the proc with the external data attrs[model_key] = mapper.call(data) when Symbol # Map the symbol directly from the external data attrs[model_key] = data[mapper] end end resource.assign_attributes(attrs) save_with_result(resource) end |
.deep_transform_keys_to_snake_case(data) ⇒ Object
Transforms the keys of a nested hash or array to snake_case (recursive transformation for deep structures)
Arguments:
- data (Hash, Array, or other): The data structure to transform.
Returns:
- The transformed data structure with all keys in snake_case.
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'app/utils/has_helpers/sync_utils.rb', line 127 def self.deep_transform_keys_to_snake_case(data) case data when Hash # if data is serialized, deserialize if data["_aj_serialized"].present? return ActiveJob::Serializers.deserialize(data) end data.transform_keys { |key| key.to_s.underscore.to_sym }.each_with_object({}) do |(key, value), result| result[key] = deep_transform_keys_to_snake_case(value) end when Array data.map { |element| deep_transform_keys_to_snake_case(element) } else data end end |
.dependencies_satisfied?(data, dependencies) ⇒ Boolean
Checks whether all declared sync dependencies are satisfied.
Arguments:
- data (Hash): The payload being synced.
- dependencies (Array<String, Symbol>): Dependency names (e.g. "user", "organization").
Returns:
- true if all dependencies exist
- false if any dependency is missing
167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'app/utils/has_helpers/sync_utils.rb', line 167 def self.dependencies_satisfied?(data, dependencies) Array(dependencies).compact.each do |dependency| next if dependency.blank? dependency_class = resolve_dependency_class(dependency) next unless dependency_class dependency_id = extract_dependency_id(data, dependency) return false unless dependency_id && dependency_class.exists?(id: dependency_id) end true end |
.handle_destruction(resource) ⇒ Object
Handles the logic when a resource is destroyed
For example, sync with an external system to delete the resource or mark it as inactive.
Arguments:
- resource (ApplicationRecord): The record to sync (which is being destroyed).
Returns:
- `true` if the sync was successful, or an error message if it wasn't.
104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'app/utils/has_helpers/sync_utils.rb', line 104 def self.handle_destruction(resource) unless resource.respond_to?(:id) && resource.id.present? = "Failed to destroy #{resource.class.name}: missing resource id" Rails.logger.error() return end return true if resource.destroy = "Failed to destroy resource #{resource.class.name} with ID #{resource.id}: #{resource.errors..join(', ')}" Rails.logger.error() end |
.save_with_result(resource) ⇒ Object
Attempts to save the resource and returns a success or error message
Arguments:
- resource (ApplicationRecord): The resource to save.
Returns:
- `true` if the resource was saved successfully, or an error message if it failed.
153 154 155 |
# File 'app/utils/has_helpers/sync_utils.rb', line 153 def self.save_with_result(resource) resource.save ? true : resource.errors..join(", ") end |
.touch_dependencies!(data, dependencies) ⇒ Object
Touches declared dependency records so their updated_at reflects related sync changes.
Arguments:
- data (Hash): The payload being synced.
- dependencies (Array<String, Symbol>): Dependency names.
Returns:
- nil (side-effect only)
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'app/utils/has_helpers/sync_utils.rb', line 191 def self.touch_dependencies!(data, dependencies) Array(dependencies).compact.each do |dependency| next if dependency.blank? dependency_class = resolve_dependency_class(dependency) next unless dependency_class dependency_id = extract_dependency_id(data, dependency) unless dependency_id Rails.logger.warn("Could not touch dependency #{dependency}: missing dependency id in payload") next end dependency_record = dependency_class.find_by(id: dependency_id) unless dependency_record Rails.logger.warn("Could not touch dependency #{dependency}: #{dependency_class.name} with id=#{dependency_id} was not found") next end dependency_record.touch # rubocop:disable Rails/SkipsModelValidations -- touch required to bump updated_at on sync dependencies without running callbacks/validations rescue => e Rails.logger.warn("Could not touch dependency #{dependency} with id=#{dependency_id}: #{e.}") end nil end |