Class: Spree::Import

Inherits:
Object
  • Object
show all
Includes:
NumberIdentifier
Defined in:
app/models/spree/import.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.available_modelsArray<Class>

Returns the available models for the import

Returns:

  • (Array<Class>)


339
340
341
# File 'app/models/spree/import.rb', line 339

def available_models
  available_types.map(&:model_class)
end

.available_typesArray<Class>

Returns the available types for the import

Returns:

  • (Array<Class>)


333
334
335
# File 'app/models/spree/import.rb', line 333

def available_types
  Spree.import_types
end

.model_classObject

eg. Spree::Imports::Orders => Spree::Order

Raises:

  • (NameError)


364
365
366
367
368
369
370
371
372
# File 'app/models/spree/import.rb', line 364

def model_class
  return Spree.user_class if to_s == 'Spree::Imports::Customers'

  klass = "Spree::#{to_s.demodulize.singularize}".safe_constantize

  raise NameError, "Missing model class for #{self}" unless klass

  klass
end

.required_scopeSymbol?

Admin API scope family gating this import type — an import is a bulk write, so an API key needs write_<required_scope> to create and manage it. Derived from the class name (Spree::Imports::Customers => :customers); override in subclasses gated by a different scope. Returns nil on the base class, so unmapped types are only accessible to write_all keys.

Returns:

  • (Symbol, nil)


357
358
359
360
361
# File 'app/models/spree/import.rb', line 357

def required_scope
  return nil if self == Spree::Import

  to_s.demodulize.underscore.to_sym
end

.type_for_model(model) ⇒ Class

Returns the type for the model

Returns:

  • (Class)


345
346
347
# File 'app/models/spree/import.rb', line 345

def type_for_model(model)
  available_types.find { |type| type.model_class.to_s == model.to_s }
end

Instance Method Details

#attachment_file_contentString

Returns the content of the attachment file

Returns:

  • (String)


275
276
277
# File 'app/models/spree/import.rb', line 275

def attachment_file_content
  @attachment_file_content ||= attachment.attached? ? attachment.blob.download&.force_encoding('UTF-8') : nil
end

#can_be_deleted?Boolean

Imports are deletable only while no processing jobs can be in flight — CreateRowsJob/ProcessGroupJob re-load the record mid-run.

Returns:

  • (Boolean)


132
133
134
# File 'app/models/spree/import.rb', line 132

def can_be_deleted?
  %w[pending mapping completed failed].include?(status)
end

#complete?Boolean

Returns true if the import is complete

Returns:

  • (Boolean)


125
126
127
# File 'app/models/spree/import.rb', line 125

def complete?
  status == 'completed'
end

#create_mappingsObject

Creates mappings from the schema fields TODO: get mappings from the previous import if it exists, so user won't have to map the same columns again



281
282
283
284
285
286
287
# File 'app/models/spree/import.rb', line 281

def create_mappings
  schema_fields.each do |schema_field|
    mapping = mappings.find_or_create_by!(schema_field: schema_field[:name])
    mapping.try_to_auto_assign_file_column(csv_headers)
    mapping.save!
  end
end

#create_rows_asyncvoid

This method returns an undefined value.

Creates rows asynchronously



291
292
293
# File 'app/models/spree/import.rb', line 291

def create_rows_async
  Spree::Imports::CreateRowsJob.set(wait: 2.seconds).perform_later(id)
end

#csv_headersArray<String>

Returns the headers of the csv file

Returns:

  • (Array<String>)


264
265
266
267
268
269
270
271
# File 'app/models/spree/import.rb', line 264

def csv_headers
  return [] if attachment_file_content.blank?

  @csv_headers ||= ::CSV.parse_line(
    attachment_file_content,
    col_sep: preferred_delimiter
  )
end

#current_abilitySpree::Ability

Returns the current ability for the import

Returns:



313
314
315
# File 'app/models/spree/import.rb', line 313

def current_ability
  @current_ability ||= Spree.ability_class.new(user, { store: store })
end

#display_nameString

Returns the display name for the import

Returns:

  • (String)


250
251
252
# File 'app/models/spree/import.rb', line 250

def display_name
  "#{Spree.t(type.demodulize.pluralize.downcase)} #{number}"
end

#event_serializer_classObject



326
327
328
# File 'app/models/spree/import.rb', line 326

def event_serializer_class
  'Spree::Api::V3::ImportSerializer'.safe_constantize
end

#group_columnString?

Returns the schema field name used to group rows for parallel processing. Rows sharing the same value in this field are processed together in one job. Returns nil for imports where rows are independent (default — batched in chunks).

Returns:

  • (String, nil)


107
108
109
# File 'app/models/spree/import.rb', line 107

def group_column
  nil
end

#import_schemaSpree::ImportSchema

Returns the import schema for the import

Returns:



194
195
196
# File 'app/models/spree/import.rb', line 194

def import_schema
  "Spree::ImportSchemas::#{type.demodulize}".safe_constantize.new
end

#large_import?Boolean

Returns true if the import has more rows than the large import threshold. Large imports skip per-row UI broadcasts and use bulk processing.

Returns:

  • (Boolean)


99
100
101
# File 'app/models/spree/import.rb', line 99

def large_import?
  rows_count >= Spree::Config[:large_import_threshold]
end

#mapped_fieldsArray<String>

Returns the mapped fields for the import schema

Returns:

  • (Array<String>)


238
239
240
# File 'app/models/spree/import.rb', line 238

def mapped_fields
  @mapped_fields ||= mappings.mapped.where(schema_field: required_fields)
end

#mapping?Boolean

Returns true if the import is in mapping state

Returns:

  • (Boolean)


113
114
115
# File 'app/models/spree/import.rb', line 113

def mapping?
  status == 'mapping'
end

#mapping_done?Boolean

Returns true if the mapping is done

Returns:

  • (Boolean)


244
245
246
# File 'app/models/spree/import.rb', line 244

def mapping_done?
  mapped_fields.count == required_fields.count
end

#model_classClass

Returns the model class for the import

Returns:

  • (Class)


184
185
186
187
188
189
190
# File 'app/models/spree/import.rb', line 184

def model_class
  if type == 'Spree::Imports::Customers'
    Spree.user_class
  else
    "Spree::#{type.demodulize.singularize}".safe_constantize
  end
end

#process_rows_asyncvoid

This method returns an undefined value.

Processes rows asynchronously



297
298
299
# File 'app/models/spree/import.rb', line 297

def process_rows_async
  Spree::Imports::ProcessRowsJob.perform_later(id)
end

#processing?Boolean

Returns true if the import is processing or completed mapping

Returns:

  • (Boolean)


119
120
121
# File 'app/models/spree/import.rb', line 119

def processing?
  ['processing', 'completed_mapping'].include?(status)
end

#publish_import_completed_eventObject



258
259
260
# File 'app/models/spree/import.rb', line 258

def publish_import_completed_event
  publish_event('import.completed')
end

#required_fieldsArray<String>

Returns the required fields for the import schema

Returns:

  • (Array<String>)


232
233
234
# File 'app/models/spree/import.rb', line 232

def required_fields
  import_schema.required_fields
end

#results_page_urlString?

URL of the wizard/results view for this import — the caller-provided results_url with the wizard's ?import= param appended (a no-op for the legacy admin's show URL, whose path already identifies the import). Nil when the preference is absent; the mailer then renders no link.

Returns:

  • (String, nil)


158
159
160
161
162
163
164
165
166
167
168
# File 'app/models/spree/import.rb', line 158

def results_page_url
  return if preferred_results_url.blank?

  uri = URI.parse(preferred_results_url)
  params = URI.decode_www_form(uri.query.to_s)
  params << ['import', prefixed_id]
  uri.query = URI.encode_www_form(params)
  uri.to_s
rescue URI::InvalidURIError
  nil
end

#results_urlObject

Public API name for the results_url preference (read/write symmetry). String preferences round-trip nil as "" — normalize blank to nil.



145
146
147
# File 'app/models/spree/import.rb', line 145

def results_url
  preferred_results_url.presence
end

#results_url=(value) ⇒ Object



149
150
151
# File 'app/models/spree/import.rb', line 149

def results_url=(value)
  self.preferred_results_url = value
end

#row_lookup_cacheHash

Per-instance cache shared by row processors within a single processing job. Group jobs funnel every row through the same Import instance, so lookups of shared records (tax/shipping categories, option types, metafield definitions) resolve once per job instead of once per row.

Returns:

  • (Hash)


322
323
324
# File 'app/models/spree/import.rb', line 322

def row_lookup_cache
  @row_lookup_cache ||= {}
end

#row_processor_classClass

Returns the row processor class for the import

Returns:

  • (Class)


200
201
202
# File 'app/models/spree/import.rb', line 200

def row_processor_class
  "Spree::ImportRowProcessors::#{type.demodulize.singularize}".safe_constantize
end

#rows_status_countsHash{String => Integer}

Per-status row counts for API status payloads, memoized so one request issues a single grouped COUNT instead of one per status.

Returns:

  • (Hash{String => Integer})


139
140
141
# File 'app/models/spree/import.rb', line 139

def rows_status_counts
  @rows_status_counts ||= rows.group(:status).count
end

#sample_rowHash

First data row as { header => value }, reading a single line of the attached CSV — used by the mapping UI to show example values.

Returns:

  • (Hash)


173
174
175
176
177
178
179
180
# File 'app/models/spree/import.rb', line 173

def sample_row
  return {} if attachment_file_content.blank?

  row = ::CSV.foreach(StringIO.new(attachment_file_content), headers: true, col_sep: preferred_delimiter).first
  row&.to_h || {}
rescue ::CSV::MalformedCSVError, EncodingError
  {}
end

#schema_fieldsArray<Hash>

Returns the fields for the import schema If model supports metafields, it will include the metafield definitions for this model

Returns:

  • (Array<Hash>)


207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'app/models/spree/import.rb', line 207

def schema_fields
  base_fields = import_schema.fields

  # Dynamically add metafield definitions if the model supports metafields
  if model_class_supports_metafields?
    metafield_fields = metafield_definitions_for_model.map do |definition|
      {
        name: definition.csv_header_name,
        label: definition.name
      }
    end
    base_fields + metafield_fields
  else
    base_fields
  end
end

#storeSpree::Store

Returns the store for the import

Returns:



303
304
305
306
307
308
309
# File 'app/models/spree/import.rb', line 303

def store
  if owner.is_a?(Spree::Store)
    owner
  else
    owner.respond_to?(:store) ? owner.store : Spree::Store.default
  end
end

#touch_storeObject



254
255
256
# File 'app/models/spree/import.rb', line 254

def touch_store
  store.touch
end

#unmapped_file_columnsArray<String>

Returns the file columns that are not mapped

Returns:

  • (Array<String>)


226
227
228
# File 'app/models/spree/import.rb', line 226

def unmapped_file_columns
  csv_headers.reject { |header| mappings.mapped.exists?(file_column: header) }
end