Class: Etna::Clients::Magma::ModelSynchronizationWorkflow
- Inherits:
-
Struct
- Object
- Struct
- Etna::Clients::Magma::ModelSynchronizationWorkflow
- Defined in:
- lib/etna/clients/magma/workflows/model_synchronization_workflow.rb
Overview
Note! These workflows are not perfectly atomic, nor perfectly synchronized due to nature of the backend. These primitives are best effort locally synchronized, but cannot defend the backend or simultaneous system updates.
Instance Attribute Summary collapse
-
#model_name ⇒ Object
Returns the value of attribute model_name.
-
#plan_only ⇒ Object
Returns the value of attribute plan_only.
-
#renames ⇒ Object
Returns the value of attribute renames.
-
#source_models ⇒ Object
Returns the value of attribute source_models.
-
#target_client ⇒ Object
Returns the value of attribute target_client.
-
#target_project ⇒ Object
Returns the value of attribute target_project.
-
#update_block ⇒ Object
Returns the value of attribute update_block.
-
#use_versions ⇒ Object
Returns the value of attribute use_versions.
-
#validate ⇒ Object
Returns the value of attribute validate.
Class Method Summary collapse
- .from_api_source(source_project:, source_client:, **kwds) ⇒ Object
- .models_affected_by(action) ⇒ Object
Instance Method Summary collapse
- #copy_link_into_target(link, reciprocal) ⇒ Object
-
#ensure_model(model_name) ⇒ Object
Non cyclical, non re-entrant due to the requirement that parents cannot form a cycle.
- #ensure_model_attribute(model_name, attribute_name) ⇒ Object
-
#ensure_model_attribute_target_rename(model_name, attribute_name) ⇒ Object
Returns a tuple of the target’s attribute, post rename if necessary, if it exists, and the name of the target attribute cases here: 1.
- #ensure_model_link(model_name, link_model_name, attribute_name) ⇒ Object
-
#ensure_model_tree(model_name, seen = Set.new) ⇒ Object
Potentially cyclical, protected against re-entry via the seen cache.
- #execute_planned! ⇒ Object
- #execute_update(action) ⇒ Object
-
#plan_update(action) ⇒ Object
Applies the given action to the source models and ‘plans’ its execution.
- #planned_actions ⇒ Object
-
#prepare_parent(model_name, template, parents) ⇒ Object
Non cyclical, non re-entrant due to the requirement that parents cannot form a cycle.
- #queue_update(action) ⇒ Object
-
#target_attribute_of_source(model_name, attribute_name) ⇒ Object
Subclass and override when the source <-> target attribute mapping is not 1:1.
- #target_models ⇒ Object
-
#target_of_source(model_name) ⇒ Object
Subclass and override when the source <-> target mapping is not 1:1.
Instance Attribute Details
#model_name ⇒ Object
Returns the value of attribute model_name
9 10 11 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 9 def model_name @model_name end |
#plan_only ⇒ Object
Returns the value of attribute plan_only
9 10 11 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 9 def plan_only @plan_only end |
#renames ⇒ Object
Returns the value of attribute renames
9 10 11 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 9 def renames @renames end |
#source_models ⇒ Object
Returns the value of attribute source_models
9 10 11 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 9 def source_models @source_models end |
#target_client ⇒ Object
Returns the value of attribute target_client
9 10 11 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 9 def target_client @target_client end |
#target_project ⇒ Object
Returns the value of attribute target_project
9 10 11 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 9 def target_project @target_project end |
#update_block ⇒ Object
Returns the value of attribute update_block
9 10 11 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 9 def update_block @update_block end |
#use_versions ⇒ Object
Returns the value of attribute use_versions
9 10 11 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 9 def use_versions @use_versions end |
#validate ⇒ Object
Returns the value of attribute validate
9 10 11 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 9 def validate @validate end |
Class Method Details
.from_api_source(source_project:, source_client:, **kwds) ⇒ Object
102 103 104 105 106 107 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 102 def self.from_api_source(source_project:, source_client:, **kwds) self.new( source_models: source_client.retrieve(RetrievalRequest.new(project_name: source_project, model_name: 'all')).models, **kwds ) end |
.models_affected_by(action) ⇒ Object
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 287 def self.models_affected_by(action) case action when Etna::Clients::Magma::RenameAttributeAction [action.model_name] when Etna::Clients::Magma::UpdateAttributeAction [action.model_name] when Etna::Clients::Magma::AddAttributeAction [action.model_name] when Etna::Clients::Magma::AddModelAction [action.model_name] when Etna::Clients::Magma::AddLinkAction action.links.map(&:model_name) else [] end end |
Instance Method Details
#copy_link_into_target(link, reciprocal) ⇒ Object
45 46 47 48 49 50 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 45 def copy_link_into_target(link, reciprocal) attr = target_models.build_model(link.model_name).build_template.build_attributes.build_attribute(link.attribute_name) attr.attribute_type = link.type attr.name = attr.attribute_name = link.attribute_name attr.link_model_name = reciprocal.model_name end |
#ensure_model(model_name) ⇒ Object
Non cyclical, non re-entrant due to the requirement that parents cannot form a cycle. This method, and it’s partner prepare_parent, should never call into any re-entrant or potentially cyclical method, like ensure_model_tree.
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 247 def ensure_model(model_name) return unless (source_model = source_models.model(model_name)) target_model_name = target_of_source(model_name) return if target_models.model_keys.include?(target_model_name) template = source_model.template add_model_action = AddModelAction.new( { model_name: target_model_name, identifier: template.identifier, } ) parents = template.attributes.all.select { |a| a.attribute_type == AttributeType::PARENT } parent_model_name, parent_link_type = prepare_parent(model_name, template, parents) unless parent_model_name.nil? add_model_action.parent_model_name = parent_model_name add_model_action.parent_link_type = parent_link_type end queue_update(add_model_action) end |
#ensure_model_attribute(model_name, attribute_name) ⇒ Object
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 170 def ensure_model_attribute(model_name, attribute_name) return unless (model = source_models.model(model_name)) return unless (source_attribute = model.template.attributes.attribute(attribute_name)) target_model_name = target_of_source(model_name) target_attribute, target_attribute_name = ensure_model_attribute_target_rename(model_name, attribute_name) if target_attribute.nil? add_attribute = AddAttributeAction.new( model_name: target_model_name, attribute_name: target_attribute_name, ) Attribute.copy(source_attribute, add_attribute) queue_update(add_attribute) else # If there are is no diff, don't produce an action. target_attribute = Attribute.new(target_attribute.raw) target_attribute.set_field_defaults! source_attribute = Attribute.new(source_attribute.raw) source_attribute.set_field_defaults! source_editable = source_attribute.raw.slice(*Attribute::EDITABLE_ATTRIBUTE_ATTRIBUTES.map(&:to_s)) target_editable = target_attribute.raw.slice(*Attribute::EDITABLE_ATTRIBUTE_ATTRIBUTES.map(&:to_s)) if source_editable == target_editable return end update_attribute = UpdateAttributeAction.new( model_name: target_model_name, attribute_name: target_attribute_name, ) Attribute.copy(source_attribute, update_attribute, attributes: Attribute::EDITABLE_ATTRIBUTE_ATTRIBUTES) queue_update(update_attribute) end end |
#ensure_model_attribute_target_rename(model_name, attribute_name) ⇒ Object
Returns a tuple of the target’s attribute, post rename if necessary, if it exists, and the name of the target attribute cases here:
-
There is no rename for the attribute
a. There target attribute already exists -> [target_attribute, attribute_name]
b. The target attribute does not exist -> [nil, attribute_name]
-
There is an expected rename from the source
a. The target has neither the renamed attribute or the original attribute -> [nil, new_attribute_name]
b. The target has the renamed attribute already -> [renamed_attribute, new_attribute_name]
c. The target has the source attribute, which is not yet renamed. -> [renamed_attribute, new_attribute_name]
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 219 def ensure_model_attribute_target_rename(model_name, attribute_name) target_model_name = target_of_source(model_name) target_attribute_name = target_attribute_of_source(model_name, attribute_name) return nil unless (target_model = target_models.model(target_model_name)) target_original_attribute = target_model.template.attributes.attribute(target_attribute_name) if renames && (attribute_renames = renames[model_name]) && (new_name = attribute_renames[attribute_name]) new_name = target_attribute_of_source(model_name, new_name) unless target_model.template.attributes.include?(new_name) if target_original_attribute rename = RenameAttributeAction.new(model_name: target_model_name, attribute_name: target_attribute_name, new_attribute_name: new_name) queue_update(rename) else return [nil, new_name] end end return [target_model.template.attributes.attribute(new_name), new_name] end [target_original_attribute, target_attribute_name] end |
#ensure_model_link(model_name, link_model_name, attribute_name) ⇒ Object
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 147 def ensure_model_link(model_name, link_model_name, attribute_name) return unless (model = source_models.model(model_name)) return unless (source_attribute = model.template.attributes.attribute(attribute_name)) return unless (link_model = source_models.model(link_model_name)) link_model_attributes = link_model.template.attributes reciprocal = link_model_attributes.all.find do |attr| attr.link_model_name == model_name end target_model_name = target_of_source(model_name) target_link_model_name = target_of_source(link_model_name) target_attributes = target_models.model(target_model_name).template.attributes return if target_attributes.attribute_keys.include?(target_link_model_name) add_link = AddLinkAction.new add_link.links << AddLinkDefinition.new(model_name: target_model_name, attribute_name: target_link_model_name, type: source_attribute.attribute_type) add_link.links << AddLinkDefinition.new(model_name: target_link_model_name, attribute_name: reciprocal.attribute_name, type: reciprocal.attribute_type) queue_update(add_link) end |
#ensure_model_tree(model_name, seen = Set.new) ⇒ Object
Potentially cyclical, protected against re-entry via the seen cache. Establishes the link attributes in a given model graph.
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 121 def ensure_model_tree(model_name, seen = Set.new) return unless (source_model = source_models.model(model_name)) return if seen.include?(model_name) seen.add(model_name) ensure_model(model_name) attributes = source_model.template.attributes attributes.all.each do |attribute| # Don't copy or update parent links. Once a model has been setup with a parent someway. unless attribute.attribute_type == AttributeType::PARENT if attribute.link_model_name ensure_model(attribute.link_model_name) ensure_model_link(model_name, attribute.link_model_name, attribute.attribute_name) else ensure_model_attribute(model_name, attribute.attribute_name) end end # Even if it's a parent node, however, we still want to cascade the tree expansion to all links. unless attribute.link_model_name.nil? ensure_model_tree(attribute.link_model_name, seen) end end end |
#execute_planned! ⇒ Object
31 32 33 34 35 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 31 def execute_planned! @planned_actions.each do |action| execute_update(action) end end |
#execute_update(action) ⇒ Object
37 38 39 40 41 42 43 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 37 def execute_update(action) update = UpdateModelRequest.new(project_name: self.target_project) update_block.call(action) if update_block update.add_action(action) @target_models = nil target_client.update_model(update) end |
#plan_update(action) ⇒ Object
Applies the given action to the source models and ‘plans’ its execution.
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 93 94 95 96 97 98 99 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 53 def plan_update(action) case action when UpdateAttributeAction attribute_update = target_models.build_model(action.model_name).build_template.build_attributes.build_attribute(action.attribute_name) Attribute.copy(action, attribute_update, attributes: Attribute::EDITABLE_ATTRIBUTE_ATTRIBUTES) when AddAttributeAction new_attribute = target_models.build_model(action.model_name).build_template.build_attributes.build_attribute(action.attribute_name) Attribute.copy(action, new_attribute) when AddLinkAction first_link = action.links[0] second_link = action.links[1] copy_link_into_target(first_link, second_link) copy_link_into_target(second_link, first_link) when AddModelAction template = target_models.build_model(action.model_name).build_template template.name = action.model_name template.identifier = action.identifier template.parent = action.parent_model_name child_link = AddLinkDefinition.new(type: AttributeType::PARENT, model_name: template.name, attribute_name: template.parent) parent_link = AddLinkDefinition.new(type: action.parent_link_type, model_name: template.parent, attribute_name: template.name) copy_link_into_target(child_link, parent_link) copy_link_into_target(parent_link, child_link) ['created_at', 'updated_at'].each do |time_attr_name| template.build_attributes.build_attribute(time_attr_name).tap do |attr| attr.attribute_type = Etna::Clients::Magma::AttributeType::DATE_TIME attr.attribute_name = time_attr_name end end if action.parent_link_type != Etna::Clients::Magma::AttributeType::TABLE template.build_attributes.build_attribute(template.identifier).tap do |attr| attr.attribute_name = template.identifier attr.attribute_type = Etna::Clients::Magma::AttributeType::IDENTIFIER end end when RenameAttributeAction attributes = target_models.model(action.model_name).template.attributes attributes.raw[action.new_attribute_name] = attributes.raw.delete(action.attribute_name) else raise "Unexpected plan_action #{action}" end planned_actions << action end |
#planned_actions ⇒ Object
19 20 21 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 19 def planned_actions @planned_actions ||= [] end |
#prepare_parent(model_name, template, parents) ⇒ Object
Non cyclical, non re-entrant due to the requirement that parents cannot form a cycle. This method, and it’s partner and ensure_model, should never call into any re-entrant or potentially cyclical method, like ensure_model_tree.
274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 274 def prepare_parent(model_name, template, parents) return [nil, nil] if parents.empty? return [nil, nil] unless (parent_model = source_models.model(template.parent)) return [nil, nil] unless (child_attribute = parent_model.template.attributes.attribute(model_name)) ensure_model(template.parent) [ target_of_source(template.parent), child_attribute.attribute_type ] end |
#queue_update(action) ⇒ Object
23 24 25 26 27 28 29 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 23 def queue_update(action) if plan_only plan_update(action) else execute_update(action) end end |
#target_attribute_of_source(model_name, attribute_name) ⇒ Object
Subclass and override when the source <-> target attribute mapping is not 1:1.
115 116 117 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 115 def target_attribute_of_source(model_name, attribute_name) attribute_name end |
#target_models ⇒ Object
13 14 15 16 17 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 13 def target_models @target_models ||= begin target_client.retrieve(RetrievalRequest.new(project_name: self.target_project, model_name: 'all')).models end end |
#target_of_source(model_name) ⇒ Object
Subclass and override when the source <-> target mapping is not 1:1.
110 111 112 |
# File 'lib/etna/clients/magma/workflows/model_synchronization_workflow.rb', line 110 def target_of_source(model_name) model_name end |