spree_core 5.6.0.rc5 → 5.6.0.rc6

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: 1b3d64f54fca96fb946e3ee9b49d80648677ce7f5c36d0d6bb10769bf8c98df9
4
- data.tar.gz: 82a353e9cad247a2be8f909d569ab286f323a63fe04b1ef5676d66c72001fdf7
3
+ metadata.gz: 2118af5f16565f54bbb6e6bc66ed0db0bd44e8ad250b226dca1f2ae6dfd50dc9
4
+ data.tar.gz: 85cf95c960549e5557cb62c51d3b22516ce91d19b2532001c613fe2da036229c
5
5
  SHA512:
6
- metadata.gz: ee39b451ea0e40f7ea1b0811ae858b7a58580952c943b3356a642acf3b04dc5a461c2ae8864db916a14f1dcbe4b46428b924e852fdb21e271d071e3917388561
7
- data.tar.gz: a90ca190815ef4e7ba9fec0cfe9bce1902b4c00eb35c1cd27c64e2edbe20980d69353826c612b4534cea402723a0cb5db8e9f2da4aea5023d5e51e05764c2175
6
+ metadata.gz: 629b648d7bcc536638305dc942cc5fe8a08ddf9bcba35a7a4e132e84c72f486c106af32711d887b3b67097b35cac9b8664b3f8667630a6d90f56b3e1f815c974
7
+ data.tar.gz: 1077ccfe23cf6e5c98bd565a559551a344dec7ce20d0e3fe83b06dc9a8be02e74c5107d8edc48d2c0397c24248197d9097fdbb77896ed07b8f7bcd5da9a0d568
@@ -10,6 +10,19 @@ module Spree
10
10
  # taxon creation races).
11
11
  class BaseJob < Spree::BaseJob
12
12
  queue_as Spree.queues.imports
13
+
14
+ private
15
+
16
+ def with_store_content_locale(store, &block)
17
+ locale = store&.default_locale
18
+ return yield if locale.blank?
19
+
20
+ previous_content_locale = Spree::Current.content_locale
21
+ Spree::Current.content_locale = locale
22
+ I18n.with_locale(locale, &block)
23
+ ensure
24
+ Spree::Current.content_locale = previous_content_locale unless locale.blank?
25
+ end
13
26
  end
14
27
  end
15
28
  end
@@ -9,9 +9,11 @@ module Spree
9
9
  def perform(product_id, store_id, taxon_pretty_names)
10
10
  product = Spree::Product.find(product_id)
11
11
  store = Spree::Store.find(store_id)
12
- taxons = taxon_pretty_names.filter_map { |taxon_pretty_name| find_or_create_taxon(store, taxon_pretty_name) }
13
12
 
14
- product.taxons = taxons
13
+ with_store_content_locale(store) do
14
+ taxons = taxon_pretty_names.filter_map { |taxon_pretty_name| find_or_create_taxon(store, taxon_pretty_name) }
15
+ product.taxons = taxons
16
+ end
15
17
  end
16
18
 
17
19
  private
@@ -25,43 +25,45 @@ module Spree
25
25
  import = Spree::Import.find(import_id)
26
26
  Spree::Current.store = import.store
27
27
 
28
- mappings = import.mappings.mapped.to_a
29
- schema_fields = import.schema_fields
30
- large = import.large_import?
31
- grouped = import.group_column.present? && mappings.any? { |m| m.schema_field == import.group_column }
32
- started_at = Time.current
33
- processed_rows = []
28
+ with_store_content_locale(import.store) do
29
+ mappings = import.mappings.mapped.to_a
30
+ schema_fields = import.schema_fields
31
+ large = import.large_import?
32
+ grouped = import.group_column.present? && mappings.any? { |m| m.schema_field == import.group_column }
33
+ started_at = Time.current
34
+ processed_rows = []
34
35
 
35
- row_ids.each_slice(ROWS_BATCH_SIZE) do |ids|
36
- # Skip rows already completed on a prior attempt so retries don't double-process them.
37
- rows = import.rows.where(id: ids).pending_and_failed.order(:row_number).to_a
38
- # Share the already-loaded import across rows: each row's processor reads
39
- # `row.import` (store, ability, lookup cache), and without this every row
40
- # lazily loads its own Import instance and rebuilds all of that per row.
41
- rows.each { |row| row.association(:import).target = import }
36
+ row_ids.each_slice(ROWS_BATCH_SIZE) do |ids|
37
+ # Skip rows already completed on a prior attempt so retries don't double-process them.
38
+ rows = import.rows.where(id: ids).pending_and_failed.order(:row_number).to_a
39
+ # Share the already-loaded import across rows: each row's processor reads
40
+ # `row.import` (store, ability, lookup cache), and without this every row
41
+ # lazily loads its own Import instance and rebuilds all of that per row.
42
+ rows.each { |row| row.association(:import).target = import }
42
43
 
43
- if large
44
- Spree::Events.disable do
45
- rows.each { |row| row.bulk_process!(mappings: mappings, schema_fields: schema_fields) }
46
- end
47
- elsif grouped
48
- # A group is one product plus its variants: per-record lifecycle
49
- # events (variant.created, price.created, product.updated per
50
- # touch) are noise to subscribers — one product event is published
51
- # for the whole group below. import_row.* events still flow.
52
- Spree::Events.disable_lifecycle do
53
- rows.each { |row| row.process!(mappings: mappings, schema_fields: schema_fields) }
54
- end
55
- else
56
- rows.each do |row|
57
- row.process!(mappings: mappings, schema_fields: schema_fields)
44
+ if large
45
+ Spree::Events.disable do
46
+ rows.each { |row| row.bulk_process!(mappings: mappings, schema_fields: schema_fields) }
47
+ end
48
+ elsif grouped
49
+ # A group is one product plus its variants: per-record lifecycle
50
+ # events (variant.created, price.created, product.updated per
51
+ # touch) are noise to subscribers — one product event is published
52
+ # for the whole group below. import_row.* events still flow.
53
+ Spree::Events.disable_lifecycle do
54
+ rows.each { |row| row.process!(mappings: mappings, schema_fields: schema_fields) }
55
+ end
56
+ else
57
+ rows.each do |row|
58
+ row.process!(mappings: mappings, schema_fields: schema_fields)
59
+ end
58
60
  end
61
+ processed_rows.concat(rows)
59
62
  end
60
- processed_rows.concat(rows)
61
- end
62
63
 
63
- publish_group_events(processed_rows, started_at) if grouped
64
- check_import_completion(import, large)
64
+ publish_group_events(processed_rows, started_at) if grouped
65
+ check_import_completion(import, large)
66
+ end
65
67
  end
66
68
 
67
69
  private
@@ -17,7 +17,7 @@ module Spree
17
17
  extend ActiveSupport::Concern
18
18
 
19
19
  # Payload keys under `data` whose values are live credentials.
20
- SENSITIVE_PAYLOAD_KEYS = %w[reset_token unsubscribe_token].freeze
20
+ SENSITIVE_PAYLOAD_KEYS = %w[reset_token unsubscribe_token verification_token].freeze
21
21
 
22
22
  REDACTION_PLACEHOLDER = '[REDACTED]'
23
23
 
@@ -46,9 +46,15 @@ module Spree
46
46
 
47
47
  # This method should be called when a download is initiated.
48
48
  # It returns +true+ or +false+ depending on whether the authorization is granted.
49
+ #
50
+ # The access-limit check and the counter increment run inside a row lock so
51
+ # concurrent requests sharing the same token cannot each pass the cap before
52
+ # any of them increments the counter (TOCTOU race).
49
53
  def authorize!
50
54
  ActiveRecord::Base.connected_to(role: :writing) do
51
- authorizable? && increment!(:access_counter, touch: true)
55
+ with_lock do
56
+ authorizable? && increment!(:access_counter, touch: true)
57
+ end
52
58
  end
53
59
  end
54
60
 
@@ -62,7 +62,7 @@ module Spree
62
62
 
63
63
  def generate_code
64
64
  loop do
65
- code = "#{prefix.downcase}#{SecureRandom.hex(3).downcase}"
65
+ code = "#{prefix.downcase}#{SecureRandom.hex(8).downcase}"
66
66
  break code unless Spree::GiftCard.exists?(code: code) || @gift_cards_to_insert.detect { |gc| gc[:code] == code }
67
67
  end
68
68
  end
@@ -24,6 +24,7 @@ module Spree
24
24
 
25
25
  acts_as_paranoid
26
26
  acts_as_taggable_on :tags, :labels
27
+ acts_as_taggable_tenant :store_id
27
28
  normalizes :name, with: ->(value) { value&.to_s&.squish&.presence }
28
29
 
29
30
  include Spree::ProductScopes
@@ -43,29 +43,29 @@ unless price_list.price_rules.any? { |rule| rule.is_a?(Spree::PriceRules::Custom
43
43
  rule.save!
44
44
  end
45
45
 
46
- if price_list.prices.none?
47
- currency = store.default_currency
48
- now = Time.current
49
- rows = Spree::Variant.joins(:product)
50
- .where(spree_products: { store_id: store.id })
51
- .joins(:prices)
52
- .where(spree_prices: { currency: currency, price_list_id: nil })
53
- .pluck(:id, Spree::Price.arel_table[:amount])
54
- .to_h # one row per variant — last base price wins
55
- .filter_map do |variant_id, amount|
46
+ # Same paths the admin UI uses: add_products materializes a blank row per
47
+ # variant × supported currency (skipping rows that already exist), then trade
48
+ # prices (40% off that currency's own retail price) fill in wherever a base
49
+ # price exists and the row is still blank. Both steps are idempotent, so
50
+ # re-running sample data converges partial state — new products, newly
51
+ # supported currencies, or a previously interrupted run — without clobbering
52
+ # hand-edited amounts.
53
+ price_list.add_products(store.products.ids)
54
+
55
+ blank_prices = price_list.prices.where(amount: nil).pluck(:id, :variant_id, :currency)
56
+ if blank_prices.any?
57
+ base_amounts = Spree::Price.where(price_list_id: nil, variant_id: blank_prices.map { |_, variant_id, _| variant_id }.uniq)
58
+ .pluck(:variant_id, :currency, :amount)
59
+ .each_with_object({}) { |(variant_id, currency, amount), memo| memo[[variant_id, currency]] = amount }
60
+
61
+ rows = blank_prices.filter_map do |id, variant_id, currency|
62
+ amount = base_amounts[[variant_id, currency]]
56
63
  next if amount.blank?
57
64
 
58
- {
59
- variant_id: variant_id,
60
- price_list_id: price_list.id,
61
- amount: (amount * 0.6).round(2),
62
- currency: currency,
63
- created_at: now,
64
- updated_at: now
65
- }
65
+ { id: id, variant_id: variant_id, currency: currency, amount: (amount * 0.6).round(2) }
66
66
  end
67
67
 
68
- Spree::Price.insert_all(rows) if rows.any?
68
+ price_list.bulk_update_prices(rows) if rows.any?
69
69
  end
70
70
 
71
71
  price_list.activate if price_list.can_activate?
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '5.6.0.rc5'.freeze
2
+ VERSION = '5.6.0.rc6'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
@@ -57,3 +57,13 @@ steps:
57
57
  notes: |
58
58
  Recomputes the descendant-inclusive products_count counter cache on every
59
59
  taxon in batches.
60
+
61
+ - id: product_tag_tenants
62
+ name: "Backfill tenant on product taggings"
63
+ task: "spree:upgrade:backfill_product_tag_tenants"
64
+ notes: |
65
+ Sets the tenant column on existing Spree::Product taggings from each
66
+ product's store_id, so product tag autocomplete is bounded to the owning
67
+ store (matching Spree::Order). New product taggings tenant themselves;
68
+ only rows created before the upgrade need this. Until it runs, older
69
+ product tags are hidden from the store-scoped tag vocabulary.
@@ -0,0 +1,26 @@
1
+ namespace :spree do
2
+ namespace :upgrade do
3
+ desc <<~DESC
4
+ Backfills the +tenant+ column on existing +Spree::Product+ taggings from
5
+ each product's +store_id+, so product tag vocabulary is bounded to the
6
+ owning store (matching +Spree::Order+, which already tenants its taggings).
7
+
8
+ Run once after upgrading to the release that adds
9
+ +acts_as_taggable_tenant :store_id+ to +Spree::Product+. New product
10
+ taggings get their tenant automatically; only rows created before the
11
+ upgrade need this. Idempotent — re-running only touches rows still
12
+ missing a tenant.
13
+ DESC
14
+ task backfill_product_tag_tenants: :environment do
15
+ taggings = ActsAsTaggableOn::Tagging.arel_table.name
16
+ products = Spree::Product.table_name
17
+
18
+ updated = ActsAsTaggableOn::Tagging.
19
+ where(taggable_type: 'Spree::Product', tenant: nil).
20
+ joins("INNER JOIN #{products} ON #{products}.id = #{taggings}.taggable_id").
21
+ update_all("tenant = #{products}.store_id")
22
+
23
+ puts " Backfilled tenant on #{updated} product tagging(s)."
24
+ end
25
+ end
26
+ end
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.6.0.rc5
4
+ version: 5.6.0.rc6
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-22 00:00:00.000000000 Z
13
+ date: 2026-07-23 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: i18n-tasks
@@ -1837,6 +1837,7 @@ files:
1837
1837
  - lib/tasks/markets.rake
1838
1838
  - lib/tasks/media.rake
1839
1839
  - lib/tasks/price_history.rake
1840
+ - lib/tasks/product_tag_tenants.rake
1840
1841
  - lib/tasks/products.rake
1841
1842
  - lib/tasks/publications.rake
1842
1843
  - lib/tasks/role_users.rake
@@ -1867,9 +1868,9 @@ licenses:
1867
1868
  - BSD-3-Clause
1868
1869
  metadata:
1869
1870
  bug_tracker_uri: https://github.com/spree/spree/issues
1870
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0.rc5
1871
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0.rc6
1871
1872
  documentation_uri: https://docs.spreecommerce.org/
1872
- source_code_uri: https://github.com/spree/spree/tree/v5.6.0.rc5
1873
+ source_code_uri: https://github.com/spree/spree/tree/v5.6.0.rc6
1873
1874
  post_install_message:
1874
1875
  rdoc_options: []
1875
1876
  require_paths: