Skip to content

Commit

Permalink
Adds a generator to create an initializer with new defaults
Browse files Browse the repository at this point in the history
Following the work done in solidusio#4064, this commits introduces a Rails
generator that creates a new initializer called, by default,
`new_solidus_defaults.rb`.

This initializer works in a very similar way that
[`new_framework_defaults.rb` does in
Rails](https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#configure-framework-defaults).
It allows users to preview the defaults that have changed on a new
Solidus version, as they are printed one by one on a commented line.
Users can then keep enabling them while updating their application code.

The main difference with the Rails system is that the `load_defaults`
call is added to the initializer instead of the main configuration file
(it would be the `spree.rb` initializer in our case). Users can remove
it altogether when they're done with the process. The reason is that in
our case, the `loaded_defaults` instance variable of the application
configurations defaults to the last version, so it's not needed anymore
when the process is done. In contrast, calling `load_defaults` in Rails
changes the defaults imperatively to the new values (so it defaults to
the outdated ones).

For now, we're leaving this as a generator, but we could reference it
from a rake task, although probably there's no need.

Be aware that users need to provide the version from which they are
updating. It's an option that offers more flexibility, as users can
update from versions different from the latest one. It also plays well
with our system's flexibility, for instance, to change defaults between
a pre-release and a release. However, we can add some code to default to
the latest minor version, but we should keep that information in our
code, and that's a small burden for our update process.
  • Loading branch information
waiting-for-dev committed Jun 1, 2021
1 parent b897ecc commit 4495d56
Show file tree
Hide file tree
Showing 5 changed files with 330 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# 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.
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 -%>
80 changes: 80 additions & 0 deletions core/lib/generators/solidus/update/update_generator.rb
Original file line number Diff line number Diff line change
@@ -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
28 changes: 28 additions & 0 deletions core/lib/spree/core/preference_changes_between_solidus_versions.rb
Original file line number Diff line number Diff line change
@@ -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
169 changes: 169 additions & 0 deletions core/spec/lib/generators/solidus/update/update_generator_spec.rb
Original file line number Diff line number Diff line change
@@ -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.exists?(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
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 4495d56

Please sign in to comment.