Module: Mock::ImportConcern::ClassMethods
- Defined in:
- app/models/concerns/mock/import_concern.rb
Overview
———- Class Methods ———-
Instance Method Summary collapse
-
#apply_content_to_model(contents_hash, model, root_model) ⇒ Object
Public: Applies the contents hash provided onto the model object provided.
-
#apply_duplicate_handling_strategy(contents_hash, duplicate_strategy, root_model) ⇒ Object
Public: Determines whether we should allow, replace or skip duplicates.
-
#has_existing_instance?(contents_hash) ⇒ Boolean
Public: Takes a Hash of attributes for this model, and does a cross check if there exists an instance of this model already with the same values as those passed in.
-
#has_many_relations_hash ⇒ Object
Public: Creates a hash that maps relationship name to model class constant, for our has_many relations.
-
#has_one_relations_hash ⇒ Object
Public: Creates a hash that maps relationship name to model class constant, for our has_one relations.
-
#import(contents, duplicate_strategy, root_model = nil) ⇒ Object
Public: Performs an import operation on the mixing in model, using the model contents provided.
-
#remove_duplicates(contents_hash) ⇒ Object
Internal: Finds existing instances of this model that match the uniqueness attributes defined for this model, and destroys them.
-
#retrieve_any_existing_instance(contents_hash) ⇒ Object
Internal: Returns the first matching existing instance of this model found, otherwise nil.
-
#retrieve_existing_instances(contents_hash) ⇒ Object
Internal: Retrieves all instances of this model that match a subset of the values in the contents hash; specifically, the uniqueness values.
-
#uniqueness_attributes ⇒ Object
Public: Provides an array of those attributes whose values together, determine non-synthetic uniqueness.
-
#uniqueness_conditions(contents_hash) ⇒ Object
Public: Takes a hash of attributes and pulls out just those that match our uniqueness attributes.
Instance Method Details
#apply_content_to_model(contents_hash, model, root_model) ⇒ Object
Public: Applies the contents hash provided onto the model object provided. This includes core properties of the model, as well as related objects. For related objects, we effectively call into the import() method on the related class, building out our object graph with recursion.
contents_hash - the hash of attributes for a candidate instance of this model.
model - the model instance we are setting values into from the contents hash.
root_model - represents the root model for the model we're currently importing. We need this
for when we make re-entrant calls into the import() method for related
models to our model instance.
Returns nothing.
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 |
# File 'app/models/concerns/mock/import_concern.rb', line 284 def apply_content_to_model(contents_hash, model, root_model) has_many_relationships_hash = has_many_relations_hash() has_one_relationships_hash = has_one_relations_hash() # Any contents in hash not in a pair will generate an exception. Defend against such malformed input # content with an exception handling block. begin contents_hash.each_pair do |key, value| #log "Processing key from contents hash: '#{key}'" next if key == 'id' # Defend against these, which really shouldn't be in the source input files anyways. if model.has_attribute? key #log "Processing attribute: #{key} | value: #{value}" model.send("#{key}=", value) # Superior to instance_variable_set b/c we're dealing with ActiveRecord attributes elsif has_one_relationships_hash.keys.include? key #log "The key: #{key} is one of our has_one relations" klazz = has_one_relationships_hash[key] if klazz.respond_to? :import #log "#{klazz.to_s} responds to import." = klazz.import(value, Enums::DuplicateStrategy::ALLOW, root_model) #log "++About to add '#{related_has_one_entity.class.name}' as a has one to '#{self.name}' via the key '#{key}'" mutator_key = "#{key}=".to_sym model.send(mutator_key, ) #log "..Completed adding '#{related_has_one_entity.class.name}' as a has one to '#{self.name}' via the key '#{key}'" end elsif has_many_relationships_hash.keys.include? key #log "The key: #{key} is one of our has_many relations" klazz = has_many_relationships_hash[key] if klazz.respond_to? :import #log "#{klazz.to_s} responds to import." value.each do || model.send(key) << klazz.import(, Enums::DuplicateStrategy::ALLOW, root_model) end else log "#{klazz.to_s} does NOT respond to import." end end end rescue Exception => exception log_error "ERROR: Ran into an exception importing model #{self.to_s}. #{exception.class.to_s}: #{exception}." root_model.import_errors << "#{exception.class.to_s}: #{exception.}" end end |
#apply_duplicate_handling_strategy(contents_hash, duplicate_strategy, root_model) ⇒ Object
Public: Determines whether we should allow, replace or skip duplicates. We’ll first search for a duplicate and if one exists, we’ll then apply the duplicate strategy provided in determining how to deal with the duplicate. As we go, we’ll add entries to the root model’s import_notices array if warranted. For imports that should be skipped, we set the skip_import flag on the root model indicating that it should not be saved.
contents_hash - the hash of attributes for a candidate instance of this model.
duplicate_strategy - a constant from the Enums::DuplicateStrategy module. This indicates whether duplicates
should be allowed, skipped or replace that which they duplicate. This is only meaningful
when importing a root model.
root_model - if not specified, we'll eventually set it by walking up the parent resource chain
on the model we do instantiate.
Returns nothing.
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'app/models/concerns/mock/import_concern.rb', line 248 def apply_duplicate_handling_strategy(contents_hash, duplicate_strategy, root_model) if has_existing_instance?(contents_hash) case duplicate_strategy when Enums::DuplicateStrategy::ALLOW # Do nothing; allow it if there happened to be a duplicate. # Log this in import_notices root_model.import_notices << "Imported as an allowed duplicate." when Enums::DuplicateStrategy::REPLACE # Remove existing entries num_dupes_removed = remove_duplicates(contents_hash) # Log this in import_notices root_model.import_notices << "Imported, but had to delete #{num_dupes_removed} duplicate(s) prior." when Enums::DuplicateStrategy::SKIP # Log this in import_notices root_model.import_notices << "Skipped importing this, as it was a duplicate." # Break out of this import; nothing more for us to do. We'll set a return flag to indicate this. root_model.skip_import = true else # Do nothing. Assume our caller has already checked for valid duplicate_strategy values. end # case end end |
#has_existing_instance?(contents_hash) ⇒ Boolean
Public: Takes a Hash of attributes for this model, and does a cross check if there exists an instance of this model already with the same values as those passed in. In doing our check, we <strong>only</strong> consider those attributes present in the array returned from a call to uniqueness_attributes().
contents_hash - The hash of attributes for a candidate instance of this model.
Returns Boolean whether or not a model instance exists with the provided contents hash.
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'app/models/concerns/mock/import_concern.rb', line 122 def has_existing_instance?(contents_hash) conditions_hash = uniqueness_conditions(contents_hash) #log "#{self.to_s}: Looking for existing instance using contents_hash: #{contents_hash}" #log "#{self.to_s}: Looking for existing instance with attributes: #{conditions_hash}" # Do we have an empty set of conditions? if conditions_hash.empty? # YES: Return a no match result. This shouldn't happen, but if we allow this answer, # every record will be matched with the no-constraint conditions, and then possibly removed. log_warn "Interchange::ImportConcern has_existing_instance(): Ran into situation where conditions_hash" \ "computed was empty. This is dangerous; a wildcard match like this could cause us to delete all" \ "existing records as duplicates. Ensure uniqueness_attributes array on root models are set correctly." false else # NO: We have some conditions that we can filter on, so we'll do a search based on these # conditions and let the existence thereof be our verdict. self.where(conditions_hash).exists? end end |
#has_many_relations_hash ⇒ Object
Public: Creates a hash that maps relationship name to model class constant, for our has_many relations.
Returns Hash of the mixing in model’s ActiveRecord has_many relations with relation names as keys
and destination classes as values.
102 103 104 105 106 107 108 109 110 111 |
# File 'app/models/concerns/mock/import_concern.rb', line 102 def has_many_relations_hash relations_reflection = self.reflect_on_all_associations(:has_many) relations_hash = {} relations_reflection.each do |relation| relations_hash[relation.name.to_s] = relation.class_name.constantize end #log "has_many_relations_hash: #{relations_hash}" relations_hash end |
#has_one_relations_hash ⇒ Object
Public: Creates a hash that maps relationship name to model class constant, for our has_one relations.
Returns Hash of the mixing in model’s ActiveRecord has_one relations with relation names as keys
and destination classes as values.
86 87 88 89 90 91 92 93 94 95 |
# File 'app/models/concerns/mock/import_concern.rb', line 86 def has_one_relations_hash relations_reflection = self.reflect_on_all_associations(:has_one) relations_hash = {} relations_reflection.each do |relation| relations_hash[relation.name.to_s] = relation.class_name.constantize end #log "has_one_relations_hash: #{relations_hash}" relations_hash end |
#import(contents, duplicate_strategy, root_model = nil) ⇒ Object
Public: Performs an import operation on the mixing in model, using the model contents provided.
Note: when we ourselves call import() on related models, we effectively ignore the duplicate_strategy, as those related models will by definition, always be non-root models. We only care about duplicates when it comes to models that define top level collections (i.e. root models).
contents - the contents of a new model to be imported. This may be a proper Ruby Hash.
duplicate_strategy - a constant from the Enums::DuplicateStrategy module. This indicates whether duplicates
should be allowed, skipped or replace that which they duplicate.
This is only meaningful when importing a root model.
root_model - if not specified, we'll eventually set it by walking up the parent resource chain
on the model we do instantiate (default: parent resource).
Returns ActiveRecord::Base the model instance we just built.
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'app/models/concerns/mock/import_concern.rb', line 200 def import(contents, duplicate_strategy, root_model=nil) #log "#{self.to_s}: Asked to import data." # Convent the contents passed in to a Hash if it is currently a String of (presumably) JSON. #if contents.is_a? String # log 'We were passed a String of contents' # contents_hash = JSON.parse(contents) #else # log 'We were passed a Hash of contents' # contents_hash = contents #end contents_hash = contents model = self.new unless root_model if model.is_root_resource? root_model = model root_model.import_errors = [] # Initialize the list of errors root_model.import_notices = [] # Initialize the list of notices # Determine if we have duplicates, and if so, handle them as directed by duplicate_strategy: apply_duplicate_handling_strategy(contents_hash, duplicate_strategy, root_model) end end # Apply values from the contents hash onto the model: apply_content_to_model(contents_hash, model, root_model) # Return the new model just built; callers are responsible for saving, when appropriate. model end |
#remove_duplicates(contents_hash) ⇒ Object
Internal: Finds existing instances of this model that match the uniqueness attributes defined for this model, and destroys them. Presumably, this is to make way for a newly imported instance that would then be a dupe.
contents_hash - the hash of attributes for a candidate instance of this model.
Returns Fixnum the number of instances we found (and presumably destroyed).
175 176 177 178 179 180 181 182 183 |
# File 'app/models/concerns/mock/import_concern.rb', line 175 def remove_duplicates(contents_hash) existing_instances = retrieve_existing_instances(contents_hash) existing_instances.each do | existing_instance | log "#{self.to_s}: remove_duplicates(): Destroying instance #{existing_instance.id}" existing_instance.destroy end existing_instances.count end |
#retrieve_any_existing_instance(contents_hash) ⇒ Object
Internal: Returns the first matching existing instance of this model found, otherwise nil.
contents_hash - The hash of attributes for a candidate instance of this model.
Returns ActiveRecord::Base an instance of this model that matched a subset of the values in the contents hash
161 162 163 164 165 166 |
# File 'app/models/concerns/mock/import_concern.rb', line 161 def retrieve_any_existing_instance(contents_hash) existing = retrieve_existing_instances(contents_hash) unless existing.empty? existing.first end end |
#retrieve_existing_instances(contents_hash) ⇒ Object
Internal: Retrieves all instances of this model that match a subset of the values in the contents hash; specifically, the uniqueness values.
contents_hash - The hash of attributes for a candidate instance of this model.
Return Array of all instances of this model that matched a subset of the values in the contents hash, as
defined by the uniqueness_attributes
150 151 152 153 |
# File 'app/models/concerns/mock/import_concern.rb', line 150 def retrieve_existing_instances(contents_hash) conditions_hash = uniqueness_conditions(contents_hash) self.where(conditions_hash) end |
#uniqueness_attributes ⇒ Object
Public: Provides an array of those attributes whose values together, determine non-synthetic uniqueness. We define an empty array here. Classes that use this module mixin should override with there own list of keys as symbols. Never include the :id attribute, because that is synthetic.
Returns Array of symbols representing attributes that together, are a business compound primary key.
51 52 53 |
# File 'app/models/concerns/mock/import_concern.rb', line 51 def uniqueness_attributes [] end |
#uniqueness_conditions(contents_hash) ⇒ Object
Public: Takes a hash of attributes and pulls out just those that match our uniqueness attributes. The resultant hash can be used as conditions in a <emphasis>where</emphasis> clause to query with.
contents_hash - The hash of attributes for a candidate instance of this model.
Returns Hash containing properties as key-value pairs, which uniquely identify an instance of the mixed
in model.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'app/models/concerns/mock/import_concern.rb', line 63 def uniqueness_conditions(contents_hash) conditions_hash = {} log_info "Mock::ImportConcern: uniqueness_conditions(): uniqueness_attributes to work with is: #{uniqueness_attributes}" uniqueness_attributes.each do | unique_attribute_key | #log "Mock::ImportConcern: uniqueness_conditions(): Searching for key: #{unique_attribute_key}" # The contents hash we'll be looking through is JSON, with string keys instead of symbol keys, # and from testing, apparently that matters! Hence, we do a 'to_s' on each key before looking # through the contents_hash for it: search_value = contents_hash[unique_attribute_key.to_s] if search_value #log "Mock::ImportConcern: uniqueness_conditions(): Found value #{search_value} for key: #{unique_attribute_key}" conditions_hash[unique_attribute_key] = search_value end end conditions_hash end |