The Strategic Ruby gem (Painless Strategy Pattern for Ruby and Rails) just had a new release in 0.9.1 (as well as 0.9.0), adding the following features:
- Strategy Matcher: custom matcher support for selecting strategies (e.g. implement a fuzz matcher for picking a strategy by full string name or any partial match in case it is typed by a user)
- Strategy Alias: specify an alias for selecting a strategy (e.g. car strategy has
"sedan"
as an alias) - Strategy Exclusion: exclude a strategy from a matcher (e.g. partial match on
'USA'
and'US'
, but not'U'
)
To give you some background, the Strategic Ruby gem was mostly born out of work I did last year at Chronogolf by Lightspeed, a golf course management web app built in Ruby on Rails, where we had countless of strategies for customizing models, especially in relation to quotes, pricing, payments, memberships, and golf course tee time reservations.
It is currently used in the DCR Programming Language, implementing language commands (Command Pattern) as a special case of Strategy Pattern, with auto-inference of strategy names from command file names by convention.
Strategic enables you to make any existing domain model "strategic", externalizing all logic concerning algorithmic variations into separate strategy classes that are easy to find, maintain and extend while honoring the Open/Closed Principle and avoiding conditionals.
In summary, if you make a class called TaxCalculator
strategic by including the Strategic
mixin module, now you are able to drop strategies under the tax_calculator
directory sitting next to the class (e.g. tax_calculator/us_strategy.rb
, tax_calculator/canada_strategy.rb
) while gaining extra API methods to grab strategy names to present in a user interface, grab strategy classes to select, and/or instantiate TaxCalculator
directly with a strategy from the get-go.
Example
1- Include the Strategic
module in the Class to strategize TaxCalculator
:
class TaxCalculator
include Strategic
# strategies may implement a tax_for(amount) method
end
2- Now, you can add strategies under this directory without having to modify the original class: tax_calculator
3- Add strategy classes having names ending with Strategy
by convention (e.g. UsStrategy
) under the namespace matching the original class name (TaxCalculator::
as in tax_calculator/us_strategy.rb
representing TaxCalculator::UsStrategy
) and including the module (Strategic::Strategy
):
class TaxCalculator::UsStrategy
include Strategic::Strategy
def tax_for(amount)
amount * state_rate(context.state)
end
# ... other strategy methods follow
end
class TaxCalculator::CanadaStrategy
include Strategic::Strategy
def tax_for(amount)
amount * (gst(context.province) + qst(context.province))
end
# ... other strategy methods follow
end
(note: if you use strategy inheritance hierarchies, make sure to have strategy base classes end with StrategyBase to avoid getting picked up as strategies)
4- In client code, set the strategy by underscored string reference minus the word strategy (e.g. UsStrategy becomes simply 'us'):
tax_calculator = TaxCalculator.new(args)
tax_calculator.strategy = 'us'
4a. Alternatively, instantiate the strategic model with a strategy to begin with:
tax_calculator = TaxCalculator.new_with_strategy('us', args)
5- Invoke the strategy implemented method:
tax = tax_calculator.tax_for(39.78)
Default strategy for a strategy name that has no strategy class is nil
You may set a default strategy on a strategic model via class method default_strategy
class TaxCalculator
include Strategic
default_strategy 'canada'
end
tax_calculator = TaxCalculator.new(args)
tax = tax_calculator.tax_for(39.78)
Learn more at:
Top comments (1)
Version 1.0.0 was released today, improving the design to more authentically match the Gang of Four Strategy Design Pattern:
andymaleh.blogspot.com/2021/03/str...