Module: Jinx::Migratable
- Defined in:
- lib/jinx/migration/migratable.rb
Overview
The Migratable mix-in adds migration support for Resource domain objects. For each migration Resource created by a Migrator, the migration process is as follows:
-
The migrator creates the Resource using the empty constructor.
-
Each input field value which maps to a Resource attribute is obtained from the migration source.
-
If the Resource class implements a method
migrate_
attribute for the migration attribute, then that migrate method is called with the input value argument. If there is a migrate method, then the attribute is set to the result of calling that method, otherwise the attribute is set to the original input value.For example, if the
Name
input field maps toParent.name
, then a customParent
migrate_name
shim method can be defined to reformat the input name. -
The Resource attribute is set to the (possibly modified) value.
-
After all input fields are processed, then #migration_valid? is called to determine whether the migrated object can be used. #migration_valid? is true by default, but a migration shim can add a validation check, migrated Resource class to return false for special cases.
For example, a custom
Parent
migration_valid?
shim method can be defined to return whether there is a non-empty input field value. -
After the migrated objects are validated, then the Migrator fills in dependency hierarchy gaps. For example, if the Resource class
Parent
owns thehousehold
dependent which in turn owns theaddress
dependent and the migration has created aParent
and anAddress
but noHousehold
, then an emptyHousehold
is created which is owned by the migratedParent
and owns the migratedAddress
. -
After all dependencies are filled in, then the independent references are set for each created Resource (including the new dependents). If a created Resource has an independent non-collection Resource reference attribute and there is a migrated instance of that attribute type, then the attribute is set to that migrated instance.
For example, if
Household
has aaddress
attribute and there is a single migratedAddress
instance, then theaddress
attribute is set to that migratedAddress
instance.If the referencing class implements a method
migrate_
attribute for the migration attribute, then that migrate method is called with the referenced instance argument. The result is used to set the attribute. Otherwise, the attribute is set to the original referenced instance.There must be a single unambiguous candidate independent instance, e.g. in the unlikely but conceivable case that two
Address
instances are migrated, then theaddress
attribute is not set. Similarly, collection attributes are not set, e.g. aAddress
protocols
attribute is not set to a migratedProtocol
instance. -
The #migrate method is called to complete the migration. As described in the method documentation, a migration shim Resource subclass can override the method for custom migration processing, e.g. to migrate the ambiguous or collection attributes mentioned above, or to fill in missing values.
Note that there is an extensive set of attribute defaults defined in the
Jinx::Resource
application domain classes. These defaults are applied in a migration database save action and need not be set in a migration shim. For example, if an acceptable default for anAddress.country
property is defined in theAddress
meta-data, then the country does not need to be set in a migration shim.
Instance Method Summary collapse
-
#extract(file) ⇒ Object
Extracts the content of this migration target to the given file.
-
#migratable__migrate_owner(row, migrated, target, proc_hash = nil) ⇒ Resource?
private
Migrates the owner as follows: * If there is exactly one migrated owner, then the owner reference is set to that owner.
-
#migratable__preferred_owner(candidates) ⇒ Resource
private
This base implementation returns nil.
- #migratable__set_nonowner_references(attr_filter, row, migrated, proc_hash = nil) ⇒ Object private
-
#migratable__target_value(pa, row, migrated, proc_hash = nil) ⇒ Resource?
private
The migrated instance of the given class, or nil if there is not exactly one such instance.
-
#migratable_independent_attributes ⇒ Object
Returns this Resource’s class Propertied#independent_attributes.
-
#migrate(row, migrated) ⇒ Object
Completes setting this Migratable domain object’s attributes from the given input row.
-
#migrate_references(row, migrated, target, proc_hash = nil) ⇒ Object
Migrates this domain object’s migratable references.
-
#migration_valid? ⇒ Boolean
Returns whether this migration target domain object is valid.
Instance Method Details
#extract(file) ⇒ Object
Extracts the content of this migration target to the given file.
This base implementation is a no-op. Subclasses can modify this method to write data to the extract.
137 138 |
# File 'lib/jinx/migration/migratable.rb', line 137 def extract(file) end |
#migratable__migrate_owner(row, migrated, target, proc_hash = nil) ⇒ Resource? (private)
Migrates the owner as follows:
-
If there is exactly one migrated owner, then the owner reference is set to that owner.
-
Otherwise, if there is more than one owner but only one owner instance of the given target class, then that target instance is that owner.
-
Otherwise, no reference is set.
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/jinx/migration/migratable.rb', line 154 def migratable__migrate_owner(row, migrated, target, proc_hash=nil) # the owner attributes=> migrated reference hash ovh = self.class.owner_attributes.to_compact_hash do |mattr| pa = self.class.property(mattr) migratable__target_value(pa, row, migrated, proc_hash) end # If there is more than one owner candidate, then select the owner # attribute which references the target. If there is more than one # such attribute, then select the preferred owner. if ovh.size > 1 then tvh = ovh.filter_on_value { |ov| target === ov }.to_hash if tvh.size == 1 then ovh = tvh else ownrs = ovh.values.uniq if ownrs.size == 1 then ovh = {ovh.keys.first => ownrs.first} else logger.debug { "The migrated dependent #{qp} has ambiguous migrated owner references #{ovh.qp}." } preferred = migratable__preferred_owner(ownrs) if preferred then logger.debug { "The preferred dependent #{qp} migrated owner reference is #{preferred.qp}." } ovh = {ovh.keys.detect { |k| ovh[k] == preferred } => preferred} end end end end if ovh.size == 1 then oattr, oref = ovh.first set_property_value(oattr, oref) logger.debug { "Set the #{qp} #{oattr} owner to the migrated #{oref.qp}." } end oref end |
#migratable__preferred_owner(candidates) ⇒ Resource (private)
This base implementation returns nil. Subclasses can override this to select a preferred owner.
193 194 195 |
# File 'lib/jinx/migration/migratable.rb', line 193 def migratable__preferred_owner(candidates) nil end |
#migratable__set_nonowner_references(attr_filter, row, migrated, proc_hash = nil) ⇒ Object (private)
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/jinx/migration/migratable.rb', line 201 def migratable__set_nonowner_references(attr_filter, row, migrated, proc_hash=nil) attr_filter.each_pair do |mattr, pa| # skip owners next if pa.owner? # the target value ref = migratable__target_value(pa, row, migrated, proc_hash) || next if pa.collection? then # the current value value = send(pa.reader) || next value << ref logger.debug { "Added the migrated #{ref.qp} to #{qp} #{mattr}." } else current = send(mattr) if current then logger.debug { "Ignoring the migrated #{ref.qp} since #{qp} #{mattr} is already set to #{current.qp}." } else set_property_value(mattr, ref) logger.debug { "Set the #{qp} #{mattr} to the migrated #{ref.qp}." } end end end end |
#migratable__target_value(pa, row, migrated, proc_hash = nil) ⇒ Resource? (private)
Returns the migrated instance of the given class, or nil if there is not exactly one such instance.
230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/jinx/migration/migratable.rb', line 230 def migratable__target_value(pa, row, migrated, proc_hash=nil) # the migrated references which are instances of the attribute type refs = migrated.select { |other| other != self and pa.type === other } # skip ambiguous references if refs.size > 1 then logger.debug { "Migrator did not set references to ambiguous targets #{refs.pp_s}." } end return unless refs.size == 1 # the single reference ref = refs.first # the shim method, if any proc = proc_hash[pa.to_sym] if proc_hash # if there is a shim method, then call it proc ? proc.call(self, ref, row) : ref end |
#migratable_independent_attributes ⇒ Object
Returns this Resource’s class Propertied#independent_attributes. Applications can override this implement to restrict the independent attributes which are migrated, e.g. to include only saved independent attributes.
127 128 129 |
# File 'lib/jinx/migration/migratable.rb', line 127 def migratable_independent_attributes self.class.independent_attributes end |
#migrate(row, migrated) ⇒ Object
Completes setting this Migratable domain object’s attributes from the given input row. This method is responsible for migrating attributes which are not mapped in the configuration. It is called after the configuration attributes for the given row are migrated and before #migrate_references.
This base implementation is a no-op. Subclasses can modify this method to complete the migration. The overridden methods should call super
to pick up the superclass migration.
81 82 |
# File 'lib/jinx/migration/migratable.rb', line 81 def migrate(row, migrated) end |
#migrate_references(row, migrated, target, proc_hash = nil) ⇒ Object
Migrates this domain object’s migratable references. This method is called by the Migrator and should not be overridden by subclasses. Subclasses tailor individual reference attribute migration by defining a migrate_
attribute method for the attribute to modify.
The migratable reference attributes consist of the non-collection saved independent attributes and the unidirectional dependent attributes which don’t already have a value. For each such migratable attribute, if there is a single instance of the attribute type in the given migrated domain objects, then the attribute is set to that migrated instance.
If the attribute is associated with a method in proc_hash, then that method is called on the migrated instance and input row. The attribute is set to the method return value. proc_hash includes an entry for each migrate_
attribute method defined by this Resource’s class.
114 115 116 117 118 119 120 |
# File 'lib/jinx/migration/migratable.rb', line 114 def migrate_references(row, migrated, target, proc_hash=nil) # migrate the owner migratable__migrate_owner(row, migrated, target, proc_hash) # migrate the remaining attributes migratable__set_nonowner_references(migratable_independent_attributes, row, migrated, proc_hash) migratable__set_nonowner_references(self.class.unidirectional_dependent_attributes, row, migrated, proc_hash) end |
#migration_valid? ⇒ Boolean
Returns whether this migration target domain object is valid. The default is true. A migration shim should override this method on the target if there are conditions which determine whether the migration should be skipped for this target object.
89 90 91 |
# File 'lib/jinx/migration/migratable.rb', line 89 def migration_valid? true end |