strategic 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +11 -0
- data/LICENSE.txt +1 -1
- data/README.md +86 -67
- data/lib/strategic.rb +67 -9
- metadata +31 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d4031c3d360c08e12ac58e42d6e56b97c177c342e64a1680275f50ad0add7e08
|
4
|
+
data.tar.gz: 5b4b783eca5301aa05f6043729531c7458b40806ed3b768c7b341b283d77ccda
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5edee9e7e0f491e5ecda04465372bb9e45274ecc906d07798e627a4485f8776f1625c8175ff403b3f8a25a8e16781f20dd6d02c76b0600cc41b7a9c4354b920
|
7
|
+
data.tar.gz: 1c7ebfb4ce224c80d3181b55e4c9137ac7d1015733211592ec95218a581cedafd56abe235fab07de92c2cdf2343873eb2ad5a7789ebc18d97f05f84b88528c13
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
## 0.9.0
|
4
|
+
|
5
|
+
- `strategy_matcher` block support that enables any strategy to specify a custom matcher (or the superclass of all strategies instead)
|
6
|
+
- `strategy_exclusion` class method support that enables any strategy to specify exclusions from the custom `strategy_matcher`
|
7
|
+
- `strategy_alias` class method support that enables any strategy to specify extra aliases (used by superclass's `strategy_class_for` method)
|
8
|
+
|
9
|
+
## 0.8.0
|
10
|
+
|
11
|
+
- Initial version with `strategy_class_for`, `new_strategy`, `strategies`, and `strategy_names`
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,18 +1,23 @@
|
|
1
|
-
# Strategic
|
1
|
+
# Strategic 0.9.0
|
2
|
+
## Painless Strategy Pattern in Ruby and Rails
|
2
3
|
[![Gem Version](https://badge.fury.io/rb/strategic.svg)](http://badge.fury.io/rb/strategic)
|
4
|
+
[![Build Status](https://travis-ci.com/AndyObtiva/strategic.svg?branch=master)](https://travis-ci.com/AndyObtiva/strategic?branch=master)
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/github/AndyObtiva/strategic/badge.svg?branch=master)](https://coveralls.io/github/AndyObtiva/strategic?branch=master)
|
3
6
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
7
|
+
(Note: this gem is a very early alpha work in progress and may change API in the future)
|
8
|
+
|
9
|
+
`if`/`case` conditionals can get really hairy in highly sophisticated business domains.
|
10
|
+
Object-oriented inheritance helps remedy the problem, but dumping all
|
11
|
+
logic variations in subclasses can cause a maintenance nightmare.
|
12
|
+
Thankfully, the Strategy Pattern as per the [Gang of Four book](https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) solves the problem by externalizing logic to
|
8
13
|
separate classes outside the domain models.
|
9
14
|
|
10
|
-
Still, there are a number of challenges with repeated implementation of Strategy Pattern:
|
15
|
+
Still, there are a number of challenges with "repeated implementation" of the Strategy Pattern:
|
11
16
|
- Making domain models aware of newly added strategies without touching their
|
12
17
|
code (Open/Closed Principle).
|
13
|
-
- Fetching the right strategy without use of conditionals.
|
18
|
+
- Fetching the right strategy without the use of conditionals.
|
14
19
|
- Avoiding duplication of strategy dispatch code for multiple domain models
|
15
|
-
- Have
|
20
|
+
- Have strategies mirror an existing domain model inheritance hierarchy
|
16
21
|
|
17
22
|
`strategic` solves these problems by offering:
|
18
23
|
- Strategy Pattern support through a Ruby mixin and strategy path/name convention
|
@@ -21,56 +26,16 @@ code (Open/Closed Principle).
|
|
21
26
|
- Ability to fetch a strategy by name or by object type to mirror
|
22
27
|
- Plain Ruby and Ruby on Rails support
|
23
28
|
|
24
|
-
`
|
29
|
+
`Strategic` enables you to make any existing domain model "strategic",
|
25
30
|
externalizing all logic concerning algorithmic variations into separate strategy
|
26
|
-
classes that are easy to find, maintain and extend.
|
27
|
-
|
28
|
-
## Instructions
|
29
|
-
|
30
|
-
### Option 1: Bundler
|
31
|
-
|
32
|
-
Add the following to bundler's `Gemfile`.
|
33
|
-
|
34
|
-
```ruby
|
35
|
-
gem 'strategic', '~> 0.8.0'
|
36
|
-
```
|
37
|
-
|
38
|
-
### Option 2: Manual
|
39
|
-
|
40
|
-
Or manually install and require library.
|
41
|
-
|
42
|
-
```bash
|
43
|
-
gem install strategic -v0.8.0
|
44
|
-
```
|
45
|
-
|
46
|
-
```ruby
|
47
|
-
require 'strategic'
|
48
|
-
```
|
49
|
-
|
50
|
-
### Usage
|
51
|
-
|
52
|
-
Steps:
|
53
|
-
1. Have the original class you'd like to strategize include Strategic
|
54
|
-
2. Create a directory matching the class underscored file name minus the '.rb' extension
|
55
|
-
3. Create a strategy class under that directory, which:
|
56
|
-
- Lives under the original class namespace
|
57
|
-
- Extends the original class to strategize
|
58
|
-
- Has a class name that ends with `Strategy` suffix (e.g. `NewCustomerStrategy`)
|
59
|
-
4. Get needed strategy class using `strategy_class_for` class method taking strategy name (any case) or related object/type (can call `strategy_names` class method to obtain strategy names)
|
60
|
-
5. Instantiate strategy with needed constructor parameters
|
61
|
-
6. Invoke strategy method needed
|
62
|
-
|
63
|
-
Alternative approach:
|
64
|
-
|
65
|
-
Combine steps 4 and 5 using `new_strategy` method, which takes both strategy name
|
66
|
-
and constructor parameters
|
67
|
-
|
68
|
-
Passing an invalid strategy name to `strategy_class_for` returns original class as the default
|
69
|
-
strategy.
|
31
|
+
classes that are easy to find, maintain and extend while honoring the Open/Closed Principle.
|
70
32
|
|
71
33
|
### Example
|
72
34
|
|
73
|
-
|
35
|
+
<img src="strategic-example.png"
|
36
|
+
alt="Strategic Example" />
|
37
|
+
|
38
|
+
1. Include `Strategic` module in the Class to strategize: `TaxCalculator`
|
74
39
|
|
75
40
|
```ruby
|
76
41
|
class TaxCalculator
|
@@ -82,9 +47,9 @@ class TaxCalculator
|
|
82
47
|
end
|
83
48
|
```
|
84
49
|
|
85
|
-
2.
|
50
|
+
2. Now, you can add strategies under this directory without having to modify the original class: `tax_calculator`
|
86
51
|
|
87
|
-
3.
|
52
|
+
3. Add strategy classes under the namespace matching the original class name (`TaxCalculator`) and extending the original class (`TaxCalculator`) just to take advantage of default logic in it:
|
88
53
|
|
89
54
|
```ruby
|
90
55
|
class TaxCalculator::UsStrategy < TaxCalculator
|
@@ -108,32 +73,32 @@ class TaxCalculator::CanadaStrategy < TaxCalculator
|
|
108
73
|
end
|
109
74
|
```
|
110
75
|
|
111
|
-
4.
|
76
|
+
4. In client code, obtain the needed strategy by underscored string reference minus the word strategy (e.g. UsStrategy becomes simply 'us'):
|
112
77
|
|
113
78
|
```ruby
|
114
79
|
tax_calculator_strategy_class = TaxCalculator.strategy_class_for('us')
|
115
80
|
```
|
116
81
|
|
117
|
-
5. Instantiate strategy:
|
82
|
+
5. Instantiate the strategy object:
|
118
83
|
|
119
84
|
```ruby
|
120
85
|
tax_calculator_strategy = strategy_class.new('IL')
|
121
86
|
```
|
122
87
|
|
123
|
-
6. Invoke strategy method:
|
88
|
+
6. Invoke the strategy overridden method:
|
124
89
|
|
125
90
|
```ruby
|
126
91
|
tax = tax_calculator_strategy.tax_for(39.78)
|
127
92
|
```
|
128
93
|
|
129
|
-
**Alternative approach using `new_strategy`:**
|
94
|
+
**Alternative approach using `new_strategy(strategy_name, *initializer_args)`:**
|
130
95
|
|
131
96
|
```ruby
|
132
97
|
tax_calculator_strategy = TaxCalculator.new_strategy('US', 'IL')
|
133
98
|
tax = tax_calculator_strategy.tax_for(39.78)
|
134
99
|
```
|
135
100
|
|
136
|
-
**Default strategy for a strategy name that has no strategy class is TaxCalculator
|
101
|
+
**Default strategy for a strategy name that has no strategy class is the superclass: `TaxCalculator`**
|
137
102
|
|
138
103
|
```ruby
|
139
104
|
tax_calculator_strategy_class = TaxCalculator.strategy_class_for('France')
|
@@ -141,13 +106,66 @@ tax_calculator_strategy = tax_calculator_strategy_class.new
|
|
141
106
|
tax = tax_calculator_strategy.tax_for(100.0) # returns 9.0 from TaxCalculator
|
142
107
|
```
|
143
108
|
|
144
|
-
##
|
109
|
+
## Setup
|
145
110
|
|
146
|
-
|
111
|
+
### Option 1: Bundler
|
112
|
+
|
113
|
+
Add the following to bundler's `Gemfile`.
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
gem 'strategic', '~> 0.9.0'
|
117
|
+
```
|
118
|
+
|
119
|
+
### Option 2: Manual
|
120
|
+
|
121
|
+
Or manually install and require library.
|
122
|
+
|
123
|
+
```bash
|
124
|
+
gem install strategic -v0.9.0
|
125
|
+
```
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
require 'strategic'
|
129
|
+
```
|
130
|
+
|
131
|
+
### Usage
|
132
|
+
|
133
|
+
Steps:
|
134
|
+
1. Have the original class you'd like to strategize include Strategic
|
135
|
+
2. Create a directory matching the class underscored file name minus the '.rb' extension
|
136
|
+
3. Create a strategy class under that directory, which:
|
137
|
+
- Lives under the original class namespace
|
138
|
+
- Extends the original class to strategize
|
139
|
+
- Has a class name that ends with `Strategy` suffix (e.g. `NewCustomerStrategy`)
|
140
|
+
4. Get needed strategy class using `strategy_class_for` class method taking strategy name (any case) or related object/type (can call `strategy_names` class method to obtain strategy names)
|
141
|
+
5. Instantiate strategy with needed constructor parameters
|
142
|
+
6. Invoke strategy method needed
|
143
|
+
|
144
|
+
Alternative approach:
|
145
|
+
|
146
|
+
Combine steps 4 and 5 using `new_strategy` method, which takes both strategy name
|
147
|
+
and constructor parameters
|
148
|
+
|
149
|
+
Passing an invalid strategy name to `strategy_class_for` returns original class as the default
|
150
|
+
strategy.
|
151
|
+
|
152
|
+
## API
|
153
|
+
|
154
|
+
- `StrategicSuperClass::strategy_class_for(string_or_class_or_object)`: selects a strategy class based on a string (e.g. 'us' selects USStrategy) or alternatively a class/object if you have a mirror hierarchy for the strategy hierarchy
|
155
|
+
- `StrategicSuperClass::new_strategy(string_or_class_or_object, *args, &block)`: instantiates a strategy based on a string/class/object and strategy constructor args
|
156
|
+
- `StrategicSuperClass::strategies`: returns list of strategies discovered by convention (nested under a namespace matching the superclass name)
|
157
|
+
- `StrategicSuperClass::strategy_names`: returns list of strategy names (strings) discovered by convention (nested under a namespace matching the superclass name)
|
158
|
+
- `StrategicSuperClass::strategy_matcher`: custom match (e.g. `strategy_matcher {|string| string.start_with?('C') && string.end_with?('o')}`)
|
159
|
+
- `StrategicSuperClass::strategy_exclusion`: exclusion from custom matcher (e.g. `strategy_exclusion 'Cio'`)
|
160
|
+
- `StrategicSuperClass::strategy_alias`: alias for strategy in addition to strategy's name derived from class name by convention (e.g. `strategy_alias 'USA'` for `UsStrategy`)
|
147
161
|
|
148
162
|
## TODO
|
149
163
|
|
150
|
-
|
164
|
+
[TODO.md](TODO.md)
|
165
|
+
|
166
|
+
## Change Log
|
167
|
+
|
168
|
+
[CHANGELOG.md](CHANGELOG.md)
|
151
169
|
|
152
170
|
## Contributing
|
153
171
|
|
@@ -161,7 +179,8 @@ None
|
|
161
179
|
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
162
180
|
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
163
181
|
|
164
|
-
##
|
182
|
+
## License
|
183
|
+
|
184
|
+
[MIT](LICENSE.txt)
|
165
185
|
|
166
|
-
Copyright (c) 2020 Andy Maleh.
|
167
|
-
further details.
|
186
|
+
Copyright (c) 2020-2021 Andy Maleh.
|
data/lib/strategic.rb
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
# Copyright (c) 2020-2021 Andy Maleh
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
1
22
|
module Strategic
|
2
23
|
def self.included(klass)
|
3
24
|
klass.extend(ClassMethods)
|
@@ -5,6 +26,30 @@ module Strategic
|
|
5
26
|
end
|
6
27
|
|
7
28
|
module ClassMethods
|
29
|
+
def strategy_alias(alias_string_or_class_or_object)
|
30
|
+
strategy_aliases << alias_string_or_class_or_object
|
31
|
+
end
|
32
|
+
|
33
|
+
def strategy_aliases
|
34
|
+
@strategy_aliases ||= []
|
35
|
+
end
|
36
|
+
|
37
|
+
def strategy_exclusion(exclusion_string_or_class_or_object)
|
38
|
+
strategy_exclusions << exclusion_string_or_class_or_object
|
39
|
+
end
|
40
|
+
|
41
|
+
def strategy_exclusions
|
42
|
+
@strategy_exclusions ||= []
|
43
|
+
end
|
44
|
+
|
45
|
+
def strategy_matcher(&matcher_block)
|
46
|
+
if block_given?
|
47
|
+
@strategy_matcher = matcher_block
|
48
|
+
else
|
49
|
+
@strategy_matcher
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
8
53
|
def require_strategies
|
9
54
|
klass_path = caller[1].split(':').first
|
10
55
|
strategy_path = File.expand_path(File.join(klass_path, '..', Strategic.underscore(self.name), '**', '*.rb'))
|
@@ -14,17 +59,30 @@ module Strategic
|
|
14
59
|
end
|
15
60
|
|
16
61
|
def strategy_class_for(string_or_class_or_object)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
62
|
+
strategy_class = nil
|
63
|
+
if strategy_matcher
|
64
|
+
strategy_class = strategies.detect do |strategy|
|
65
|
+
match = strategy&.strategy_matcher&.call(string_or_class_or_object)
|
66
|
+
match ||= strategy.instance_exec(string_or_class_or_object, &strategy_matcher)
|
67
|
+
match unless strategy.strategy_exclusions.include?(string_or_class_or_object)
|
68
|
+
end
|
21
69
|
else
|
22
|
-
|
70
|
+
if string_or_class_or_object.is_a?(String)
|
71
|
+
strategy_class_name = string_or_class_or_object.downcase
|
72
|
+
elsif string_or_class_or_object.is_a?(Class)
|
73
|
+
strategy_class_name = string_or_class_or_object.name
|
74
|
+
else
|
75
|
+
strategy_class_name = string_or_class_or_object.class.name
|
76
|
+
end
|
77
|
+
begin
|
78
|
+
class_name = "::#{self.name}::#{Strategic.classify(strategy_class_name)}Strategy"
|
79
|
+
strategy_class = class_eval(class_name)
|
80
|
+
rescue NameError
|
81
|
+
# No Op
|
82
|
+
end
|
23
83
|
end
|
24
|
-
|
25
|
-
|
26
|
-
rescue NameError
|
27
|
-
self
|
84
|
+
strategy_class ||= strategies.detect { |strategy| strategy.strategy_aliases.include?(string_or_class_or_object) }
|
85
|
+
strategy_class ||= self
|
28
86
|
end
|
29
87
|
|
30
88
|
def new_strategy(string_or_class_or_object, *args, &block)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strategic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Maleh
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -56,14 +56,14 @@ dependencies:
|
|
56
56
|
name: bundler
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '1.0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
@@ -86,42 +86,56 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - '='
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.8.
|
89
|
+
version: 0.8.23
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - '='
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.8.
|
96
|
+
version: 0.8.23
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: simplecov
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: 0.
|
103
|
+
version: 0.16.1
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: 0.
|
110
|
+
version: 0.16.1
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
112
|
+
name: simplecov-lcov
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: 0.
|
117
|
+
version: 0.7.0
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: 0.
|
124
|
+
version: 0.7.0
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: puts_debuggerer
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.8.1
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 0.8.1
|
125
139
|
description: |
|
126
140
|
if/case conditionals can get really hairy in highly sophisticated business domains.
|
127
141
|
Domain model inheritance can help remedy the problem, but you don't want to dump all
|
@@ -139,9 +153,11 @@ email: andy.am@gmail.com
|
|
139
153
|
executables: []
|
140
154
|
extensions: []
|
141
155
|
extra_rdoc_files:
|
156
|
+
- CHANGELOG.md
|
142
157
|
- LICENSE.txt
|
143
158
|
- README.md
|
144
159
|
files:
|
160
|
+
- CHANGELOG.md
|
145
161
|
- LICENSE.txt
|
146
162
|
- README.md
|
147
163
|
- lib/strategic.rb
|
@@ -149,7 +165,7 @@ homepage: http://github.com/AndyObtiva/strategic
|
|
149
165
|
licenses:
|
150
166
|
- MIT
|
151
167
|
metadata: {}
|
152
|
-
post_install_message:
|
168
|
+
post_install_message:
|
153
169
|
rdoc_options: []
|
154
170
|
require_paths:
|
155
171
|
- lib
|
@@ -164,9 +180,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
164
180
|
- !ruby/object:Gem::Version
|
165
181
|
version: '0'
|
166
182
|
requirements: []
|
167
|
-
|
168
|
-
|
169
|
-
signing_key:
|
183
|
+
rubygems_version: 3.2.3
|
184
|
+
signing_key:
|
170
185
|
specification_version: 4
|
171
186
|
summary: Painless Strategy Pattern for Ruby and Rails
|
172
187
|
test_files: []
|