spree_core 5.0.5 → 5.0.6

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: f19fd414a37bad67d13a4a491582f6c391ab2677a3ee2a93952e7e8cd52d94d0
4
- data.tar.gz: 98d87831e0aeed0b5165eb4a38c6198a66423a4b2102a91f529eb2c9941db059
3
+ metadata.gz: 964d9b4646aa2d9c5ac22647f126c304bc7756b3034757b8bda9b0f9faa6d496
4
+ data.tar.gz: '07803df2f7c528d8a35c10b04f02f591b4c2ffe859163c7b2c94e5ad0edfc388'
5
5
  SHA512:
6
- metadata.gz: a6e96c2e6e5ac2c433392016b01fc5aa6a73a719ce766ba3d6619fae46559b9ca25141be70621d0f31d207a8896a1d79de7dc154d474334af02c46fc9dbe9fba
7
- data.tar.gz: 524ecf14bf52c91ac982cd6c489d45592eab70e68ed8de95f0565be7740a49a04aac9aeacffe94c3d355b480f68e6af051efd32b5ba2bd2334cdea1f1119f9f9
6
+ metadata.gz: 36a5518a9bfd31ed6b6b3a74df7953534a8568cc36a7204acf0eca5499e93bd0b88068f35bd8b7dcbc52d09b9f47ea629cfa582a39d24d717d61c9803e865748
7
+ data.tar.gz: 46fb9aab1ceafb1d44f872e870908e3ff0df44458c4b6248a4bc6e54655f4de4e457b47c9a5c8c133b16cc631f93226a0280585cb8ebee3920b677cd48acee40
@@ -28,7 +28,7 @@ module Spree
28
28
  def url_is_valid
29
29
  parts = url.split('.')
30
30
 
31
- errors.add(:url, 'use domain or subdomain') if (parts[0] != 'www' && parts.size > 3) || (parts[0] == 'www' && parts.size > 4) || parts.size < 2
31
+ errors.add(:url, 'use domain or subdomain') if parts.size > 4 || parts.size < 2
32
32
  end
33
33
 
34
34
  def ensure_default
@@ -0,0 +1,112 @@
1
+ module Spree
2
+ class Product < Spree.base_class
3
+ module Slugs
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ extend FriendlyId
8
+ include Spree::TranslatableResourceSlug
9
+
10
+ translates :slug
11
+ friendly_id :slug_candidates, use: [:history, :slugged, :scoped, :mobility], scope: spree_base_uniqueness_scope, slug_limit: 255
12
+
13
+ Product::Translation.class_eval do
14
+ acts_as_paranoid
15
+ # deleted translation values also need to be accessible for index views listing deleted resources
16
+ default_scope { unscope(where: :deleted_at) }
17
+
18
+ before_validation :set_slug
19
+ before_validation :ensure_slug_is_unique
20
+
21
+ validates :slug, presence: true, uniqueness: { allow_blank: true, case_sensitive: true, scope: [*::Spree.base_class.spree_base_uniqueness_scope, :locale] }
22
+
23
+ private
24
+
25
+ def set_slug
26
+ self.slug = generate_slug
27
+ end
28
+
29
+ def generate_slug
30
+ if name.blank? && slug.blank?
31
+ translated_model.name.to_url
32
+ elsif slug.blank?
33
+ name.to_url
34
+ else
35
+ slug.to_url
36
+ end
37
+ end
38
+
39
+ def ensure_slug_is_unique
40
+ slug_exists = self.class.where(slug: slug, locale: locale).where.not(id: id).exists?
41
+ self.slug = [slug, SecureRandom.uuid].join('-') if slug_exists
42
+ end
43
+ end
44
+
45
+ before_validation :downcase_slug
46
+ before_validation :normalize_slug, on: :update
47
+ after_destroy :punch_slugs
48
+ after_restore :regenerate_slug
49
+
50
+ validates :slug, presence: true, uniqueness: { allow_blank: true, case_sensitive: true, scope: spree_base_uniqueness_scope }
51
+
52
+ def self.slug_available?(slug, id)
53
+ !where(slug: slug).where.not(id: id).exists?
54
+ end
55
+ end
56
+
57
+ def ensure_slug_is_unique(candidate_slug)
58
+ return slug if candidate_slug.blank? || slug.blank?
59
+ return candidate_slug if self.class.slug_available?(candidate_slug, id)
60
+
61
+ normalize_friendly_id([candidate_slug, uuid_for_friendly_id])
62
+ end
63
+
64
+ private
65
+
66
+ def slug_candidates
67
+ if defined?(:deleted_at) && deleted_at.present?
68
+ [
69
+ ['deleted', :name],
70
+ ['deleted', :name, :sku],
71
+ ['deleted', :name, :uuid_for_friendly_id]
72
+ ]
73
+ else
74
+ [
75
+ [:name],
76
+ [:name, :sku],
77
+ [:name, :uuid_for_friendly_id]
78
+ ]
79
+ end
80
+ end
81
+
82
+ def downcase_slug
83
+ slug&.downcase!
84
+ end
85
+
86
+ def normalize_slug
87
+ self.slug = normalize_friendly_id(slug)
88
+ end
89
+
90
+ def regenerate_slug
91
+ self.slug = nil
92
+ save!
93
+ end
94
+
95
+ def punch_slugs
96
+ return if new_record? || frozen?
97
+
98
+ self.slug = nil
99
+
100
+ set_slug
101
+ update_column(:slug, slug)
102
+
103
+ new_slug = ->(rec) { "deleted-#{rec.id}_#{rec.slug}"[..254] }
104
+
105
+ translations.with_deleted.each { |rec| rec.update_columns(slug: new_slug.call(rec)) }
106
+ slugs.with_deleted.each { |rec| rec.update_column(:slug, new_slug.call(rec)) }
107
+
108
+ translations.find_by!(locale: I18n.locale).update_column(:slug, slug) if Spree.use_translations?
109
+ end
110
+ end
111
+ end
112
+ end
@@ -20,14 +20,17 @@
20
20
 
21
21
  module Spree
22
22
  class Product < Spree.base_class
23
- extend FriendlyId
23
+ acts_as_paranoid
24
+ acts_as_taggable_on :tags, :labels
25
+ auto_strip_attributes :name
26
+
24
27
  include Spree::ProductScopes
25
28
  include Spree::MultiStoreResource
26
29
  include Spree::TranslatableResource
27
- include Spree::TranslatableResourceSlug
28
30
  include Spree::MemoizedData
29
31
  include Spree::Metadata
30
32
  include Spree::Product::Webhooks
33
+ include Spree::Product::Slugs
31
34
  if defined?(Spree::VendorConcern)
32
35
  include Spree::VendorConcern
33
36
  end
@@ -53,33 +56,8 @@ module Spree
53
56
  pg_search_scope :search_by_name, against: { name: 'A', meta_title: 'B' },
54
57
  using: { tsearch: { prefix: true, any_word: true } }
55
58
  end
56
-
57
- before_save :set_slug
58
- acts_as_paranoid
59
- # deleted translation values also need to be accessible for index views listing deleted resources
60
- default_scope { unscope(where: :deleted_at) }
61
- def set_slug
62
- self.slug = generate_slug
63
- end
64
-
65
- private
66
-
67
- def generate_slug
68
- if name.blank? && slug.blank?
69
- translated_model.name.to_url
70
- elsif slug.blank?
71
- name.to_url
72
- else
73
- slug.to_url
74
- end
75
- end
76
59
  end
77
60
 
78
- friendly_id :slug_candidates, use: [:history, :scoped, :mobility], scope: spree_base_uniqueness_scope
79
- acts_as_paranoid
80
- auto_strip_attributes :name
81
- acts_as_taggable_on :tags, :labels
82
-
83
61
  # we need to have this callback before any dependent: :destroy associations
84
62
  # https://github.com/rails/rails/issues/3458
85
63
  before_destroy :ensure_not_in_complete_orders
@@ -145,17 +123,12 @@ module Spree
145
123
  after_initialize :ensure_master
146
124
  after_initialize :assign_default_tax_category
147
125
 
148
- before_validation :downcase_slug
149
- before_validation :normalize_slug, on: :update
150
126
  before_validation :validate_master
151
127
  before_validation :ensure_default_shipping_category
152
128
 
153
129
  after_create :add_associations_from_prototype
154
130
  after_create :build_variants_from_option_values_hash, if: :option_values_hash
155
131
 
156
- after_destroy :punch_slug
157
- after_restore :update_slug_history
158
-
159
132
  after_save :save_master
160
133
  after_save :run_touch_callbacks, if: :anything_changed?
161
134
  after_save :reset_nested_changes
@@ -173,7 +146,6 @@ module Spree
173
146
  validates :price, if: :requires_price?
174
147
  end
175
148
 
176
- validates :slug, presence: true, uniqueness: { allow_blank: true, case_sensitive: true, scope: spree_base_uniqueness_scope }
177
149
  validate :discontinue_on_must_be_later_than_make_active_at, if: -> { make_active_at && discontinue_on }
178
150
 
179
151
  scope :for_store, ->(store) { joins(:store_products).where(StoreProduct.table_name => { store_id: store.id }) }
@@ -512,17 +484,6 @@ module Spree
512
484
  where conditions.inject(:or)
513
485
  end
514
486
 
515
- def self.slug_available?(slug, id)
516
- !where(slug: slug).where.not(id: id).exists?
517
- end
518
-
519
- def ensure_slug_is_unique(candidate_slug)
520
- return slug if candidate_slug.blank? || slug.blank?
521
- return candidate_slug if self.class.slug_available?(candidate_slug, id)
522
-
523
- normalize_friendly_id([candidate_slug, uuid_for_friendly_id])
524
- end
525
-
526
487
  # Suitable for displaying only variants that has at least one option value.
527
488
  # There may be scenarios where an option type is removed and along with it
528
489
  # all option values. At that point all variants associated with only those
@@ -738,25 +699,6 @@ module Spree
738
699
  self.tax_category = Spree::TaxCategory.default if new_record?
739
700
  end
740
701
 
741
- def normalize_slug
742
- self.slug = normalize_friendly_id(slug)
743
- end
744
-
745
- def punch_slug
746
- # punch slug with date prefix to allow reuse of original
747
- return if frozen?
748
-
749
- update_column(:slug, "#{Time.current.to_i}_#{slug}"[0..254])
750
-
751
- translations.with_deleted.each do |t|
752
- t.update_column :slug, "#{Time.current.to_i}_#{t.slug}"[0..254]
753
- end
754
- end
755
-
756
- def update_slug_history
757
- save!
758
- end
759
-
760
702
  def anything_changed?
761
703
  saved_changes? || @nested_changes
762
704
  end
@@ -812,14 +754,6 @@ module Spree
812
754
  end
813
755
  end
814
756
 
815
- # Try building a slug based on the following fields in increasing order of specificity.
816
- def slug_candidates
817
- [
818
- :name,
819
- [:name, :sku]
820
- ]
821
- end
822
-
823
757
  def run_touch_callbacks
824
758
  run_callbacks(:touch)
825
759
  end
@@ -867,10 +801,6 @@ module Spree
867
801
  previously_new_record? || tag_list_previously_changed? || available_on_previously_changed?
868
802
  end
869
803
 
870
- def downcase_slug
871
- slug&.downcase!
872
- end
873
-
874
804
  def after_activate
875
805
  # Implement your logic here
876
806
  end
@@ -27,39 +27,39 @@ module Spree
27
27
  upper_limit_condition = true
28
28
  end
29
29
 
30
- eligibility_errors.add(:base, ineligible_message_max) unless upper_limit_condition
31
- eligibility_errors.add(:base, ineligible_message_min) unless lower_limit_condition
30
+ eligibility_errors.add(:base, ineligible_message_max(order)) unless upper_limit_condition
31
+ eligibility_errors.add(:base, ineligible_message_min(order)) unless lower_limit_condition
32
32
 
33
33
  eligibility_errors.empty?
34
34
  end
35
35
 
36
36
  private
37
37
 
38
- def formatted_amount_min
39
- Spree::Money.new(preferred_amount_min).to_s
38
+ def formatted_amount_min(order)
39
+ Spree::Money.new(preferred_amount_min, currency: order.currency).to_s
40
40
  end
41
41
 
42
- def formatted_amount_max
42
+ def formatted_amount_max(order)
43
43
  if preferred_amount_max.present?
44
- Spree::Money.new(preferred_amount_max).to_s
44
+ Spree::Money.new(preferred_amount_max, currency: order.currency).to_s
45
45
  else
46
46
  Spree.t('no_maximum')
47
47
  end
48
48
  end
49
49
 
50
- def ineligible_message_max
50
+ def ineligible_message_max(order)
51
51
  if preferred_operator_max == 'lt'
52
- eligibility_error_message(:item_total_more_than_or_equal, amount: formatted_amount_max)
52
+ eligibility_error_message(:item_total_more_than_or_equal, amount: formatted_amount_max(order))
53
53
  else
54
- eligibility_error_message(:item_total_more_than, amount: formatted_amount_max)
54
+ eligibility_error_message(:item_total_more_than, amount: formatted_amount_max(order))
55
55
  end
56
56
  end
57
57
 
58
- def ineligible_message_min
58
+ def ineligible_message_min(order)
59
59
  if preferred_operator_min == 'gte'
60
- eligibility_error_message(:item_total_less_than, amount: formatted_amount_min)
60
+ eligibility_error_message(:item_total_less_than, amount: formatted_amount_min(order))
61
61
  else
62
- eligibility_error_message(:item_total_less_than_or_equal, amount: formatted_amount_min)
62
+ eligibility_error_message(:item_total_less_than_or_equal, amount: formatted_amount_min(order))
63
63
  end
64
64
  end
65
65
  end
@@ -5,7 +5,7 @@
5
5
  data-controller="address-form address-autocomplete"
6
6
  data-address-form-countries-value="<%= available_countries.to_json %>"
7
7
  data-address-form-states-value="<%= available_states.to_json %>"
8
- data-address-form-current-state-id-value="<%= address.state_id %>"
8
+ data-address-form-current-state-id-value="<%= address.state_id || address_form.object.state_id %>"
9
9
  >
10
10
  <div id="<%= "#{address_id}country" %>">
11
11
  <div class="form-group mb-0">
@@ -33,6 +33,8 @@ module Spree
33
33
  include Spree::UserPaymentSource
34
34
  RUBY
35
35
  end
36
+ gsub_file user_class_file, "< ApplicationRecord", "< Spree.base_class"
37
+
36
38
  say "Successfully added Spree user modules into #{user_class_file}"
37
39
  else
38
40
  say "Could not locate user model file at #{user_class_file}. Please add these lines manually:", :red
@@ -42,13 +44,14 @@ module Spree
42
44
  include Spree::UserMethods
43
45
  include Spree::UserPaymentSource
44
46
  RUBY
47
+
48
+ say "Please replace < ApplicationRecord with < Spree.base_class in #{user_class_file}"
45
49
  end
46
50
 
47
51
  append_file 'config/initializers/spree.rb' do
48
52
  %Q{
49
53
  if defined?(Devise) && Devise.respond_to?(:parent_controller)
50
- Devise.parent_controller = "Spree::StoreController"
51
- Devise.parent_mailer = "Spree::BaseMailer"
54
+ Devise.parent_controller = "Spree::BaseController"
52
55
  end\n}
53
56
  end
54
57
  end
@@ -78,6 +78,11 @@ module Spree
78
78
  def install_storefront
79
79
  if @install_storefront && Spree::Core::Engine.frontend_available?
80
80
  generate 'spree:storefront:install'
81
+
82
+ # generate devise controllers if authentication is devise
83
+ if @authentication == 'devise'
84
+ generate 'spree:storefront:devise'
85
+ end
81
86
  end
82
87
  end
83
88
 
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '5.0.5'.freeze
2
+ VERSION = '5.0.6'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
@@ -46,21 +46,39 @@ namespace :common do
46
46
  "--authentication=#{args[:authentication]}"
47
47
  ]
48
48
 
49
- puts 'Setting up dummy database...'
50
- system('bin/rails db:environment:set RAILS_ENV=test > /dev/null 2>&1')
51
- system('bundle exec rake db:drop db:create > /dev/null 2>&1')
52
- Spree::DummyModelGenerator.start
53
- system('bundle exec rake db:migrate > /dev/null 2>&1')
49
+ if !skip_javascript || ENV['LIB_NAME'] == 'spree/emails'
50
+ puts 'Precompiling assets...'
51
+ system('bundle exec rake assets:precompile > /dev/null 2>&1')
52
+ end
53
+
54
+ unless ENV['NO_MIGRATE']
55
+ puts 'Setting up dummy database...'
56
+ system('bin/rails db:environment:set RAILS_ENV=test > /dev/null 2>&1')
57
+ system('bundle exec rake db:drop db:create > /dev/null 2>&1')
58
+ Spree::DummyModelGenerator.start
59
+ system('bundle exec rake db:migrate > /dev/null 2>&1')
60
+ end
54
61
 
55
62
  begin
56
63
  require "generators/#{ENV['LIB_NAME']}/install/install_generator"
57
64
  puts 'Running extension installation generator...'
58
- "#{ENV['LIB_NAME'].camelize}::Generators::InstallGenerator".constantize.start(['--auto-run-migrations'])
65
+
66
+ if ENV['NO_MIGRATE']
67
+ "#{ENV['LIB_NAME'].camelize}::Generators::InstallGenerator".constantize.start([])
68
+ else
69
+ "#{ENV['LIB_NAME'].camelize}::Generators::InstallGenerator".constantize.start(['--auto-run-migrations'])
70
+ end
59
71
  rescue LoadError
60
72
  puts 'Skipping installation no generator to run...'
61
73
  end
74
+ end
62
75
 
63
- system('bundle exec rake assets:precompile > /dev/null 2>&1') if !skip_javascript || ENV['LIB_NAME'] == 'spree/emails'
76
+ task :db_setup do |_t|
77
+ puts 'Setting up dummy database...'
78
+ system('bin/rails db:environment:set RAILS_ENV=test > /dev/null 2>&1')
79
+ system('bundle exec rake db:drop db:create > /dev/null 2>&1')
80
+ Spree::DummyModelGenerator.start
81
+ system('bundle exec rake db:migrate > /dev/null 2>&1')
64
82
  end
65
83
 
66
84
  task :seed do |_t|
@@ -2,7 +2,7 @@ require 'spree/testing_support/common_rake'
2
2
 
3
3
  desc 'Generates a dummy app for testing an extension'
4
4
  namespace :extension do
5
- task :test_app, [:user_class] do |_t, args|
5
+ task :test_app, [:authentication, :user_class] do |_t, args|
6
6
  Spree::DummyGeneratorHelper.inject_extension_requirements = true
7
7
  Rake::Task['common:test_app'].execute(args.with_defaults(install_admin: true, install_storefront: true))
8
8
  end
@@ -9,6 +9,12 @@ FactoryBot.define do
9
9
  trait :with_description do
10
10
  description { '<div>Test <strong>description</strong></div>' }
11
11
  end
12
+
13
+ trait :with_header_image do
14
+ after(:create) do |taxon|
15
+ taxon.image.attach(io: File.new(Spree::Core::Engine.root.join('spec', 'fixtures', 'thinking-cat.jpg')), filename: 'thinking-cat.jpg')
16
+ end
17
+ end
12
18
  end
13
19
 
14
20
  factory :automatic_taxon, parent: :taxon do
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.0.5
4
+ version: 5.0.6
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: 2025-06-30 00:00:00.000000000 Z
13
+ date: 2025-08-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: i18n-tasks
@@ -766,6 +766,7 @@ files:
766
766
  - app/models/spree/preference.rb
767
767
  - app/models/spree/price.rb
768
768
  - app/models/spree/product.rb
769
+ - app/models/spree/product/slugs.rb
769
770
  - app/models/spree/product/webhooks.rb
770
771
  - app/models/spree/product_option_type.rb
771
772
  - app/models/spree/product_promotion_rule.rb
@@ -1311,9 +1312,9 @@ licenses:
1311
1312
  - BSD-3-Clause
1312
1313
  metadata:
1313
1314
  bug_tracker_uri: https://github.com/spree/spree/issues
1314
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.0.5
1315
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.0.6
1315
1316
  documentation_uri: https://docs.spreecommerce.org/
1316
- source_code_uri: https://github.com/spree/spree/tree/v5.0.5
1317
+ source_code_uri: https://github.com/spree/spree/tree/v5.0.6
1317
1318
  post_install_message:
1318
1319
  rdoc_options: []
1319
1320
  require_paths: