chain_options 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +138 -0
- data/.travis.yml +21 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +72 -0
- data/LICENSE.txt +21 -0
- data/README.md +319 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/chain_options.gemspec +32 -0
- data/lib/chain_options/builder.rb +27 -0
- data/lib/chain_options/integration.rb +96 -0
- data/lib/chain_options/option.rb +195 -0
- data/lib/chain_options/option_set.rb +123 -0
- data/lib/chain_options/test_integration/rspec.rb +131 -0
- data/lib/chain_options/util.rb +99 -0
- data/lib/chain_options/version.rb +5 -0
- data/lib/chain_options.rb +12 -0
- metadata +165 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5632f3b6617e959a1329ae0bba7f1c4c3f24b8678e326a60e2507c07aad97efe
|
4
|
+
data.tar.gz: 1d814bcc04f29d57d614faea7738e38df7ecdc1917016b084c30dd61b57d6d17
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5f3d50a324399a2a9709c4fbd2308678ba39c882c47460cc5541ef3c5265b21b4c9254c33c5f79368d6b937133274fc7eb5695cea42950a6dfa24957fd2bd38e
|
7
|
+
data.tar.gz: 02d5fc98eb5832186a75618e8b4721a15eb13282942ad12485bde7e7ee0dcc0595d3cf4a94345bd94e3d36bf26c7310758326c808a3eeb907c8e930030c1ae24
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.3
|
3
|
+
Exclude:
|
4
|
+
- tmp/**/*
|
5
|
+
|
6
|
+
#---------------------------------------------
|
7
|
+
# Layout
|
8
|
+
#---------------------------------------------
|
9
|
+
|
10
|
+
# Hashes do not need padding
|
11
|
+
Layout/SpaceInsideHashLiteralBraces:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
# Allow 2 space indentation for when inside a case
|
15
|
+
Layout/CaseIndentation:
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
# Allow empty lines in classes
|
19
|
+
Layout/EmptyLinesAroundClassBody:
|
20
|
+
Enabled: false
|
21
|
+
|
22
|
+
# Allow multiple spaces before first argument
|
23
|
+
Layout/SpaceBeforeFirstArg:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
# Allow extra spacing, e.g. to align components
|
27
|
+
Layout/ExtraSpacing:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
# Usually good, but in some cases not possible
|
31
|
+
Layout/AlignHash:
|
32
|
+
Enabled: false
|
33
|
+
|
34
|
+
# Allow an empty line after do / before end
|
35
|
+
Layout/EmptyLinesAroundBlockBody:
|
36
|
+
Enabled: false
|
37
|
+
|
38
|
+
# Again, generally a good idea, but it has problems with multiline operations in
|
39
|
+
# combination with assignments
|
40
|
+
Layout/MultilineOperationIndentation:
|
41
|
+
Enabled: false
|
42
|
+
|
43
|
+
# See the corresponding other cops
|
44
|
+
Layout/EmptyLinesAroundModuleBody:
|
45
|
+
Enabled: false
|
46
|
+
|
47
|
+
Layout/SpaceInLambdaLiteral:
|
48
|
+
Enabled: false
|
49
|
+
|
50
|
+
#---------------------------------------------
|
51
|
+
# Metrics
|
52
|
+
#---------------------------------------------
|
53
|
+
|
54
|
+
# Allow bigger classes
|
55
|
+
Metrics/ClassLength:
|
56
|
+
Enabled: false
|
57
|
+
|
58
|
+
Metrics/LineLength:
|
59
|
+
Max: 120
|
60
|
+
|
61
|
+
# To make it possible to copy or click on URIs in the code, we allow lines
|
62
|
+
# containing a URI to be longer than Max.
|
63
|
+
AllowHeredoc: true
|
64
|
+
AllowURI: true
|
65
|
+
|
66
|
+
Metrics/BlockLength:
|
67
|
+
Max: 75
|
68
|
+
Exclude:
|
69
|
+
- spec/**/*.rb
|
70
|
+
- lib/chain_options/test_integration/rspec.rb
|
71
|
+
|
72
|
+
# Allow longer methods
|
73
|
+
Metrics/MethodLength:
|
74
|
+
Enabled: false
|
75
|
+
|
76
|
+
# Allow bigger modules
|
77
|
+
Metrics/ModuleLength:
|
78
|
+
Enabled: false
|
79
|
+
|
80
|
+
Metrics/ParameterLists:
|
81
|
+
Exclude:
|
82
|
+
- lib/chain_options/option_set.rb
|
83
|
+
|
84
|
+
#---------------------------------------------
|
85
|
+
# Naming
|
86
|
+
#---------------------------------------------
|
87
|
+
|
88
|
+
Naming/HeredocDelimiterNaming:
|
89
|
+
Enabled: false
|
90
|
+
|
91
|
+
#---------------------------------------------
|
92
|
+
# Style
|
93
|
+
#---------------------------------------------
|
94
|
+
|
95
|
+
# Allow fail() for initial exception, raise() for re-raise
|
96
|
+
# It seems that the cop decision was mainly based on "more people use raise than fail"...
|
97
|
+
Style/SignalException:
|
98
|
+
Enabled: false
|
99
|
+
|
100
|
+
# Allow assigning multiple variables in one line.
|
101
|
+
# This should not be overused, but comes in handy when assigning initializer values to instance variables
|
102
|
+
Style/ParallelAssignment:
|
103
|
+
Enabled: false
|
104
|
+
|
105
|
+
# Depending on the situation, it might make more sense to use
|
106
|
+
# [:symbol1, :symbol2] over %i[symbol1 symbol2], e.g. for multiline aligning reasons.
|
107
|
+
Style/SymbolArray:
|
108
|
+
Enabled: false
|
109
|
+
|
110
|
+
# Not all modules have to have top level comments
|
111
|
+
Style/Documentation:
|
112
|
+
Enabled: false
|
113
|
+
|
114
|
+
# Allow class variable usage
|
115
|
+
Style/ClassVars:
|
116
|
+
Enabled: false
|
117
|
+
|
118
|
+
# Allow block comments
|
119
|
+
Style/BlockComments:
|
120
|
+
Enabled: false
|
121
|
+
|
122
|
+
# Allow the use of !! (conversion of nil/object to true/false)
|
123
|
+
Style/DoubleNegation:
|
124
|
+
Enabled: false
|
125
|
+
|
126
|
+
# Allow unless/if blocks even for one-liners
|
127
|
+
Style/IfUnlessModifier:
|
128
|
+
Enabled: false
|
129
|
+
|
130
|
+
Style/GuardClause:
|
131
|
+
Enabled: false
|
132
|
+
|
133
|
+
Style/AccessModifierDeclarations:
|
134
|
+
Enabled: false
|
135
|
+
|
136
|
+
Style/MethodMissingSuper:
|
137
|
+
Exclude:
|
138
|
+
- lib/chain_options/util.rb
|
data/.travis.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
language: ruby
|
2
|
+
sudo: false
|
3
|
+
cache: bundler
|
4
|
+
|
5
|
+
rvm:
|
6
|
+
- 2.3.0
|
7
|
+
- 2.3.3
|
8
|
+
- 2.4.0
|
9
|
+
- 2.4.4
|
10
|
+
- 2.5.0
|
11
|
+
- 2.5.3
|
12
|
+
|
13
|
+
before_install: gem install bundler
|
14
|
+
|
15
|
+
before_script:
|
16
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
17
|
+
- chmod +x ./cc-test-reporter
|
18
|
+
- ./cc-test-reporter before-build
|
19
|
+
|
20
|
+
after_script:
|
21
|
+
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
chain_options (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ast (2.4.0)
|
10
|
+
byebug (10.0.2)
|
11
|
+
coderay (1.1.2)
|
12
|
+
diff-lcs (1.3)
|
13
|
+
docile (1.3.1)
|
14
|
+
jaro_winkler (1.5.1)
|
15
|
+
json (2.1.0)
|
16
|
+
method_source (0.9.2)
|
17
|
+
parallel (1.12.1)
|
18
|
+
parser (2.5.3.0)
|
19
|
+
ast (~> 2.4.0)
|
20
|
+
powerpack (0.1.2)
|
21
|
+
pry (0.12.2)
|
22
|
+
coderay (~> 1.1.0)
|
23
|
+
method_source (~> 0.9.0)
|
24
|
+
pry-byebug (3.6.0)
|
25
|
+
byebug (~> 10.0)
|
26
|
+
pry (~> 0.10)
|
27
|
+
rainbow (3.0.0)
|
28
|
+
rake (10.5.0)
|
29
|
+
rspec (3.8.0)
|
30
|
+
rspec-core (~> 3.8.0)
|
31
|
+
rspec-expectations (~> 3.8.0)
|
32
|
+
rspec-mocks (~> 3.8.0)
|
33
|
+
rspec-core (3.8.0)
|
34
|
+
rspec-support (~> 3.8.0)
|
35
|
+
rspec-expectations (3.8.2)
|
36
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
37
|
+
rspec-support (~> 3.8.0)
|
38
|
+
rspec-mocks (3.8.0)
|
39
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
40
|
+
rspec-support (~> 3.8.0)
|
41
|
+
rspec-support (3.8.0)
|
42
|
+
rubocop (0.60.0)
|
43
|
+
jaro_winkler (~> 1.5.1)
|
44
|
+
parallel (~> 1.10)
|
45
|
+
parser (>= 2.5, != 2.5.1.1)
|
46
|
+
powerpack (~> 0.1)
|
47
|
+
rainbow (>= 2.2.2, < 4.0)
|
48
|
+
ruby-progressbar (~> 1.7)
|
49
|
+
unicode-display_width (~> 1.4.0)
|
50
|
+
ruby-progressbar (1.10.0)
|
51
|
+
simplecov (0.16.1)
|
52
|
+
docile (~> 1.1)
|
53
|
+
json (>= 1.8, < 3)
|
54
|
+
simplecov-html (~> 0.10.0)
|
55
|
+
simplecov-html (0.10.2)
|
56
|
+
unicode-display_width (1.4.0)
|
57
|
+
|
58
|
+
PLATFORMS
|
59
|
+
ruby
|
60
|
+
|
61
|
+
DEPENDENCIES
|
62
|
+
bundler (~> 1.16)
|
63
|
+
chain_options!
|
64
|
+
pry (~> 0.12)
|
65
|
+
pry-byebug (~> 3.6)
|
66
|
+
rake (~> 10.0)
|
67
|
+
rspec (~> 3.8)
|
68
|
+
rubocop (~> 0.60)
|
69
|
+
simplecov (~> 0.16)
|
70
|
+
|
71
|
+
BUNDLED WITH
|
72
|
+
1.17.1
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Stefan Exner
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,319 @@
|
|
1
|
+
[](https://travis-ci.org/lokalportal/chain_options)
|
2
|
+
[](https://codeclimate.com/github/lokalportal/chain_options/maintainability)
|
3
|
+
[](https://codeclimate.com/github/lokalportal/chain_options/test_coverage)
|
4
|
+
|
5
|
+
# ChainOptions
|
6
|
+
|
7
|
+
ChainOptions is a small gem which allows you to add non-destructive chainable options to
|
8
|
+
your classes. It is useful to incrementally build instances without overriding the previous
|
9
|
+
one and provides an easy-to-understand DSL to set options either through
|
10
|
+
method-chaining or in a block.
|
11
|
+
|
12
|
+
An example:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class MyItemFeed
|
16
|
+
include ChainOptions::Integration
|
17
|
+
|
18
|
+
chain_option :page,
|
19
|
+
default: 1,
|
20
|
+
invalid: :default,
|
21
|
+
validate: ->(value) { value.to_i.positive? }
|
22
|
+
|
23
|
+
chain_option :per_page,
|
24
|
+
default: 30,
|
25
|
+
validate: ->(value) { value.to_i.positive? },
|
26
|
+
invalid: :default
|
27
|
+
end
|
28
|
+
|
29
|
+
feed = MyItemFeed.new.build_options do
|
30
|
+
set :page, params[:page]
|
31
|
+
set :per_page, params[:per_page]
|
32
|
+
end
|
33
|
+
|
34
|
+
# or
|
35
|
+
|
36
|
+
feed = MyItemFeed.new.page(params[:page]).per_page(params[:per_page])
|
37
|
+
```
|
38
|
+
|
39
|
+
## Installation
|
40
|
+
|
41
|
+
Add this line to your application's Gemfile:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
gem 'chain_options'
|
45
|
+
```
|
46
|
+
|
47
|
+
And then execute:
|
48
|
+
|
49
|
+
$ bundle
|
50
|
+
|
51
|
+
Or install it yourself as:
|
52
|
+
|
53
|
+
$ gem install chain_options
|
54
|
+
|
55
|
+
## Usage
|
56
|
+
|
57
|
+
To use ChainOptions in one of your classes, simply include its integration module:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
include ChainOptions::Integration
|
61
|
+
```
|
62
|
+
|
63
|
+
Afterwards, you're ready to define the options available to instances of your class.
|
64
|
+
|
65
|
+
### Basic Options
|
66
|
+
|
67
|
+
The easiest way to define an option is to call `chain_option` with just the option name:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
class MyClass
|
71
|
+
chain_option :my_option
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
This will generate the method `#my_option` which is accessible by instances of your class.
|
76
|
+
When it's called with an argument, it will return a new instance of your class with
|
77
|
+
the option set to this value, when being called without an argument, it will return the current value.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
my_class = MyClass.new #=> Instance 1 of MyClass
|
81
|
+
my_class.my_option('my value') #=> Instance 2 of MyClass
|
82
|
+
my_class.my_option #=> 'my value'
|
83
|
+
```
|
84
|
+
|
85
|
+
Please note that instance variables are currently not carried over to the new
|
86
|
+
instances built when setting a new option.
|
87
|
+
This decision was made to ensure no cached values could be used any more
|
88
|
+
after changing an option value:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
class Feed
|
92
|
+
chain_option :page
|
93
|
+
chain_option :per_page
|
94
|
+
|
95
|
+
def entries
|
96
|
+
@entries ||= MyModel.page(page).per(per_page)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
Setting `page` to a different value after `#entries` was called once would not
|
102
|
+
lead to another page being loaded, the return value would stay the same.
|
103
|
+
|
104
|
+
This behaviour might be changed in the future, but would only make the gem more complex
|
105
|
+
for now.
|
106
|
+
|
107
|
+
Array may be passed in as multiple arguments or an Array object, so the following calls are equivalent:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
my_object.my_value(1, 2, 3)
|
111
|
+
my_option.my_value([1, 2, 3])
|
112
|
+
```
|
113
|
+
|
114
|
+
### Advanced Options
|
115
|
+
|
116
|
+
#### Filters
|
117
|
+
|
118
|
+
It is possible to apply filters to option values. As soon as a filter Proc is defined,
|
119
|
+
it is assumed that the option value will be an Array.
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
chain_option :my_even_numbers,
|
123
|
+
filter: -> (number) { number.even? }
|
124
|
+
|
125
|
+
my_object.my_even_numbers(1, 2, 3, 4, 5) #=> [2, 4]
|
126
|
+
```
|
127
|
+
|
128
|
+
**Note**: As soon as `:filter` is defined, the value will be treated as Array, even if only a single
|
129
|
+
element is passed in:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
my_object.my_even_numbers(2) #=> [2]
|
133
|
+
```
|
134
|
+
|
135
|
+
#### Value Validations
|
136
|
+
|
137
|
+
It is possible to define validations on the setting value. These are executed whenever a new
|
138
|
+
value is set and will either cause an Exception or the option going back to the default value:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
chain_option :per_page,
|
142
|
+
validate: -> (value) { value.to_i.positive? },
|
143
|
+
invalid: :raise
|
144
|
+
```
|
145
|
+
|
146
|
+
The above example ensures that a value set for the `per_page` option has to be positive.
|
147
|
+
Otherwise, an `ArgumentError` is raised.
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
chain_option :per_page,
|
151
|
+
default: 1
|
152
|
+
validate: -> (value) { value.to_i.positive? },
|
153
|
+
invalid: :default
|
154
|
+
|
155
|
+
my_object.per_page(-1).per_page #=> 1
|
156
|
+
```
|
157
|
+
|
158
|
+
**Note**: If filters are set up as well, your validation proc will always receive an Array, never a single element.
|
159
|
+
|
160
|
+
#### Value Transformations
|
161
|
+
|
162
|
+
It is possible to perform automatic transformations (or type casts) on an option value,
|
163
|
+
pretty similar to what ActiveRecord does when e.g. a numeric value is assigned to a string attribute.
|
164
|
+
|
165
|
+
As options don't have a type, you have to define the transformation yourself:
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
chain_option :my_strings,
|
169
|
+
transform: -> (element) { element.to_s }
|
170
|
+
|
171
|
+
chain_option :my_strings,
|
172
|
+
transform: :to_s
|
173
|
+
```
|
174
|
+
|
175
|
+
The above calls are equivalent. If a symbol is given, the value (resp. each element of it in case of
|
176
|
+
an Array) is expected to respond to a method with the same name.
|
177
|
+
|
178
|
+
If the value is an array, the `transform` Proc will receive each item individually.
|
179
|
+
|
180
|
+
#### Default Values
|
181
|
+
|
182
|
+
It is possible to specify a default value for each option using the `:default` keyword argument.
|
183
|
+
The default value is returned in the following cases:
|
184
|
+
|
185
|
+
* No custom value was set for the option yet
|
186
|
+
* The value set for the option is invalid and the option is set to use the default value instead (see below)
|
187
|
+
|
188
|
+
The default value may either be a Proc which is executed on demand or any kind of Ruby object.
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
chain_option :per_page,
|
192
|
+
default: -> { SomeStore.get_default_per_page }
|
193
|
+
```
|
194
|
+
|
195
|
+
#### Incremental Values
|
196
|
+
|
197
|
+
Options can be set to increment their value through multiple setter calls:
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
chain_option :favourite_books, incremental: true
|
201
|
+
|
202
|
+
user.favourite_books('Lord of the Rings').favourite_books('The Hobbit')
|
203
|
+
#=> [['Lord of the Rings'], ['The Hobbit]]
|
204
|
+
```
|
205
|
+
|
206
|
+
As the values should still be separateable, the elements which were added in each
|
207
|
+
setter call are wrapped in another array instead of just appending them to the collection.
|
208
|
+
Otherwise, it wouldn't be possible to determine that the following value was caused by two sets:
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
user.favourite_books('Momo', 'Neverending Story').favourite_books('Lord of the Rings', 'The Hobbit')
|
212
|
+
#=> [["Momo", "Neverending Story"], ["Lord of the Rings", "The Hobbit"]]
|
213
|
+
```
|
214
|
+
|
215
|
+
#### Blocks as option values
|
216
|
+
|
217
|
+
If your option accepts blocks as values, setting this to `true` allows you to use the block syntax
|
218
|
+
to set a new option value instead of having to pass in a lambda function or Proc object:
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
chain_option :my_proc, allow_block: true
|
222
|
+
|
223
|
+
my_object = my_object.my_proc do
|
224
|
+
# ...
|
225
|
+
end
|
226
|
+
|
227
|
+
my_object.my_proc #=> <#Proc...>
|
228
|
+
```
|
229
|
+
|
230
|
+
## Option Testing
|
231
|
+
|
232
|
+
ChainOptions comes with basic RSpec integration by providing custom matchers.
|
233
|
+
|
234
|
+
To use them, simply require the corresponding module and include it in your specs:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
require 'chain_options/test_integration/rspec'
|
238
|
+
|
239
|
+
subject { MyClass.new }
|
240
|
+
|
241
|
+
describe 'my_option' do
|
242
|
+
include ChainOptions::TestIntegration::Rspec
|
243
|
+
|
244
|
+
it { is_expected.to have_chain_option(:my_option) }
|
245
|
+
end
|
246
|
+
```
|
247
|
+
|
248
|
+
Every matcher call starts with `have_chain_option` which ensures the the given
|
249
|
+
object actually has access to a chain option with the given name.
|
250
|
+
|
251
|
+
### Value Acceptance
|
252
|
+
|
253
|
+
To test for values which should raise an exception when being set as a chain option value,
|
254
|
+
continue the matcher as follows:
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
it { is_expected.to have_chain_option(:my_option).which_takes(42).and_raises_an_exception }
|
258
|
+
```
|
259
|
+
|
260
|
+
This matcher can only fail if the option is set to `invalid: :raise`.
|
261
|
+
|
262
|
+
### Value Filters / Transformations
|
263
|
+
|
264
|
+
To test whether the option is actually set to the correct value after passing an object to it,
|
265
|
+
continue the matcher as follows:
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
it { is_expected.to have_chain_option(:my_option).which_takes(42).and_sets_it_as_value }
|
269
|
+
```
|
270
|
+
|
271
|
+
If you expect the option to perform a filtering and/or transformation, you can also
|
272
|
+
specify the actual value you expect to be set:
|
273
|
+
|
274
|
+
```ruby
|
275
|
+
it { is_expected.to have_chain_option(:my_option).which_takes(42).and_sets("42").as_value }
|
276
|
+
```
|
277
|
+
|
278
|
+
### Default Value
|
279
|
+
|
280
|
+
To test whether the option has a certain default value, continue the matcher as follows:
|
281
|
+
|
282
|
+
```ruby
|
283
|
+
it { is_expected.to have_chain_option(:my_option).with_the_default_value(21) }
|
284
|
+
```
|
285
|
+
|
286
|
+
### Basic Testing
|
287
|
+
|
288
|
+
If you can't or don't want to use the custom matchers, you could define your own helper
|
289
|
+
methods to keep your option tests readable:
|
290
|
+
|
291
|
+
```ruby
|
292
|
+
def expect_to_eql(name, value, expected)
|
293
|
+
expect(subject.send(name, value).send(name)).to eql expected
|
294
|
+
end
|
295
|
+
|
296
|
+
def expect_to_raise(name, value)
|
297
|
+
expect { subject.send(name, value) }.to raise_error(ArgumentError, /not valid/),
|
298
|
+
"`#{value.inspect}` should not be a valid value for option `#{name}`"
|
299
|
+
end
|
300
|
+
|
301
|
+
it { expect_to_eql :my_option, 42, '42' }
|
302
|
+
it { expect_to_raise :my_option, Object.new }
|
303
|
+
```
|
304
|
+
|
305
|
+
## Contributing
|
306
|
+
|
307
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/lokalportal/chain_options.
|
308
|
+
For pull request, please follow [git-flow](https://danielkummer.github.io/git-flow-cheatsheet/) naming conventions.
|
309
|
+
|
310
|
+
This project is intended to be a safe, welcoming space for collaboration,
|
311
|
+
and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
312
|
+
|
313
|
+
## License
|
314
|
+
|
315
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
316
|
+
|
317
|
+
## Code of Conduct
|
318
|
+
|
319
|
+
Everyone interacting in the ChainOptions project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/chain_options/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'chain_options'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'chain_options/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'chain_options'
|
9
|
+
spec.version = ChainOptions::VERSION
|
10
|
+
spec.authors = ['Stefan Exner']
|
11
|
+
spec.email = ['stex@sterex.de']
|
12
|
+
|
13
|
+
spec.summary = 'DSL to add non(destructive).option(methods).to(objects)'
|
14
|
+
spec.homepage = 'https://github.com/lokalportal/chain_options'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = 'exe'
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.required_ruby_version = ['>= 2.3', '< 3']
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
26
|
+
spec.add_development_dependency 'pry', '~> 0.12'
|
27
|
+
spec.add_development_dependency 'pry-byebug', '~> 3.6'
|
28
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3.8'
|
30
|
+
spec.add_development_dependency 'rubocop', '~> 0.60'
|
31
|
+
spec.add_development_dependency 'simplecov', '~> 0.16'
|
32
|
+
end
|