spree_core 5.5.2 → 5.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c50d137ef2f51b5fbdb5c343c360d805fe8839978386c08902ff3f29873c7e56
4
- data.tar.gz: f495c858e04f2c469323c744c5bcdff4c3d23891b85771f44082dc104922de21
3
+ metadata.gz: 2edebb88f8cab59b634a4bbbae5a24b15feab2d7673ecd521d70a1265b4e7bd0
4
+ data.tar.gz: 1fa56dfbe5ebd747212b1608490c5414246cfad12e721f49d040c97f8299ef33
5
5
  SHA512:
6
- metadata.gz: 96e1c8a7b981d143e5aab043ce6d236cd55627ddc28f1e29e573c873440e7515787e0f64f016409b54001c4313eca64b9f71f9f4641ddf3e021741c80300717d
7
- data.tar.gz: 6810ea91184c9646922ba3dba4973fd337dfc4068513ead2cc3d198bb5f316798f4f1810eb51b08b50381eecb8603c378d56c3ef5b162c38f6d0f1cb62caf519
6
+ metadata.gz: 20f0b006c40c37d8e8cbf25d97b99a73e9f3ce72d6a4903f6238aec8cad435f45c1f6517cfb54356732f7c0f69910e1013f45b7a344bc29506c888521661f46a
7
+ data.tar.gz: c72003a7f9002a1443ef60fa19f70fe6aa4e4acf9f466012101889bd964c7aaf806d9f136fe158453c751d4f11dee74f4c42b9c9e1d39ec82b3994a5293337ee
@@ -1,6 +1,10 @@
1
1
  module Spree
2
2
  module Imports
3
3
  class ProcessGroupJob < Spree::Imports::BaseJob
4
+ # Rows are loaded in slices so a large group never holds every ImportRow
5
+ # (and its raw CSV data) in memory for the whole job.
6
+ ROWS_BATCH_SIZE = 100
7
+
4
8
  def perform(import_id, row_ids)
5
9
  import = Spree::Import.find(import_id)
6
10
  Spree::Current.store = import.store
@@ -8,16 +12,23 @@ module Spree
8
12
  mappings = import.mappings.mapped.to_a
9
13
  schema_fields = import.schema_fields
10
14
  large = import.large_import?
11
- # Skip rows already completed on a prior attempt so retries don't double-process them.
12
- rows = import.rows.where(id: row_ids).pending_and_failed.order(:row_number)
13
15
 
14
- if large
15
- Spree::Events.disable do
16
- rows.each { |row| row.bulk_process!(mappings: mappings, schema_fields: schema_fields) }
17
- end
18
- else
19
- rows.each do |row|
20
- row.process!(mappings: mappings, schema_fields: schema_fields)
16
+ row_ids.each_slice(ROWS_BATCH_SIZE) do |ids|
17
+ # Skip rows already completed on a prior attempt so retries don't double-process them.
18
+ rows = import.rows.where(id: ids).pending_and_failed.order(:row_number).to_a
19
+ # Share the already-loaded import across rows: each row's processor reads
20
+ # `row.import` (store, ability, lookup cache), and without this every row
21
+ # lazily loads its own Import instance and rebuilds all of that per row.
22
+ rows.each { |row| row.association(:import).target = import }
23
+
24
+ if large
25
+ Spree::Events.disable do
26
+ rows.each { |row| row.bulk_process!(mappings: mappings, schema_fields: schema_fields) }
27
+ end
28
+ else
29
+ rows.each do |row|
30
+ row.process!(mappings: mappings, schema_fields: schema_fields)
31
+ end
21
32
  end
22
33
  end
23
34
 
@@ -2,6 +2,7 @@ module Spree
2
2
  module Imports
3
3
  class ProcessRowsJob < Spree::Imports::BaseJob
4
4
  BATCH_SIZE = 100
5
+ UNGROUPED_KEY = '__ungrouped__'.freeze
5
6
 
6
7
  def perform(import_id)
7
8
  import = Spree::Import.find(import_id)
@@ -27,20 +28,27 @@ module Spree
27
28
 
28
29
  import.rows.pending_and_failed.order(:row_number).pluck(:id, :data).each do |id, data|
29
30
  parsed = JSON.parse(data)
30
- key = parsed[file_column].to_s.strip.downcase.presence || '__ungrouped__'
31
+ key = parsed[file_column].to_s.strip.downcase.presence || UNGROUPED_KEY
31
32
  groups[key] << id
32
33
  rescue JSON::ParserError
33
- groups['__ungrouped__'] << id
34
+ groups[UNGROUPED_KEY] << id
34
35
  end
35
36
 
37
+ # Rows without a group value don't depend on each other, so they don't have
38
+ # to share a single job — split them into bounded batches. Real groups stay
39
+ # intact: their rows must run sequentially (product row before variant rows).
40
+ ungrouped = groups.delete(UNGROUPED_KEY)
41
+ batches = groups.values
42
+ ungrouped&.each_slice(BATCH_SIZE) { |row_ids| batches << row_ids }
43
+
36
44
  # Set count before enqueuing so workers can't complete prematurely
37
45
  import.update_columns(
38
- processing_groups_count: groups.size,
46
+ processing_groups_count: batches.size,
39
47
  completed_groups_count: 0,
40
48
  updated_at: Time.current
41
49
  )
42
50
 
43
- groups.each_value { |row_ids| ProcessGroupJob.perform_later(import.id, row_ids) }
51
+ batches.each { |row_ids| ProcessGroupJob.perform_later(import.id, row_ids) }
44
52
  end
45
53
 
46
54
  def dispatch_batched(import)
@@ -245,6 +245,15 @@ module Spree
245
245
  @current_ability ||= Spree.ability_class.new(user, { store: store })
246
246
  end
247
247
 
248
+ # Per-instance cache shared by row processors within a single processing job.
249
+ # Group jobs funnel every row through the same Import instance, so lookups of
250
+ # shared records (tax/shipping categories, option types, metafield definitions)
251
+ # resolve once per job instead of once per row.
252
+ # @return [Hash]
253
+ def row_lookup_cache
254
+ @row_lookup_cache ||= {}
255
+ end
256
+
248
257
  def event_serializer_class
249
258
  'Spree::Api::V3::ImportSerializer'.safe_constantize
250
259
  end
@@ -20,6 +20,15 @@ module Spree
20
20
 
21
21
  private
22
22
 
23
+ # Memoizes a shared-record lookup (including nil misses) in the import's
24
+ # per-job cache so repeated rows don't re-run the same query.
25
+ def cached_lookup(*key)
26
+ cache = import.row_lookup_cache
27
+ return cache[key] if cache.key?(key)
28
+
29
+ cache[key] = yield
30
+ end
31
+
23
32
  def build_schema_hash(row, mappings, schema_fields)
24
33
  attributes = {}
25
34
  schema_fields.each do |field|
@@ -123,12 +123,16 @@ module Spree
123
123
 
124
124
  def prepare_shipping_category
125
125
  shipping_category_name = attributes['shipping_category'].strip
126
- Spree::ShippingCategory.find_by(name: shipping_category_name)
126
+ cached_lookup(:shipping_category, shipping_category_name) do
127
+ Spree::ShippingCategory.find_by(name: shipping_category_name)
128
+ end
127
129
  end
128
130
 
129
131
  def prepare_tax_category
130
132
  tax_category_name = attributes['tax_category'].strip
131
- Spree::TaxCategory.find_by(name: tax_category_name)
133
+ cached_lookup(:tax_category, tax_category_name) do
134
+ Spree::TaxCategory.find_by(name: tax_category_name)
135
+ end
132
136
  end
133
137
 
134
138
  def prepare_option_value_variants
@@ -171,27 +175,39 @@ module Spree
171
175
  # surfaces via the DB unique index (RecordNotUnique) or the AR uniqueness
172
176
  # validator (RecordInvalid with a :taken error on the relevant attribute).
173
177
  def find_or_create_option_type!(presentation)
174
- Spree::OptionType.search_by_name(presentation).first || Spree::OptionType.create!(presentation: presentation)
175
- rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid => e
176
- raise unless uniqueness_conflict?(e, :name)
178
+ cached_lookup(:option_type, presentation) do
179
+ begin
180
+ Spree::OptionType.search_by_name(presentation).first || Spree::OptionType.create!(presentation: presentation)
181
+ rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid => e
182
+ raise unless uniqueness_conflict?(e, :name)
177
183
 
178
- Spree::OptionType.search_by_name(presentation).first!
184
+ Spree::OptionType.search_by_name(presentation).first!
185
+ end
186
+ end
179
187
  end
180
188
 
181
189
  def find_or_create_option_value!(option_type, presentation)
182
- option_type.option_values.search_by_name(presentation).first || option_type.option_values.create!(presentation: presentation)
183
- rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid => e
184
- raise unless uniqueness_conflict?(e, :name)
190
+ cached_lookup(:option_value, option_type.id, presentation) do
191
+ begin
192
+ option_type.option_values.search_by_name(presentation).first || option_type.option_values.create!(presentation: presentation)
193
+ rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid => e
194
+ raise unless uniqueness_conflict?(e, :name)
185
195
 
186
- option_type.option_values.search_by_name(presentation).first!
196
+ option_type.option_values.search_by_name(presentation).first!
197
+ end
198
+ end
187
199
  end
188
200
 
189
201
  def find_or_create_product_option_type!(option_type)
190
- Spree::ProductOptionType.find_or_create_by!(product: product, option_type: option_type)
191
- rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid => e
192
- raise unless uniqueness_conflict?(e, :product_id)
202
+ cached_lookup(:product_option_type, product.id, option_type.id) do
203
+ begin
204
+ Spree::ProductOptionType.find_or_create_by!(product: product, option_type: option_type)
205
+ rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid => e
206
+ raise unless uniqueness_conflict?(e, :product_id)
193
207
 
194
- Spree::ProductOptionType.find_by!(product: product, option_type: option_type)
208
+ Spree::ProductOptionType.find_by!(product: product, option_type: option_type)
209
+ end
210
+ end
195
211
  end
196
212
 
197
213
  # RecordNotUnique is always a uniqueness conflict; RecordInvalid only when the
@@ -245,11 +261,13 @@ module Spree
245
261
  namespace, key = product.extract_namespace_and_key(full_key)
246
262
 
247
263
  # Find or initialize metafield definition
248
- metafield_definition = Spree::MetafieldDefinition.find_by(
249
- namespace: namespace,
250
- key: key,
251
- resource_type: product.class.name
252
- )
264
+ metafield_definition = cached_lookup(:metafield_definition, namespace, key, product.class.name) do
265
+ Spree::MetafieldDefinition.find_by(
266
+ namespace: namespace,
267
+ key: key,
268
+ resource_type: product.class.name
269
+ )
270
+ end
253
271
 
254
272
  next unless metafield_definition
255
273
 
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '5.5.2'.freeze
2
+ VERSION = '5.5.3'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
@@ -256,7 +256,7 @@ module Spree
256
256
  @@stock_item_attributes = [:variant_id, :stock_location_id, :backorderable, :count_on_hand, { metadata: {} }]
257
257
 
258
258
  @@stock_location_attributes = [
259
- :name, :active, :address1, :address2, :city, :zipcode, :company,
259
+ :name, :admin_name, :active, :address1, :address2, :city, :zipcode, :company,
260
260
  :backorderable_default, :state_name, :state_id, :country_id, :phone,
261
261
  :propagate_all_variants
262
262
  ]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.5.2
4
+ version: 5.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Schofield
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2026-07-06 00:00:00.000000000 Z
13
+ date: 2026-07-10 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: i18n-tasks
@@ -1818,9 +1818,9 @@ licenses:
1818
1818
  - BSD-3-Clause
1819
1819
  metadata:
1820
1820
  bug_tracker_uri: https://github.com/spree/spree/issues
1821
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.5.2
1821
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.5.3
1822
1822
  documentation_uri: https://docs.spreecommerce.org/
1823
- source_code_uri: https://github.com/spree/spree/tree/v5.5.2
1823
+ source_code_uri: https://github.com/spree/spree/tree/v5.5.3
1824
1824
  post_install_message:
1825
1825
  rdoc_options: []
1826
1826
  require_paths: