forked from solidusio/solidus
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow using different preference defaults depending on a Solidus version
This feature is similar to [Rail's `load_defaults`](https://guides.rubyonrails.org/configuring.html#results-of-config-load-defaults). However, instead of loading imperatively, the defaults for a given version keep the history of the value in the preference declaration. Both systems are upgrade-friendly, as users need to adjust the new version defaults manually. The main advantage of this commit implementation is communication (as the code tells about its current state and history). However, it's architecturally more complex. `Spree::Core::VersionedValue` implements the core behavior. This class accepts a specification of how a value has changed in time and returns the result for a given solidus version. The specification consists of an initial value and a series of boundaries when it changed: ```ruby value = Spree::Core::VersionedValue.new(false, "3.0" => true) value.call("2.0") # => false value.call("3.0") # => true ``` `Spree::Preferences::Configuration` bundles the behavior into the preferences system. Its `.by_version` method builds a `Proc` that accepts a solidus version and returns the corresponding value. It's meant to be used in the `default:` keyword argument: ```ruby preference :foo, :boolean, default: by_version(false, "3.0" => false) ``` Accordingly, `Spree::Preferences::Preferable` has been modified to provide arguments to the `default:` `Proc`. As this module is not only included in `Spree::Preferences::Configuration` classes, it defaults to supply no arguments to it, but `Spree::Preferences::Configuration` overrides it to add the `#loaded_defaults` attribute. Users can specify the version defaults they want to use through the `#load_defaults` method in `Spree::AppConfiguration`, `Spree::BackendConfiguration`, `Spree::FrontendConfiguration` and `Spree::ApiConfiguration`. For instance, for `Spree::AppConfiguration', which gets bound in the `Spree.config` method used in the `spree` initializer: ```ruby Spree.config do |config| config.load_defaults '3.1' end ``` We have modified the `spree` generator template to add that line of code defaulting to the current Solidus version. As the sibling step, we should create an update task creating another initializer à la `new_framework_defaults` in Rails. Related to discussion solidusio#4045
- Loading branch information
1 parent
8053d89
commit b897ecc
Showing
7 changed files
with
289 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# frozen_string_literal: true | ||
|
||
module Spree | ||
module Core | ||
# Wrapper for a value that can be different depending on the Solidus version | ||
# | ||
# Some configuration defaults can be added or changed when a new Solidus | ||
# version is released. This class encapsulates getting the correct value for a | ||
# given Solidus version. | ||
# | ||
# The way it works is you provide an initial value in time, plus the version | ||
# boundary where it got changed. Then you can fetch the value providing the | ||
# desired Solidus version: | ||
# | ||
# @example | ||
# value = VersionedValue.new(true, "3.0.0" => false) | ||
# value.call("2.7.0") # => true | ||
# value.call("3.0.0") # => false | ||
# value.call("3.1.0") # => false | ||
# | ||
# Remember that you must provide the exact boundary when a value got changed, | ||
# which could easily be during a pre-release: | ||
# | ||
# @example | ||
# value = VersionedValue.new(true, "3.0.0" => false) | ||
# value.call("3.0.0.alpha") # => true | ||
# | ||
# value = VersionedValue.new(true, "3.0.0.alpha" => false) | ||
# value.call("3.0.0.alpha") # => false | ||
# | ||
# Multiple boundaries can also be provided: | ||
# | ||
# @example | ||
# value = VersionedValue.new(0, "2.0.0" => 1, "3.0.0" => 2) | ||
# value.call("1.0.0") # => 0 | ||
# value.call("2.1.0") # => 1 | ||
# value.call("3.0.0") # => 2 | ||
class VersionedValue | ||
attr_reader :boundaries | ||
|
||
# @param initial_value [Any] | ||
# @param boundary [Hash<String, Any>] Map from version number to new value | ||
def initialize(initial_value, boundaries = {}) | ||
@boundaries = Hash[ | ||
{ '0' => initial_value } | ||
.merge(boundaries) | ||
.transform_keys { |version| to_gem_version(version) } | ||
.sort | ||
] | ||
end | ||
|
||
# @param solidus_version [String] | ||
def call(solidus_version = Spree.solidus_version) | ||
solidus_version = to_gem_version(solidus_version) | ||
boundaries.fetch( | ||
boundaries | ||
.keys | ||
.reduce do |target, following| | ||
if target <= solidus_version && solidus_version < following | ||
target | ||
else | ||
following | ||
end | ||
end | ||
) | ||
end | ||
|
||
private | ||
|
||
def to_gem_version(string) | ||
Gem::Version.new(string) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'spec_helper' | ||
require 'spree/core/versioned_value' | ||
|
||
RSpec.describe Spree::Core::VersionedValue do | ||
context 'with no boundaries' do | ||
it 'takes the initial value' do | ||
expect( | ||
described_class | ||
.new(false) | ||
.call("2.1.0") | ||
).to be(false) | ||
end | ||
end | ||
|
||
context 'with a single boundary' do | ||
it 'takes the initial value when the version preceeds' do | ||
expect( | ||
described_class | ||
.new(false, '3.0.0' => true) | ||
.call("2.9.0") | ||
).to be(false) | ||
end | ||
|
||
it 'takes the new value when the version matches' do | ||
expect( | ||
described_class | ||
.new(false, '3.0.0' => true) | ||
.call("3.0.0") | ||
).to be(true) | ||
end | ||
|
||
it 'takes the new value when the version follows' do | ||
expect( | ||
described_class | ||
.new(false, '3.0.0' => true) | ||
.call("3.1.0") | ||
).to be(true) | ||
end | ||
|
||
it 'compares as version numbers' do | ||
expect( | ||
described_class | ||
.new(false, '2.10.0' => true) | ||
.call("2.7.0") | ||
).to be(false) | ||
end | ||
|
||
it 'sorts pre-releases before releases' do | ||
expect( | ||
described_class | ||
.new(false, '3.1.0' => true) | ||
.call("3.1.0.alpha") | ||
).to be(false) | ||
end | ||
end | ||
|
||
context 'with two boundaries' do | ||
it 'takes the initial value when the version preceeds the first boundary' do | ||
expect( | ||
described_class | ||
.new(0, '2.0.0' => 1, '3.0.0' => 2) | ||
.call('1.0.0') | ||
).to be(0) | ||
end | ||
|
||
it 'takes the new value after the first boundary when the version matches the first boundary' do | ||
expect( | ||
described_class | ||
.new(0, '2.0.0' => 1, '3.0.0' => 2) | ||
.call('2.0.0') | ||
).to be(1) | ||
end | ||
|
||
it 'takes the new value after the first boundary when the version follows the first boundary but preceeds the second one' do | ||
expect( | ||
described_class | ||
.new(0, '2.0.0' => 1, '3.0.0' => 2) | ||
.call('2.5.0') | ||
).to be(1) | ||
end | ||
|
||
it 'takes the new value after the second boundary when the version matches the second boundary' do | ||
expect( | ||
described_class | ||
.new(0, '2.0.0' => 1, '3.0.0' => 2) | ||
.call('3.0.0') | ||
).to be(2) | ||
end | ||
|
||
it 'takes the new value after the second boundary when the version follows the second boundary' do | ||
expect( | ||
described_class | ||
.new(0, '2.0.0' => 1, '3.0.0' => 2) | ||
.call('4.0.0') | ||
).to be(2) | ||
end | ||
|
||
it 'works regardless of the order given to the boundaries' do | ||
expect( | ||
described_class | ||
.new(0, '3.0.0' => 2, '2.0.0' => 1) | ||
.call('4.0.0') | ||
).to be(2) | ||
end | ||
|
||
it 'compares as version numbers' do | ||
expect( | ||
described_class | ||
.new(0, '2.0.0' => 1, '2.10.0' => 2) | ||
.call("2.7.0") | ||
).to be(1) | ||
end | ||
|
||
it 'sorts pre-releases before releases' do | ||
expect( | ||
described_class | ||
.new(0, '3.1.0.alpha' => 1, '3.1.0' => 2) | ||
.call("3.2.0") | ||
).to be(2) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters