diff --git a/api/Rakefile b/api/Rakefile index 8d65355b382..bc9ba722013 100644 --- a/api/Rakefile +++ b/api/Rakefile @@ -12,6 +12,6 @@ task default: :spec DummyApp::RakeTasks.new( gem_root: File.expand_path(__dir__), lib_name: 'solidus_api' -) +) { Spree::Api::Config.load_defaults(Spree.solidus_version) } task test_app: 'db:reset' diff --git a/api/lib/spree/api/engine.rb b/api/lib/spree/api/engine.rb index 16aed62447d..bec2e05ee45 100644 --- a/api/lib/spree/api/engine.rb +++ b/api/lib/spree/api/engine.rb @@ -11,6 +11,10 @@ class Engine < Rails::Engine # Leave initializer empty for backwards-compatibility. Other apps # might still rely on this event. initializer "spree.api.environment", before: :load_config_initializers do; end + + config.after_initialize do + Spree::Api::Config.check_load_defaults_called('Spree::Api::Config') + end end end end diff --git a/api/spec/spec_helper.rb b/api/spec/spec_helper.rb index 522be24cda0..b592492c382 100644 --- a/api/spec/spec_helper.rb +++ b/api/spec/spec_helper.rb @@ -13,7 +13,7 @@ DummyApp.setup( gem_root: File.expand_path('..', __dir__), lib_name: 'solidus_api' -) +) { Spree::Api::Config.load_defaults(Spree.solidus_version) } require 'rails-controller-testing' require 'rspec/rails' diff --git a/backend/Rakefile b/backend/Rakefile index e81acac764f..f4bb0f82875 100644 --- a/backend/Rakefile +++ b/backend/Rakefile @@ -14,7 +14,10 @@ task default: [:spec, 'spec:js'] DummyApp::RakeTasks.new( gem_root: File.expand_path(__dir__), lib_name: 'solidus_backend' -) +) do + Spree::Backend::Config.load_defaults(Spree.solidus_version) + Spree::Api::Config.load_defaults(Spree.solidus_version) +end task :teaspoon do require 'teaspoon' diff --git a/backend/lib/spree/backend/engine.rb b/backend/lib/spree/backend/engine.rb index 1b66cc7939c..42f32f74bd9 100644 --- a/backend/lib/spree/backend/engine.rb +++ b/backend/lib/spree/backend/engine.rb @@ -8,6 +8,10 @@ class Engine < ::Rails::Engine # Leave initializer empty for backwards-compatability. Other apps # might still rely on this event. initializer "spree.backend.environment", before: :load_config_initializers do; end + + config.after_initialize do + Spree::Backend::Config.check_load_defaults_called('Spree::Backend::Config') + end end end end diff --git a/backend/spec/spec_helper.rb b/backend/spec/spec_helper.rb index d0d7b706e77..c84ec6c1fb4 100644 --- a/backend/spec/spec_helper.rb +++ b/backend/spec/spec_helper.rb @@ -14,7 +14,10 @@ DummyApp.setup( gem_root: File.expand_path('..', __dir__), lib_name: 'solidus_backend' -) +) do + Spree::Backend::Config.load_defaults(Spree.solidus_version) + Spree::Api::Config.load_defaults(Spree.solidus_version) +end require 'rails-controller-testing' require 'rspec-activemodel-mocks' diff --git a/backend/spec/teaspoon_env.rb b/backend/spec/teaspoon_env.rb index dbf2fe8debe..2a4012e614d 100644 --- a/backend/spec/teaspoon_env.rb +++ b/backend/spec/teaspoon_env.rb @@ -48,5 +48,8 @@ gem_root: File.expand_path('..', __dir__), lib_name: 'solidus_backend', auto_migrate: false - ) + ) do + Spree::Backend::Config.load_defaults(Spree.solidus_version) + Spree::Api::Config.load_defaults(Spree.solidus_version) + end end diff --git a/core/lib/generators/solidus/update/templates/config/initializers/new_solidus_defaults.rb.tt b/core/lib/generators/solidus/update/templates/config/initializers/new_solidus_defaults.rb.tt new file mode 100644 index 00000000000..951c9f7c32e --- /dev/null +++ b/core/lib/generators/solidus/update/templates/config/initializers/new_solidus_defaults.rb.tt @@ -0,0 +1,29 @@ +# This initializer lets you preview the defaults that have changed on the new +# Solidus version. +# +# It allows you to enable them one by one while you adapt your application. +# When you're done with all of them, you can safely remove this file and add +# the updated `load_defaults` calls to the top of the config blocks in your +# Solidus main initializer. + +Spree.config do |config| + <%= @core_changes %> +end + +<% if defined?(Spree::Frontend::Engine) -%> +Spree::Frontend::Config.configure do |config| + <%= @frontend_changes %> +end +<% end -%> + +<% if defined?(Spree::Backend::Engine) -%> +Spree::Backend::Config.configure do |config| + <%= @backend_changes %> +end +<% end -%> + +<% if defined?(Spree::Api::Engine) -%> +Spree::Api::Config.configure do |config| + <%= @api_changes %> +end +<% end -%> diff --git a/core/lib/generators/solidus/update/update_generator.rb b/core/lib/generators/solidus/update/update_generator.rb new file mode 100644 index 00000000000..7d428d1e3af --- /dev/null +++ b/core/lib/generators/solidus/update/update_generator.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'spree/core/preference_changes_between_solidus_versions' +require 'rails/generators' + +module Solidus + class UpdateGenerator < ::Rails::Generators::Base + desc 'Generates a new initializer to preview the new defaults for current Solidus version' + + source_root File.expand_path('templates', __dir__) + + class_option :from, + type: :string, + banner: 'The version your are updating from. E.g. 2.11.10' + + class_option :initializer_basename, + type: :string, + default: 'new_solidus_defaults', + banner: 'The name for the new initializer' + + class_option :to, + type: :string, + default: Spree.solidus_version, + hide: true + + class_option :initializer_directory, + type: :string, + default: 'config/initializers/', + hide: true + + def create_new_defaults_initializer + from = options[:from] + to = options[:to] + @from = from + @core_changes = core_changes_template(from, to) + @frontend_changes = frontend_changes_template(from, to) + @backend_changes = backend_changes_template(from, to) + @api_changes = api_changes_template(from, to) + + template 'config/initializers/new_solidus_defaults.rb.tt', + File.join(options[:initializer_directory], "#{options[:initializer_basename]}.rb") + end + + private + + def core_changes_template(from, to) + changes_template_for(Spree::AppConfiguration, from, to) + end + + def frontend_changes_template(from, to) + return '' unless defined?(Spree::Frontend::Engine) + + changes_template_for(Spree::FrontendConfiguration, from, to) + end + + def backend_changes_template(from, to) + return '' unless defined?(Spree::Backend::Engine) + + changes_template_for(Spree::BackendConfiguration, from, to) + end + + def api_changes_template(from, to) + return '' unless defined?(Spree::Api::Engine) + + changes_template_for(Spree::ApiConfiguration, from, to) + end + + def changes_template_for(klass, from, to) + changes = Spree::Core::PreferenceChangesBetweenSolidusVersions.new(klass).call(from: from, to: to) + return '# No changes' if changes.empty? + + [ + ["config.load_defaults('#{from}')"] + + changes.map do |pref_key, change| + " # config.#{pref_key} = #{change[:to]}" + end.flatten + ].join("\n") + end + end +end diff --git a/core/lib/generators/spree/dummy/templates/rails/application.rb.tt b/core/lib/generators/spree/dummy/templates/rails/application.rb.tt index e387ded83ba..2eeeb345021 100644 --- a/core/lib/generators/spree/dummy/templates/rails/application.rb.tt +++ b/core/lib/generators/spree/dummy/templates/rails/application.rb.tt @@ -7,4 +7,3 @@ Bundler.require(*Rails.groups(assets: %w(development test))) require '<%= lib_name %>' <%= application_definition %> - diff --git a/core/lib/spree/core.rb b/core/lib/spree/core.rb index 8497edf6f70..d114db71e78 100644 --- a/core/lib/spree/core.rb +++ b/core/lib/spree/core.rb @@ -52,6 +52,13 @@ def self.config(&_block) end module Core + def self.solidus_installed? + (Rails.env.test? && Rails.application.class.name == 'DummyApp::Application') || + Rails.application.paths['config/initializers'].paths.any? do |path| + File.exist?(path.join('spree.rb')) + end + end + class GatewayError < RuntimeError; end end end diff --git a/core/lib/spree/core/engine.rb b/core/lib/spree/core/engine.rb index d525527c333..1055a58d322 100644 --- a/core/lib/spree/core/engine.rb +++ b/core/lib/spree/core/engine.rb @@ -63,6 +63,10 @@ class Engine < ::Rails::Engine app.config.action_mailer.preview_path = "{#{original_preview_path},#{solidus_preview_path}}" ActionMailer::Base.preview_path = app.config.action_mailer.preview_path end + + config.after_initialize do + Spree::Config.check_load_defaults_called('Spree::Config') + end end end end diff --git a/core/lib/spree/core/preference_changes_between_solidus_versions.rb b/core/lib/spree/core/preference_changes_between_solidus_versions.rb new file mode 100644 index 00000000000..9573d2fa06f --- /dev/null +++ b/core/lib/spree/core/preference_changes_between_solidus_versions.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Spree + module Core + class PreferenceChangesBetweenSolidusVersions + attr_reader :config_class + + def initialize(config_class) + @config_class = config_class + end + + def call(from:, to:) + preferences_from = config_class.new.load_defaults(from) + preferences_to = config_class.new.load_defaults(to) + preferences_from.reduce({}) do |changes, (pref_key, value_from)| + value_to = preferences_to[pref_key] + if value_from == value_to + changes + else + changes.merge( + pref_key => { from: value_from, to: value_to } + ) + end + end + end + end + end +end diff --git a/core/lib/spree/preferences/configuration.rb b/core/lib/spree/preferences/configuration.rb index cc6cf2f58a8..8f46ea2ffa5 100644 --- a/core/lib/spree/preferences/configuration.rb +++ b/core/lib/spree/preferences/configuration.rb @@ -38,8 +38,11 @@ class Configuration # defaults. Set via {#load_defaults} attr_reader :loaded_defaults + attr_reader :load_defaults_called + def initialize @loaded_defaults = Spree.solidus_version + @load_defaults_called = false end # @param [String] Solidus version from which take defaults when not @@ -47,9 +50,23 @@ def initialize # @see #load_defaults def load_defaults(version) @loaded_defaults = version + @load_defaults_called = true reset end + def check_load_defaults_called(instance_constant_name = nil) + return if load_defaults_called || !Spree::Core.solidus_installed? + + target_name = instance_constant_name || "#{self.class.name}.new" + Spree::Deprecation.warn <<~MSG + Please, indicate wich Solidus version defaults the application must use. + The method `#{target_name}.load_defaults` must be called from the Solidus initializer. E.g.: + + config.load_defaults('#{Spree.solidus_version}') + + MSG + end + # @yield [config] Yields this configuration object to a block def configure yield(self) diff --git a/core/lib/spree/testing_support/dummy_app.rb b/core/lib/spree/testing_support/dummy_app.rb index 607cd0fc048..d2498287741 100644 --- a/core/lib/spree/testing_support/dummy_app.rb +++ b/core/lib/spree/testing_support/dummy_app.rb @@ -34,9 +34,10 @@ module ApplicationHelper # @private module DummyApp - def self.setup(gem_root:, lib_name:, auto_migrate: true) + def self.setup(gem_root:, lib_name:, auto_migrate: true, &block) ENV["LIB_NAME"] = lib_name DummyApp::Application.config.root = File.join(gem_root, 'spec', 'dummy') + block.call if block_given? DummyApp::Application.initialize! @@ -127,6 +128,7 @@ class Application < ::Rails::Application Spree.user_class = 'Spree::LegacyUser' Spree.config do |config| + config.load_defaults(Spree.solidus_version) config.mails_from = "store@example.com" if ENV['DISABLE_ACTIVE_STORAGE'] diff --git a/core/lib/spree/testing_support/dummy_app/rake_tasks.rb b/core/lib/spree/testing_support/dummy_app/rake_tasks.rb index 43d4381cff9..94f9473430f 100644 --- a/core/lib/spree/testing_support/dummy_app/rake_tasks.rb +++ b/core/lib/spree/testing_support/dummy_app/rake_tasks.rb @@ -4,7 +4,7 @@ module DummyApp class RakeTasks include Rake::DSL - def initialize(gem_root:, lib_name:) + def initialize(gem_root:, lib_name:, &block) task :dummy_environment do ENV['RAILS_ENV'] = 'test' require lib_name @@ -12,7 +12,8 @@ def initialize(gem_root:, lib_name:) DummyApp.setup( gem_root: gem_root, lib_name: lib_name, - auto_migrate: false + auto_migrate: false, + &block ) end end diff --git a/core/spec/lib/generators/solidus/update/update_generator_spec.rb b/core/spec/lib/generators/solidus/update/update_generator_spec.rb new file mode 100644 index 00000000000..90e0fc01fe6 --- /dev/null +++ b/core/spec/lib/generators/solidus/update/update_generator_spec.rb @@ -0,0 +1,169 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'generators/solidus/update/update_generator' + +RSpec.describe Solidus::UpdateGenerator do + let(:initializer_directory) { Rails.root.join('tmp') } + let(:initializer) { File.join(initializer_directory, 'new_solidus_defaults.rb') } + let(:delete_initializer) { proc { File.delete(initializer) if File.exist?(initializer) } } + let(:invoke) do + lambda do |from, to| + Rails::Generators.invoke('solidus:update', [ + "--initializer_directory=#{Rails.root.join('tmp')}", + "--from=#{from}", + "--to=#{to}", + "--quiet" + ]) + end + end + + before { delete_initializer.call } + after { delete_initializer.call } + + context 'core' do + it 'adds changes when present' do + config_class = Class.new(Spree::Preferences::Configuration) do + preference :foo, :boolean, default: by_version(true, '3.0' => false) + end + stub_const('Spree::AppConfiguration', config_class) + + invoke.('2.0', '3.0') + + expect(File.read(initializer)).to include( + <<~RUBY + Spree.config do |config| + config.load_defaults('2.0') + # config.foo = false + end + RUBY + ) + end + + it "informs about no changes if there're none" do + config_class = Class.new(Spree::Preferences::Configuration) + stub_const('Spree::AppConfiguration', config_class) + + invoke.('2.0', '3.0') + + expect(File.read(initializer)).to include( + <<~RUBY + Spree.config do |config| + # No changes + end + RUBY + ) + end + end + + context 'frontend' do + before { stub_const('Spree::Frontend::Engine', true) } + + it 'adds changes when present' do + config_class = Class.new(Spree::Preferences::Configuration) do + preference :foo, :boolean, default: by_version(true, '3.0' => false) + end + stub_const('Spree::FrontendConfiguration', config_class) + + invoke.('2.0', '3.0') + + expect(File.read(initializer)).to include( + <<~RUBY + Spree::Frontend::Config.configure do |config| + config.load_defaults('2.0') + # config.foo = false + end + RUBY + ) + end + + it "informs about no changes if there're none" do + config_class = Class.new(Spree::Preferences::Configuration) + stub_const('Spree::FrontendConfiguration', config_class) + + invoke.('2.0', '3.0') + + expect(File.read(initializer)).to include( + <<~RUBY + Spree::Frontend::Config.configure do |config| + # No changes + end + RUBY + ) + end + end + + context 'backend' do + before { stub_const('Spree::Backend::Engine', true) } + + it 'adds changes when present' do + config_class = Class.new(Spree::Preferences::Configuration) do + preference :foo, :boolean, default: by_version(true, '3.0' => false) + end + stub_const('Spree::BackendConfiguration', config_class) + + invoke.('2.0', '3.0') + + expect(File.read(initializer)).to include( + <<~RUBY + Spree::Backend::Config.configure do |config| + config.load_defaults('2.0') + # config.foo = false + end + RUBY + ) + end + + it "informs about no changes if there're none" do + config_class = Class.new(Spree::Preferences::Configuration) + stub_const('Spree::BackendConfiguration', config_class) + + invoke.('2.0', '3.0') + + expect(File.read(initializer)).to include( + <<~RUBY + Spree::Backend::Config.configure do |config| + # No changes + end + RUBY + ) + end + end + + context 'api' do + before { stub_const('Spree::Api::Engine', true) } + + it 'adds changes when present' do + config_class = Class.new(Spree::Preferences::Configuration) do + preference :foo, :boolean, default: by_version(true, '3.0' => false) + end + stub_const('Spree::ApiConfiguration', config_class) + + invoke.('2.0', '3.0') + + expect(File.read(initializer)).to include( + <<~RUBY + Spree::Api::Config.configure do |config| + config.load_defaults('2.0') + # config.foo = false + end + RUBY + ) + end + + it "informs about no changes if there're none" do + config_class = Class.new(Spree::Preferences::Configuration) + stub_const('Spree::ApiConfiguration', config_class) + + invoke.('2.0', '3.0') + + expect(File.read(initializer)).to include( + <<~RUBY + Spree::Api::Config.configure do |config| + # No changes + end + RUBY + ) + end + end +end diff --git a/core/spec/lib/spree/core/preference_changes_between_solidus_versions_spec.rb b/core/spec/lib/spree/core/preference_changes_between_solidus_versions_spec.rb new file mode 100644 index 00000000000..93761dcecf1 --- /dev/null +++ b/core/spec/lib/spree/core/preference_changes_between_solidus_versions_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'spree/core/preference_changes_between_solidus_versions' +require 'spree/preferences/configuration' + +RSpec.describe Spree::Core::PreferenceChangesBetweenSolidusVersions do + it 'includes defaults that have changed' do + config_class = Class.new(Spree::Preferences::Configuration) do + preference :foo, :boolean, default: by_version(true, '3.0' => false) + end + + changes = described_class.new(config_class).call(from: '2.0', to: '3.0') + + expect(changes).to include(foo: { from: true, to: false }) + end + + it "doesn't include defaults that have not changed" do + config_class = Class.new(Spree::Preferences::Configuration) do + preference :foo, :boolean, default: by_version(true, '3.0' => false) + end + + changes = described_class.new(config_class).call(from: '2.0', to: '2.5') + + expect(changes.keys).not_to include(:foo) + end +end diff --git a/core/spec/models/spree/preferences/configuration_spec.rb b/core/spec/models/spree/preferences/configuration_spec.rb index 82c9ffd8e84..e6cac582592 100644 --- a/core/spec/models/spree/preferences/configuration_spec.rb +++ b/core/spec/models/spree/preferences/configuration_spec.rb @@ -3,52 +3,85 @@ require 'rails_helper' RSpec.describe Spree::Preferences::Configuration, type: :model do - before :all do - class AppConfig < Spree::Preferences::Configuration + let(:config) do + Class.new(Spree::Preferences::Configuration) do preference :color, :string, default: :blue preference :foo, :boolean, default: by_version(true, "3.0" => false) - end - @config = AppConfig.new + end.new end it "has named methods to access preferences" do - @config.color = 'orange' - expect(@config.color).to eq 'orange' + config.color = 'orange' + expect(config.color).to eq 'orange' end it "uses [ ] to access preferences" do - @config[:color] = 'red' - expect(@config[:color]).to eq 'red' + config[:color] = 'red' + expect(config[:color]).to eq 'red' end it "uses set/get to access preferences" do - @config.set(color: 'green') - expect(@config.get(:color)).to eq 'green' + config.set(color: 'green') + expect(config.get(:color)).to eq 'green' end it "allows defining different defaults depending on the Solidus version" do - @config.load_defaults 2.1 + config.load_defaults 2.1 - expect(@config.get(:foo)).to be(true) + expect(config.get(:foo)).to be(true) - @config.load_defaults 3.1 + config.load_defaults 3.1 - expect(@config.get(:foo)).to be(false) + expect(config.get(:foo)).to be(false) end describe '#load_defaults' do it 'changes loaded_defaults' do - @config.load_defaults '2.1' + config.load_defaults '2.1' - expect(@config.loaded_defaults).to eq('2.1') + expect(config.loaded_defaults).to eq('2.1') - @config.load_defaults '3.1' + config.load_defaults '3.1' - expect(@config.loaded_defaults).to eq('3.1') + expect(config.loaded_defaults).to eq('3.1') end it 'returns updated preferences' do - expect(@config.load_defaults('2.1')).to eq(foo: true, color: :blue) + expect(config.load_defaults('2.1')).to eq(foo: true, color: :blue) + end + + it 'sets load_defaults_called flag to true' do + expect(config.load_defaults_called).to be(false) + + config.load_defaults '3.1' + + expect(config.load_defaults_called).to be(true) + end + end + + describe '#check_load_defaults_called' do + context 'when load_defaults_called is true' do + it 'does not emit a warning' do + config.load_defaults '3.1' + + expect(Spree::Deprecation).not_to receive(:warn) + + config.check_load_defaults_called + end + end + + context 'when load_defaults_called is false' do + it 'emits a warning' do + expect(Spree::Deprecation).to receive(:warn).with(/load_defaults.*must be called/) + + config.check_load_defaults_called + end + + it 'includes constant name in the message when given' do + expect(Spree::Deprecation).to receive(:warn).with(/Spree::Config.load_defaults/, any_args) + + config.check_load_defaults_called('Spree::Config') + end end end end diff --git a/frontend/Rakefile b/frontend/Rakefile index ebadefc9a71..6d3cf76ca90 100644 --- a/frontend/Rakefile +++ b/frontend/Rakefile @@ -12,6 +12,9 @@ task default: :spec DummyApp::RakeTasks.new( gem_root: File.expand_path(__dir__), lib_name: 'solidus_frontend' -) +) do + Spree::Api::Config.load_defaults(Spree.solidus_version) + Spree::Frontend::Config.load_defaults(Spree.solidus_version) +end task test_app: 'db:reset' diff --git a/frontend/lib/spree/frontend/engine.rb b/frontend/lib/spree/frontend/engine.rb index 53e18ff2fb7..6c41b26a563 100644 --- a/frontend/lib/spree/frontend/engine.rb +++ b/frontend/lib/spree/frontend/engine.rb @@ -10,6 +10,10 @@ class Engine < ::Rails::Engine # Leave initializer empty for backwards-compatability. Other apps # might still rely on this event. initializer "spree.frontend.environment", before: :load_config_initializers do; end + + config.after_initialize do + Spree::Frontend::Config.check_load_defaults_called('Spree::Frontend::Config') + end end end end diff --git a/frontend/spec/spec_helper.rb b/frontend/spec/spec_helper.rb index f292b9c01b1..c5e79eb50f8 100644 --- a/frontend/spec/spec_helper.rb +++ b/frontend/spec/spec_helper.rb @@ -14,7 +14,10 @@ DummyApp.setup( gem_root: File.expand_path('..', __dir__), lib_name: 'solidus_frontend' -) +) do + Spree::Api::Config.load_defaults(Spree.solidus_version) + Spree::Frontend::Config.load_defaults(Spree.solidus_version) +end require 'rails-controller-testing' require 'rspec/rails'