Class: SourcedAttributes::Source
- Inherits:
-
Object
- Object
- SourcedAttributes::Source
- Includes:
- DSL
- Defined in:
- lib/sourced_attributes/source.rb
Constant Summary collapse
- DEFAULT_OPTIONS =
The default values for source-agnostic options that can be overridden by including new values in the Hash argument to
sources_attributes_from. { save: true, create_new: true, batch_size: nil }
- @@subclasses =
A map of all aliases for concrete Source objects for use by the factory methods.
{}
Class Method Summary collapse
-
.create(name, opts, klass) ⇒ Object
A factory for creating instances of Source subclasses based on the parameterized name passed in and the list of registered subclasses.
-
.register_source(name) ⇒ Object
Subclasses of Source should register aliases with the factory through this method.
Instance Method Summary collapse
-
#apply ⇒ Object
Apply the current set of source data to the records it affects.
-
#apply_associations_to(pk, record) ⇒ Object
Apply the current set of source data to the associations for the given primary key.
-
#apply_attributes_to(pk, record) ⇒ Object
Apply the current set of source data to the attributes for the given primary key.
-
#create_new_records ⇒ Object
Create new instances of @klass for every key that does not already have an instance associated with it.
-
#ensure_indexed_source_data ⇒ Object
Create a Hash from the @source_data array, keyed by @primary_key.
-
#initialize(klass, opts = {}) ⇒ Source
constructor
A new instance of Source.
-
#mapped_attributes_for(pk) ⇒ Object
Apply the attribute map to the source data for the given record.
-
#preload_association_data ⇒ Object
Load into memory all key-value pairs needed to fully associate all records that a source handles.
-
#refresh ⇒ Object
Talk to the data source to refresh the contents of @source_data.
-
#refresh_affected_records ⇒ Object
Fill @affected_records with all records affected by the current set of source data, creating new records for any keys which do not yet exist.
-
#resolve_attribute_for_datum(attribute, datum) ⇒ Object
Given an attribute name and a primary key, resolve the value to be given to that attribute using the configuration supplied through the DSL.
-
#update_record(pk, record) ⇒ Object
Perform all of the operations related to updating a sourced record.
Methods included from DSL
#aliased_attribute, #association, #attributes, #complex_attribute, #conditional_attribute, #configure, #primary_key
Constructor Details
#initialize(klass, opts = {}) ⇒ Source
Returns a new instance of Source.
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/sourced_attributes/source.rb', line 31 def initialize klass, opts={} # The model this source is working on. @klass = klass # A configuration hash for source-agnostic options, passed in as a Hash # from the arguments given to the `sources_attributes_from` helper. = DEFAULT_OPTIONS.merge(opts) # A generic configuration hash for source-specific options, managed # through the `configure` helper. @config = {} # The primary key that this source will use to find records to update. # `local` is the alias of the primary key in the locally, while `source` # is the alias of the primary key in the source data. @primary_key = { local: :id, source: :id } # A mapping of attributes on the model to field names from the source. @attribute_map = {} # A mapping of attributes which require special preparation to Procs # which can perform that preparation, provided by the configuration. @complex_attributes = {} # A mapping of attributes which are only to be updated when a given # condition is met to Procs which represent that condition. @conditional_attributes = {} # A list of associations that this source will update. Each entry is a # hash, containing the keys :name, :primary_key, :preload. @associations = [] # The most recent set of data from the source, formatted as a Hash using # the :primary_key values as keys. Updated by `refresh` and used by # `apply` to update records. @source_data = [] # The last-retrieved set of data from the source, formatted the same way # as @source_data. @previous_data = [] # The records that that the source data will affect. Updated by # `refresh_affected_records`. @affected_records = {} end |
Class Method Details
.create(name, opts, klass) ⇒ Object
A factory for creating instances of Source subclasses based on the parameterized name passed in and the list of registered subclasses.
12 13 14 |
# File 'lib/sourced_attributes/source.rb', line 12 def create name, opts, klass (@@subclasses[name] or Source).new klass, opts end |
.register_source(name) ⇒ Object
Subclasses of Source should register aliases with the factory through this method.
18 19 20 |
# File 'lib/sourced_attributes/source.rb', line 18 def register_source name @@subclasses[name] = self end |
Instance Method Details
#apply ⇒ Object
Apply the current set of source data to the records it affects
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/sourced_attributes/source.rb', line 175 def apply # Make sure the source data is up-to-date refresh # Make sure the source data is indexed by the primary key ensure_indexed_source_data # Make sure that all of the affected records are loaded refresh_affected_records # Preload all key-value pairs needed to fully associate this record. preload_association_data # Wrap all of the updates in a single transaction @klass.transaction do if [:batch_size] @affected_records.each_slice([:batch_size]) do |batch| @klass.import batch.map{ |pk, record| update_record(pk, record); record } end else @affected_records.each do |pk, record| update_record pk, record end end end end |
#apply_associations_to(pk, record) ⇒ Object
Apply the current set of source data to the associations for the given primary key.
152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/sourced_attributes/source.rb', line 152 def apply_associations_to pk, record @associations.each do |config| # Get the model that this association references reflection = @klass.reflect_on_association(config[:name]) # The associated records are already loaded, but need to be plucked out # of their containing hash associated_records = config[:data][@source_data[pk][config[:source_key]]] # Apply the updated association to the record record.assign_attributes(config[:name] => associated_records) end end |
#apply_attributes_to(pk, record) ⇒ Object
Apply the current set of source data to the attributes for the given primary key.
144 145 146 147 148 |
# File 'lib/sourced_attributes/source.rb', line 144 def apply_attributes_to pk, record # Map the attributes from the source data to their local counterparts # and apply it to the record record.assign_attributes(mapped_attributes_for(pk)) end |
#create_new_records ⇒ Object
Create new instances of @klass for every key that does not already have an instance associated with it.
93 94 95 96 97 98 |
# File 'lib/sourced_attributes/source.rb', line 93 def create_new_records @source_data.keys.each do |pk| # TODO: Add an option for creating/not creating new records @affected_records[pk] ||= @klass.new(@primary_key[:local] => pk) end end |
#ensure_indexed_source_data ⇒ Object
Create a Hash from the @source_data array, keyed by @primary_key. If nothing.
83 84 85 86 87 88 89 |
# File 'lib/sourced_attributes/source.rb', line 83 def ensure_indexed_source_data return if @source_data.is_a?(Hash) @source_data = @source_data.inject({}) do |hash, datum| hash[datum[@primary_key[:source]]] = datum hash end end |
#mapped_attributes_for(pk) ⇒ Object
Apply the attribute map to the source data for the given record
114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/sourced_attributes/source.rb', line 114 def mapped_attributes_for pk source = @source_data[pk] @attribute_map.inject({}) do |hash, (attribute,_)| # Only apply conditional attributes if they're condition is met if @conditional_attributes.has_key?(attribute) next hash unless @conditional_attributes[attribute].call(source) end hash[attribute] = resolve_attribute_for_datum(attribute, source) hash end end |
#preload_association_data ⇒ Object
Load into memory all key-value pairs needed to fully associate all records that a source handles.
128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/sourced_attributes/source.rb', line 128 def preload_association_data @associations.each do |assoc| # Get the model that this association references. reflection = @klass.reflect_on_association(assoc[:name]) # Determine which key-value pairs should be preloaded. values = @source_data.map{ |key, datum| datum[assoc[:source_key]] } # Load the data, keyed by the local primary key assoc[:data] = reflection.klass \ .where(assoc[:primary_key] => values) \ .select(assoc[:primary_key], reflection.klass.primary_key) \ .index_by(&assoc[:primary_key]) end end |
#refresh ⇒ Object
Talk to the data source to refresh the contents of @source_data.
204 |
# File 'lib/sourced_attributes/source.rb', line 204 def refresh; raise :subclass_responsiblity; end |
#refresh_affected_records ⇒ Object
Fill @affected_records with all records affected by the current set of source data, creating new records for any keys which do not yet exist.
102 103 104 105 106 107 108 109 110 111 |
# File 'lib/sourced_attributes/source.rb', line 102 def refresh_affected_records # Ensure that the source data is indexed by primary key... ensure_indexed_source_data # ...so that it can be skimmed to find existing records. @affected_records = @klass \ .where(@primary_key[:local] => @source_data.keys) \ .index_by(&@primary_key[:local]) # Then create new objects for the remaining data create_new_records if [:create_new] end |
#resolve_attribute_for_datum(attribute, datum) ⇒ Object
Given an attribute name and a primary key, resolve the value to be given to that attribute using the configuration supplied through the DSL.
69 70 71 72 73 74 75 76 77 78 |
# File 'lib/sourced_attributes/source.rb', line 69 def resolve_attribute_for_datum attribute, datum # The alias of this attribute in the source data source_name = @attribute_map[attribute] # Complex Attributes are evaluated with the datum as a parameter if @complex_attributes.has_key?(attribute) @complex_attributes[attribute].call(datum) else datum[source_name] end end |
#update_record(pk, record) ⇒ Object
Perform all of the operations related to updating a sourced record
165 166 167 168 169 170 171 172 |
# File 'lib/sourced_attributes/source.rb', line 165 def update_record pk, record # Update attributes apply_attributes_to(pk, record) # Update associations apply_associations_to(pk, record) # Save the record if it should be record.save if ([:save] && ![:batch_size]) end |