assembler 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -2
- data/CHANGES.md +11 -0
- data/CONTRIBUTING.md +19 -0
- data/README.md +153 -30
- data/RELEASING.md +15 -0
- data/lib/assembler.rb +48 -16
- data/lib/assembler/builder.rb +17 -14
- data/lib/assembler/initializer.rb +18 -21
- data/lib/assembler/parameter.rb +79 -0
- data/lib/assembler/version.rb +1 -1
- data/spec/after_assembly_spec.rb +91 -0
- data/spec/assemble_from_options_spec.rb +384 -0
- data/spec/{assembler_spec.rb → assemble_from_spec.rb} +19 -3
- data/spec/before_assembly_spec.rb +80 -0
- data/spec/spec_helper.rb +1 -0
- metadata +13 -5
- data/lib/assembler/parameters.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e1d3eb4e375c75519e76037b6ac633be9901d78d
|
4
|
+
data.tar.gz: 0fd01f2626ac0d88104703fa5ea2545d7c581f85
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ff23dd635deec43014f704eabbc1282de5f5a7cd1fa6bd584da667eba048ecb39e2b895989e321dfb70c7acb515d219402e15ed983dc8650f262e166bb46842
|
7
|
+
data.tar.gz: d833e8aba1907888013f186e4afc2f22a42edea19d81ff6893791f6dc05f60ca8781396c27a97f8b8e6cc8931081383f8dddf6b509dc5df7b576fab5b8f1b25d
|
data/.rspec
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
--
|
2
|
-
--format documentation
|
1
|
+
--require spec_helper
|
data/CHANGES.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# Changes
|
2
2
|
|
3
|
+
## 1.2.0
|
4
|
+
|
5
|
+
* Add ability to access (optionally) coerced values on the builder object, for
|
6
|
+
example Foo.new(some: 'value') {|b| puts b.some}. (Ryan Michael)
|
7
|
+
* Add `assemble_from_options` DSL method for defining coercions and aliases. (Ryan Michael)
|
8
|
+
* Add `before_assembly` and `after_assembly` hooks. (Ben Hamill)
|
9
|
+
* Make the `before_assembly` and `after_assembly` hooks additive, in the case
|
10
|
+
that those methods are called more than once. (Ben Hamill)
|
11
|
+
* Change up spec set up and move project documentation around a little. (Ben
|
12
|
+
Hamill)
|
13
|
+
|
3
14
|
## 1.1.0
|
4
15
|
|
5
16
|
* Make Ruby 1.9-compatible. (Ryan Michael)
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
Help is gladly welcomed. If you have a feature you'd like to add, it's much more
|
4
|
+
likely to get in (or get in faster) the closer you stick to these steps:
|
5
|
+
|
6
|
+
1. Open an Issue to talk about it. We can discuss whether it's the right
|
7
|
+
direction or maybe help track down a bug, etc.
|
8
|
+
1. Fork the project, and make a branch to work on your feature/fix. Master is
|
9
|
+
where you'll want to start from.
|
10
|
+
1. Turn the Issue into a Pull Request. There are several ways to do this, but
|
11
|
+
[hub](https://github.com/defunkt/hub) is probably the easiest.
|
12
|
+
1. Make sure your Pull Request includes tests.
|
13
|
+
1. Bonus points if your Pull Request updates `CHANGES.md` to include a summary
|
14
|
+
of your changes and your name like the other entries. If the last entry is
|
15
|
+
the last release, add a new `## Unreleased` heading.
|
16
|
+
1. *Do not* rev the version number in your pull request.
|
17
|
+
|
18
|
+
If you don't know how to fix something, even just a Pull Request that includes a
|
19
|
+
failing test can be helpful. If in doubt, make an Issue to discuss.
|
data/README.md
CHANGED
@@ -7,14 +7,26 @@ bunch of nanomachines that can build [almost anything](http://en.wikipedia.org/w
|
|
7
7
|
|
8
8
|
Assembler is a library that gives you a DSL to describe a super-handy
|
9
9
|
initializer pattern. You specify the parameters your object should take and
|
10
|
-
Assembler
|
11
|
-
a [builder object](http://c2.com/cgi/wiki?BuilderPattern) to a block.
|
12
|
-
care of storing the parameters and gives you private accessors, too.
|
10
|
+
Assembler gives you an initializer that takes an options hash as well as
|
11
|
+
yielding a [builder object](http://c2.com/cgi/wiki?BuilderPattern) to a block.
|
12
|
+
It takes care of storing the parameters and gives you private accessors, too.
|
13
|
+
|
14
|
+
## Contents
|
15
|
+
|
16
|
+
* [Usage](#usage)
|
17
|
+
* [assemble_from](#assemble_from)
|
18
|
+
* [assemble_from_options](#assemble_from_options)
|
19
|
+
* [Before and After Hooks](#before-and-after-hooks)
|
20
|
+
* [Contributing](#contributing)
|
13
21
|
|
14
22
|
|
15
23
|
## Usage
|
16
24
|
|
17
|
-
|
25
|
+
### `assemble_from`
|
26
|
+
|
27
|
+
The `assemble_from` method is the core of Assembler. It's also aliased as
|
28
|
+
`assemble_with`. The basic use case is to pass in required parameters followed
|
29
|
+
by optional parameters (and their defaults). Take this simple example:
|
18
30
|
|
19
31
|
```ruby
|
20
32
|
class IMAPConnection
|
@@ -26,8 +38,8 @@ class IMAPConnection
|
|
26
38
|
end
|
27
39
|
```
|
28
40
|
|
29
|
-
|
30
|
-
For example:
|
41
|
+
This enables you to instantiate your object with either an options hash or via a
|
42
|
+
block. For example:
|
31
43
|
|
32
44
|
```ruby
|
33
45
|
# These two are equivalent:
|
@@ -42,9 +54,14 @@ IMAPConnection.new do |aw|
|
|
42
54
|
aw.hostname = 'imap.example.com'
|
43
55
|
aw.use_ssl = false
|
44
56
|
end
|
57
|
+
|
58
|
+
# Or you can do a combination, if you need to:
|
59
|
+
IMAPConnection.new(hostname: 'imap.example.com') do |aw|
|
60
|
+
aw.use_ssl = false
|
61
|
+
end
|
45
62
|
```
|
46
63
|
|
47
|
-
Note that when
|
64
|
+
Note that when you set `use_ssl` to `false`, the code respects that, rather than
|
48
65
|
over-writing anything falsey with the default. If you don't want that, override
|
49
66
|
it like with `port`, below.
|
50
67
|
|
@@ -69,12 +86,14 @@ end
|
|
69
86
|
These various syntaxes enable some trickery when you're dealing with a world of
|
70
87
|
uncertainty. Let's look at a more complicated example.
|
71
88
|
|
72
|
-
Say
|
89
|
+
Say you want a class that lets us describe an Elastic Load Balancer for Amazon
|
73
90
|
Web Services. There's a lot of complexity in what each of these arguments might
|
74
|
-
be, but the key thing for
|
91
|
+
be, but the key thing for this example is this: If you have `subnets`, you
|
75
92
|
shouldn't have `availability_zones` and if you have `availability_zones`, you
|
76
93
|
shouldn't have `subnets`. And, importantly, you shouldn't send in extraneous
|
77
|
-
keys
|
94
|
+
keys; you need to be able to differentiate callers sending `nil` explicitly from
|
95
|
+
not sending in anything when you make whatever API calls you're going to make to
|
96
|
+
Amazon.
|
78
97
|
|
79
98
|
```ruby
|
80
99
|
class AmazonELB
|
@@ -95,9 +114,9 @@ end
|
|
95
114
|
```
|
96
115
|
|
97
116
|
Now, since there's a lot of complexity in what each of these arguments might be,
|
98
|
-
say
|
99
|
-
|
100
|
-
"standard" ELB.
|
117
|
+
say you've developed some best-practices about what each of them should be. And
|
118
|
+
you want to make it easy to pop off slight variations on what you consider to be
|
119
|
+
a "standard" ELB.
|
101
120
|
|
102
121
|
``` ruby
|
103
122
|
module ELBFactory
|
@@ -108,7 +127,7 @@ module ELBFactory
|
|
108
127
|
elb.security_groups = security_groups
|
109
128
|
elb.instance_ids = instance_ids
|
110
129
|
|
111
|
-
elb.health_check =
|
130
|
+
elb.health_check = HealthCheck.new(
|
112
131
|
target: 'HTTP:8000/',
|
113
132
|
healthy_threshold: '3',
|
114
133
|
unhealthy_threshold: '5',
|
@@ -143,21 +162,125 @@ would look similar to the above, but require you to assign an intermediate
|
|
143
162
|
variable for no semantic benefit.
|
144
163
|
|
145
164
|
|
165
|
+
### `assemble_from_options`
|
166
|
+
|
167
|
+
If you need to do something more complicated than what's provided by
|
168
|
+
`assemble_from`, you can specify per-argument options using
|
169
|
+
`assemble_from_options`. Like `assemble_from`, it's also aliased as
|
170
|
+
`assemble_with_options`.
|
171
|
+
|
172
|
+
Default values can be specified using the `:default` option, and work the same
|
173
|
+
as using hash-syntax with `assemble_from`.
|
174
|
+
|
175
|
+
If you would like to do some type of value coercion you can specify either a
|
176
|
+
symbol or a callable using the `:coerce` option. Symbols will be passed as
|
177
|
+
messages to the input object, and anything that responds to `#call` will be
|
178
|
+
called with the input object as an argument.
|
179
|
+
|
180
|
+
If you need to accept aliased key names you can use the `:aliases` option to
|
181
|
+
specify a list of keys. Aliases only apply to input processing; instance
|
182
|
+
variables aren't set and accessors aren't be provided.
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
class IMAPConnection
|
186
|
+
extend Assembler
|
187
|
+
|
188
|
+
# Here we want to assign an IP address so we only do DNS lookup once.
|
189
|
+
assemble_from_options :hostname, coerce: ->(h) { Resolv.getaddress(h) }
|
190
|
+
|
191
|
+
# Defaults must be specified explicitly; arguments with no default are required.
|
192
|
+
assemble_from_options :use_ssl, default: false
|
193
|
+
|
194
|
+
# We'll accept values named 'port' or 'host_port' (but we'll only assign '@port').
|
195
|
+
# Symbols can also be passed for coercions.
|
196
|
+
assemble_from_options :port, default: nil, coerce: :to_i, aliases: [:host_port]
|
197
|
+
end
|
198
|
+
|
199
|
+
instance = IMAPConnection.new(hostname: 'localhost') do |b|
|
200
|
+
puts b.hostname # => '127.0.0.0' - i.e. the accessor returns the coerced value.
|
201
|
+
puts b.use_ssl # => false - i.e. the accessor returns the default value if none is specified.
|
202
|
+
b.port = '100' # Will be coerced to the integer 100.
|
203
|
+
end
|
204
|
+
|
205
|
+
instance.host_port # => MethodMissing error - accessors aren't defined for aliases.
|
206
|
+
```
|
207
|
+
|
208
|
+
### Before and After Hooks
|
209
|
+
|
210
|
+
In some cases, you might need to take care of some extra things during object
|
211
|
+
initialization. One simple case would be if you're inheriting from another class
|
212
|
+
and need to call `super` to make sure it initializes correctly. Enter
|
213
|
+
`before_assembly` and `after_assembly`.
|
214
|
+
|
215
|
+
They both take a block and that block gets evaluated in the scope of your
|
216
|
+
instance before or after the rest of Assembler's initializer runs. This means
|
217
|
+
instance variables and private methods are available to you and that `self` is
|
218
|
+
the object being created. Nothing is `yield`ed to the blocks.
|
219
|
+
|
220
|
+
If you don't need to pass arguments or always pass the same arguments, you could
|
221
|
+
do something like this:
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
class Professor < Employee
|
225
|
+
extend Assembler
|
226
|
+
|
227
|
+
before_assembly do
|
228
|
+
super('teaching')
|
229
|
+
end
|
230
|
+
end
|
231
|
+
```
|
232
|
+
|
233
|
+
If, however, you need to react to or interact with options that are passed in,
|
234
|
+
you can do something like this:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
class Professor < Employee
|
238
|
+
extend Assembler
|
239
|
+
|
240
|
+
attr_reader :title
|
241
|
+
|
242
|
+
assemble_with :department_name, :degree_subject
|
243
|
+
after_assembly do
|
244
|
+
@title = "PhD of #{degree_subject}, #{department_name}"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
```
|
248
|
+
|
249
|
+
If you call these methods more than once, each block will be run in the order
|
250
|
+
declared. The most common case would be a child class adding more before or
|
251
|
+
after functionality to that declared by a parent.
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
class Employee
|
255
|
+
extend Assembler
|
256
|
+
|
257
|
+
assemble_with :manager_id, department_name: nil
|
258
|
+
|
259
|
+
attr_reader :manager
|
260
|
+
|
261
|
+
after_assembly do
|
262
|
+
@manager = Manager.find(manager_id)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
class Professor < Employee
|
267
|
+
assemble_with :department_chair
|
268
|
+
|
269
|
+
attr_reader :people_answerable_to
|
270
|
+
|
271
|
+
after_assembly do
|
272
|
+
@people_answerable_to = [manager, department_name]
|
273
|
+
end
|
274
|
+
end
|
275
|
+
```
|
276
|
+
|
277
|
+
|
146
278
|
## Contributing
|
147
279
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
1. Turn the Issue into a Pull Request. There are several ways to do this, but
|
156
|
-
[hub](https://github.com/defunkt/hub) is probably the easiest.
|
157
|
-
1. Make sure your Pull Request includes tests.
|
158
|
-
1. Bonus points if your Pull Request updates `CHANGES.md` to include a summary
|
159
|
-
of your changes and your name like the other entries. If the last entry is
|
160
|
-
the last release, add a new `## Unreleased` heading at the top.
|
161
|
-
|
162
|
-
If you don't know how to fix something, even just a Pull Request that includes a
|
163
|
-
failing test can be helpful. If in doubt, make an Issue to discuss.
|
280
|
+
If you'd like to contribute, please see the [contribution guidelines](CONTRIBUTING.md).
|
281
|
+
|
282
|
+
|
283
|
+
## Releasing
|
284
|
+
|
285
|
+
Maintainers: Please make sure to follow the [release steps](RELEASING.md) when
|
286
|
+
it's time to cut a new release.
|
data/RELEASING.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Releasing
|
2
|
+
|
3
|
+
If you want to push a new version of this gem, do this:
|
4
|
+
|
5
|
+
1. Ideally, every Pull Request should already have included an addition to the
|
6
|
+
`CHANGES.md` file summarizing the changes and crediting the author(s). It
|
7
|
+
doesn't hurt to review this to see if anything needs adding.
|
8
|
+
1. Commit any changes you make.
|
9
|
+
1. Go into version.rb and bump the version number
|
10
|
+
[as appopriate](http://semver.org/).
|
11
|
+
1. Go into CHANGES.md and change the "Unlreleased" heading to match the new
|
12
|
+
version number.
|
13
|
+
1. Commit these changes with a message like, "Minor version bump," or similar.
|
14
|
+
1. Run `rake release`.
|
15
|
+
1. High five someone nearby.
|
data/lib/assembler.rb
CHANGED
@@ -2,19 +2,27 @@ require "assembler/version"
|
|
2
2
|
require "assembler/initializer"
|
3
3
|
|
4
4
|
module Assembler
|
5
|
-
|
5
|
+
def assemble_from_options(*args)
|
6
|
+
assembly_setup do
|
7
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
8
|
+
param_names = args
|
6
9
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
param_names.each do |param_name|
|
11
|
+
param = Parameter.new(param_name, options)
|
12
|
+
assembly_parameters_hash[param.name] = param
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
alias_method :assemble_with_options, :assemble_from_options
|
12
17
|
|
13
|
-
|
14
|
-
|
18
|
+
def assemble_from(*args)
|
19
|
+
assembly_setup do
|
20
|
+
optional = args.last.is_a?(Hash) ? args.pop : {}
|
21
|
+
required = args
|
15
22
|
|
16
|
-
|
17
|
-
|
23
|
+
optional.each { |k,v| assemble_from_options(k, default: v) }
|
24
|
+
required.each { |k| assemble_from_options(k) }
|
25
|
+
end
|
18
26
|
end
|
19
27
|
alias_method :assemble_with, :assemble_from
|
20
28
|
|
@@ -24,15 +32,39 @@ module Assembler
|
|
24
32
|
assemble_from(*args)
|
25
33
|
end
|
26
34
|
|
27
|
-
def
|
28
|
-
|
35
|
+
def before_assembly(&block)
|
36
|
+
assembly_setup do
|
37
|
+
before_assembly_blocks << block
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def before_assembly_blocks
|
42
|
+
@before_assembly_blocks ||= []
|
29
43
|
end
|
30
44
|
|
31
|
-
def
|
32
|
-
|
45
|
+
def after_assembly(&block)
|
46
|
+
assembly_setup do
|
47
|
+
after_assembly_blocks << block
|
48
|
+
end
|
33
49
|
end
|
34
50
|
|
35
|
-
def
|
36
|
-
|
51
|
+
def after_assembly_blocks
|
52
|
+
@after_assembly_blocks ||= []
|
53
|
+
end
|
54
|
+
|
55
|
+
def assembly_parameters
|
56
|
+
assembly_parameters_hash.values
|
57
|
+
end
|
58
|
+
|
59
|
+
def assembly_parameters_hash
|
60
|
+
@assembly_parameters_hash ||= {}
|
61
|
+
end
|
62
|
+
|
63
|
+
def assembly_setup
|
64
|
+
yield
|
65
|
+
ensure
|
66
|
+
include Assembler::Initializer
|
67
|
+
attr_reader *assembly_parameters.map(&:name)
|
68
|
+
private *assembly_parameters.map(&:name)
|
37
69
|
end
|
38
70
|
end
|
data/lib/assembler/builder.rb
CHANGED
@@ -1,27 +1,30 @@
|
|
1
1
|
module Assembler
|
2
2
|
class Builder
|
3
|
-
def initialize(
|
4
|
-
@
|
3
|
+
def initialize(parameters_hash, options = {})
|
4
|
+
@options = options
|
5
|
+
@parameters_hash = parameters_hash
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
parameters_hash.each do |parameter_name, parameter|
|
8
|
+
parameter.name_and_aliases.each do |name_or_alias|
|
9
|
+
self.singleton_class.class_eval(<<-RUBY)
|
10
|
+
def #{name_or_alias}=(value)
|
11
|
+
options[:#{parameter_name.to_sym}] = value
|
12
|
+
end
|
13
|
+
|
14
|
+
def #{name_or_alias}
|
15
|
+
parameters_hash[:#{parameter_name}].value_from(options)
|
16
|
+
end
|
17
|
+
RUBY
|
18
|
+
end
|
12
19
|
end
|
13
20
|
end
|
14
21
|
|
15
22
|
def to_h
|
16
|
-
|
23
|
+
options
|
17
24
|
end
|
18
25
|
|
19
26
|
private
|
20
27
|
|
21
|
-
attr_reader :
|
22
|
-
|
23
|
-
def parameters
|
24
|
-
@parameters ||= {}
|
25
|
-
end
|
28
|
+
attr_reader :parameters_hash, :options
|
26
29
|
end
|
27
30
|
end
|
@@ -1,39 +1,36 @@
|
|
1
1
|
require "assembler/builder"
|
2
|
-
require "assembler/
|
2
|
+
require "assembler/parameter"
|
3
3
|
|
4
4
|
module Assembler
|
5
5
|
module Initializer
|
6
6
|
def initialize(options={})
|
7
|
-
|
7
|
+
if self.class.before_assembly_blocks.any?
|
8
|
+
self.class.before_assembly_blocks.each do |block|
|
9
|
+
instance_eval(&block)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
builder = Assembler::Builder.new(self.class.assembly_parameters_hash, options)
|
8
14
|
|
9
15
|
yield builder if block_given?
|
10
16
|
|
11
|
-
|
17
|
+
missing_required_parameters = []
|
12
18
|
|
13
|
-
|
19
|
+
self.class.assembly_parameters.each do |param|
|
20
|
+
if_required_and_missing = -> { missing_required_parameters << param.name }
|
14
21
|
|
15
|
-
|
16
|
-
remember_value_or(param_name) { missing_required_params << param_name }
|
17
|
-
end
|
22
|
+
value = param.value_from(builder.to_h, &if_required_and_missing)
|
18
23
|
|
19
|
-
|
20
|
-
remember_value_or(param_name) { default_value }
|
24
|
+
instance_variable_set(:"@#{param.name}", value)
|
21
25
|
end
|
22
26
|
|
23
|
-
raise(ArgumentError, "missing keywords: #{
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
+
raise(ArgumentError, "missing keywords: #{missing_required_parameters.join(', ')}") if missing_required_parameters.any?
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
instance_variable_set(
|
32
|
-
:"@#{param_name}",
|
33
|
-
full_options.fetch(param_name) do
|
34
|
-
block.call
|
29
|
+
if self.class.after_assembly_blocks.any?
|
30
|
+
self.class.after_assembly_blocks.each do |block|
|
31
|
+
instance_eval(&block)
|
35
32
|
end
|
36
|
-
|
33
|
+
end
|
37
34
|
end
|
38
35
|
end
|
39
36
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Assembler
|
2
|
+
class Parameter
|
3
|
+
attr_reader :name
|
4
|
+
|
5
|
+
def initialize(name, options = {})
|
6
|
+
@name = name.to_sym
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def name_and_aliases
|
11
|
+
@name_and_aliases ||= [name] + aliases.map(&:to_sym)
|
12
|
+
end
|
13
|
+
|
14
|
+
def value_from(hash, &if_required_and_missing)
|
15
|
+
@memoized_value_from ||= {}
|
16
|
+
|
17
|
+
# NOTE: Jruby's Hash#hash implementation is BS:
|
18
|
+
# {:foo => :foo}.hash => 1
|
19
|
+
# {:bar => :bar}.hash => 1
|
20
|
+
# {:foo => :foo}.to_a.hash => 806614226
|
21
|
+
# {:bar => :bar}.to_a.hash => 3120054328
|
22
|
+
# Go figure...
|
23
|
+
memoization_key = hash.to_a.hash
|
24
|
+
|
25
|
+
return @memoized_value_from[memoization_key] if @memoized_value_from[memoization_key]
|
26
|
+
|
27
|
+
first_key = key_names.find { |name_or_alias| hash.has_key?(name_or_alias) }
|
28
|
+
|
29
|
+
raw_value = hash.fetch(first_key) do
|
30
|
+
options.fetch(:default) do
|
31
|
+
if_required_and_missing.call unless if_required_and_missing.nil?
|
32
|
+
|
33
|
+
# Returning here so we don't call coerce_value(nil)
|
34
|
+
return
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
@memoized_value_from[memoization_key] = coerce_value(raw_value)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :options
|
44
|
+
|
45
|
+
def key_names
|
46
|
+
name_and_aliases.flat_map do |name_or_alias|
|
47
|
+
[name_or_alias.to_sym, name_or_alias.to_s]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def has_default?
|
52
|
+
options.has_key?(:default)
|
53
|
+
end
|
54
|
+
|
55
|
+
def default
|
56
|
+
options[:default]
|
57
|
+
end
|
58
|
+
|
59
|
+
def coercion
|
60
|
+
options[:coerce]
|
61
|
+
end
|
62
|
+
|
63
|
+
def coerce_value(value)
|
64
|
+
if !coercion
|
65
|
+
value
|
66
|
+
elsif coercion.kind_of?(Symbol)
|
67
|
+
value.send(coercion)
|
68
|
+
elsif coercion.respond_to?(:call)
|
69
|
+
coercion.call(value)
|
70
|
+
else
|
71
|
+
raise ArgumentError, "don't know how to handle coerce value #{coercion}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def aliases
|
76
|
+
@aliases ||= Array(options[:aliases]) + Array(options[:alias])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/assembler/version.rb
CHANGED
@@ -0,0 +1,91 @@
|
|
1
|
+
describe Assembler do
|
2
|
+
describe "#after_assembly" do
|
3
|
+
context "with no other assembler helpers called" do
|
4
|
+
let(:klass) do
|
5
|
+
Class.new do
|
6
|
+
extend Assembler
|
7
|
+
|
8
|
+
after_assembly do
|
9
|
+
@after = true
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :after
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
subject { klass.new }
|
17
|
+
|
18
|
+
it "calls the after block" do
|
19
|
+
expect(subject.after).to be_true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "with a more complex declaration" do
|
24
|
+
let(:klass) do
|
25
|
+
Class.new do
|
26
|
+
extend Assembler
|
27
|
+
|
28
|
+
assemble_with middle: 'middle'
|
29
|
+
|
30
|
+
after_assembly do
|
31
|
+
a_private_method
|
32
|
+
@after = true
|
33
|
+
@middle = 'after'
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :after, :middle
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def a_private_method
|
41
|
+
"Shhhhhhh! it's a secret!"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
subject do
|
47
|
+
klass.new
|
48
|
+
end
|
49
|
+
|
50
|
+
it "calls the after block" do
|
51
|
+
expect(subject.after).to be_true
|
52
|
+
end
|
53
|
+
|
54
|
+
it "calls the block after running the rest of the initializer" do
|
55
|
+
expect(subject.middle).to eq('after')
|
56
|
+
end
|
57
|
+
|
58
|
+
it "allows access to private methods" do
|
59
|
+
expect { klass.new }.to_not raise_error
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "with two after hooks defined" do
|
64
|
+
let(:klass) do
|
65
|
+
Class.new do
|
66
|
+
extend Assembler
|
67
|
+
|
68
|
+
after_assembly do
|
69
|
+
hooks << :first
|
70
|
+
end
|
71
|
+
|
72
|
+
after_assembly do
|
73
|
+
hooks << :second
|
74
|
+
end
|
75
|
+
|
76
|
+
def hooks
|
77
|
+
@hooks ||= []
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
subject do
|
83
|
+
klass.new
|
84
|
+
end
|
85
|
+
|
86
|
+
it "calls the after hooks in order of declaration" do
|
87
|
+
expect(subject.hooks).to eq([:first, :second])
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,384 @@
|
|
1
|
+
describe Assembler do
|
2
|
+
describe "#assemble_from_options" do
|
3
|
+
context "without parameters" do
|
4
|
+
subject do
|
5
|
+
Class.new do
|
6
|
+
extend Assembler
|
7
|
+
|
8
|
+
assemble_from_options
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "throws away all input parameters (method arguments)" do
|
13
|
+
built_object = subject.new(foo: 'foo', bar: 'bar')
|
14
|
+
|
15
|
+
expect(built_object.instance_variables).to_not include(:@foo, :@bar)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "doesn't have methods on the builder object" do
|
19
|
+
subject.new do |builder|
|
20
|
+
expect(builder).to_not respond_to(:foo=)
|
21
|
+
expect(builder).to_not respond_to(:foo)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "with no default parameter" do
|
27
|
+
subject do
|
28
|
+
Class.new do
|
29
|
+
extend Assembler
|
30
|
+
|
31
|
+
assemble_from_options :foo, :bar
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "barfs from missing required parameters" do
|
36
|
+
expect { subject.new }.to raise_error(ArgumentError)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "holds onto the parameters (method arguments)" do
|
40
|
+
built_object = subject.new(foo: 'baz', bar: 'qux')
|
41
|
+
|
42
|
+
expect(built_object.instance_variable_get(:@foo)).to eq('baz')
|
43
|
+
expect(built_object.instance_variable_get(:@bar)).to eq('qux')
|
44
|
+
end
|
45
|
+
|
46
|
+
it "holds onto the parameters (block)" do
|
47
|
+
built_object = subject.new do |builder|
|
48
|
+
builder.foo = 'baz'
|
49
|
+
builder.bar = 'qux'
|
50
|
+
end
|
51
|
+
|
52
|
+
expect(built_object.instance_variable_get(:@foo)).to eq('baz')
|
53
|
+
expect(built_object.instance_variable_get(:@bar)).to eq('qux')
|
54
|
+
end
|
55
|
+
|
56
|
+
it "ignores un-named parameters in method arguments" do
|
57
|
+
built_object = subject.new(foo: 'foo', bar: 'bar', baz: 'baz')
|
58
|
+
|
59
|
+
expect(built_object.instance_variables).to_not include(:@baz)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "doesn't create builder methods for un-named parameters" do
|
63
|
+
expect {
|
64
|
+
subject.new do |builder|
|
65
|
+
builder.baz = 'baz'
|
66
|
+
end
|
67
|
+
}.to raise_error(NoMethodError)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "provides accessors in the builder object" do
|
71
|
+
subject.new do |builder|
|
72
|
+
builder.foo = :new_foo
|
73
|
+
builder.bar = :new_bar
|
74
|
+
|
75
|
+
expect(builder.foo).to eq(:new_foo)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "returns nil for un-assigned parameters" do
|
80
|
+
subject.new do |builder|
|
81
|
+
expect(builder.foo).to be_nil
|
82
|
+
|
83
|
+
builder.foo = :new_foo
|
84
|
+
builder.bar = :new_bar
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it "incorporates constructor args in the builder accessors" do
|
89
|
+
subject.new(foo: 'new_foo', bar: 'new_bar') do |builder|
|
90
|
+
expect(builder.foo).to eq('new_foo')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "with default parameter" do
|
96
|
+
subject do
|
97
|
+
Class.new do
|
98
|
+
extend Assembler
|
99
|
+
|
100
|
+
assemble_from_options :foo, :bar, default: 'default'
|
101
|
+
assemble_from_options :baz, :qux, default: nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
it "uses default values for missing parameters (method arguments)" do
|
106
|
+
built_object = subject.new
|
107
|
+
|
108
|
+
expect(built_object.instance_variable_get(:@foo)).to eq('default')
|
109
|
+
expect(built_object.instance_variable_get(:@bar)).to eq('default')
|
110
|
+
expect(built_object.instance_variable_get(:@baz)).to eq(nil)
|
111
|
+
expect(built_object.instance_variable_get(:@qux)).to eq(nil)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "uses default values for missing parameters (block)" do
|
115
|
+
built_object = subject.new do |builder|
|
116
|
+
end
|
117
|
+
|
118
|
+
expect(built_object.instance_variable_get(:@foo)).to eq('default')
|
119
|
+
expect(built_object.instance_variable_get(:@bar)).to eq('default')
|
120
|
+
expect(built_object.instance_variable_get(:@baz)).to eq(nil)
|
121
|
+
expect(built_object.instance_variable_get(:@qux)).to eq(nil)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "holds onto the parameters (method arguments)" do
|
125
|
+
built_object = subject.new(foo: 'foo', bar: 'bar', baz: 'baz', qux: 'qux')
|
126
|
+
|
127
|
+
expect(built_object.instance_variable_get(:@foo)).to eq('foo')
|
128
|
+
expect(built_object.instance_variable_get(:@bar)).to eq('bar')
|
129
|
+
expect(built_object.instance_variable_get(:@baz)).to eq('baz')
|
130
|
+
expect(built_object.instance_variable_get(:@qux)).to eq('qux')
|
131
|
+
end
|
132
|
+
|
133
|
+
it "holds onto the parameters (block)" do
|
134
|
+
built_object = subject.new do |builder|
|
135
|
+
builder.foo = 'foo'
|
136
|
+
builder.bar = 'bar'
|
137
|
+
builder.baz = 'baz'
|
138
|
+
builder.qux = 'qux'
|
139
|
+
end
|
140
|
+
|
141
|
+
expect(built_object.instance_variable_get(:@foo)).to eq('foo')
|
142
|
+
expect(built_object.instance_variable_get(:@bar)).to eq('bar')
|
143
|
+
expect(built_object.instance_variable_get(:@baz)).to eq('baz')
|
144
|
+
expect(built_object.instance_variable_get(:@qux)).to eq('qux')
|
145
|
+
end
|
146
|
+
|
147
|
+
it "ignores un-named parameters in method arguments" do
|
148
|
+
built_object = subject.new(nope: 'nope')
|
149
|
+
|
150
|
+
expect(built_object.instance_variables).to_not include(:@nope)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "doesn't create builder methods for un-named parameters" do
|
154
|
+
expect {
|
155
|
+
subject.new do |builder|
|
156
|
+
builder.nope = 'nope'
|
157
|
+
end
|
158
|
+
}.to raise_error(NoMethodError)
|
159
|
+
end
|
160
|
+
|
161
|
+
it "provides accessors in the builder object" do
|
162
|
+
subject.new do |builder|
|
163
|
+
builder.foo = :new_foo
|
164
|
+
expect(builder.foo).to eq(:new_foo)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
it "pre-fills default values in the builder accessors" do
|
169
|
+
subject.new do |builder|
|
170
|
+
expect(builder.foo).to eq('default')
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
it "incorporates constructor args in the builder accessors" do
|
175
|
+
subject.new(foo: 'new_foo') do |builder|
|
176
|
+
expect(builder.foo).to eq('new_foo')
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
context "with symbol coerce parameter" do
|
182
|
+
subject do
|
183
|
+
Class.new do
|
184
|
+
extend Assembler
|
185
|
+
|
186
|
+
assemble_from_options :foo, coerce: :to_set
|
187
|
+
assemble_from_options :bar, coerce: :to_set, default: [:coerced]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
let(:argument) { double('argument', to_set: Set.new([:coerced])) }
|
192
|
+
|
193
|
+
it "sends parameter value to constructor argument (method arguments)" do
|
194
|
+
expect(argument).to receive(:to_set).and_return(Set.new([:coerced]))
|
195
|
+
subject.new(foo: argument)
|
196
|
+
end
|
197
|
+
|
198
|
+
it "assigns the output of the coercion (method arguments)" do
|
199
|
+
built_object = subject.new(foo: argument)
|
200
|
+
|
201
|
+
expect(built_object.instance_variable_get(:@foo)).to eq(Set.new([:coerced]))
|
202
|
+
end
|
203
|
+
|
204
|
+
it "sends parameter value to constructor argument (block)" do
|
205
|
+
expect(argument).to receive(:to_set).and_return(Set.new([:coerced]))
|
206
|
+
subject.new { |b| b.foo = argument }
|
207
|
+
end
|
208
|
+
|
209
|
+
it "assigns the output of the coercion (block)" do
|
210
|
+
built_object = subject.new { |b| b.foo = argument }
|
211
|
+
|
212
|
+
expect(built_object.instance_variable_get(:@foo)).to eq(Set.new([:coerced]))
|
213
|
+
end
|
214
|
+
|
215
|
+
it "coerces default values in the builder accessor" do
|
216
|
+
subject.new(foo: [:foo]) do |builder|
|
217
|
+
expect(builder.bar).to eq(Set.new([:coerced]))
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
it "coerces constructor args in the builder accessors" do
|
222
|
+
subject.new(foo: [:not_default]) do |builder|
|
223
|
+
expect(builder.foo).to eq(Set.new([:not_default]))
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
it "coerces assigned values in the buidler accessors" do
|
228
|
+
subject.new do |builder|
|
229
|
+
builder.foo = [:not_default]
|
230
|
+
expect(builder.foo).to eq(Set.new([:not_default]))
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
context "with callable coerce parameter" do
|
236
|
+
subject do
|
237
|
+
callable = double('callable')
|
238
|
+
allow(callable).to receive(:call) do |s|
|
239
|
+
[s, :called]
|
240
|
+
end
|
241
|
+
|
242
|
+
Class.new do
|
243
|
+
extend Assembler
|
244
|
+
|
245
|
+
assemble_from_options :lambda, default: nil, coerce: ->(s) { [s, :called] }
|
246
|
+
assemble_from_options :proc, default: nil, coerce: Proc.new { |s| [s, :called] }
|
247
|
+
assemble_from_options :callable, default: nil, coerce: callable
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
let(:argument) { double('argument') }
|
252
|
+
|
253
|
+
it "assigns the output of the coercion (method arguments)" do
|
254
|
+
expect(subject.new(:lambda => :original).instance_variable_get(:@lambda)).to eq([:original, :called])
|
255
|
+
expect(subject.new(:proc => :original).instance_variable_get(:@proc)).to eq([:original, :called])
|
256
|
+
expect(subject.new(:callable => :original).instance_variable_get(:@callable)).to eq([:original, :called])
|
257
|
+
end
|
258
|
+
|
259
|
+
it "assigns the output of the coercion (block)" do
|
260
|
+
expect(subject.new {|b| b.lambda = :original}.instance_variable_get(:@lambda)).to eq([:original, :called])
|
261
|
+
expect(subject.new {|b| b.proc = :original}.instance_variable_get(:@proc)).to eq([:original, :called])
|
262
|
+
expect(subject.new {|b| b.callable = :original}.instance_variable_get(:@callable)).to eq([:original, :called])
|
263
|
+
end
|
264
|
+
|
265
|
+
it "coerces default values in the builder accessor" do
|
266
|
+
subject.new do |builder|
|
267
|
+
expect(builder.lambda).to eq([nil, :called])
|
268
|
+
expect(builder.proc).to eq([nil, :called])
|
269
|
+
expect(builder.callable).to eq([nil, :called])
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
it "coerces constructor args in the builder accessors" do
|
274
|
+
subject.new(:lambda => :original, :proc => :original, :callable => :original) do |builder|
|
275
|
+
expect(builder.lambda).to eq([:original, :called])
|
276
|
+
expect(builder.proc).to eq([:original, :called])
|
277
|
+
expect(builder.callable).to eq([:original, :called])
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
it "coerces assigned values in the buidler accessors" do
|
282
|
+
subject.new do |builder|
|
283
|
+
builder.lambda = :original
|
284
|
+
builder.proc = :original
|
285
|
+
builder.callable = :original
|
286
|
+
|
287
|
+
expect(builder.lambda).to eq([:original, :called])
|
288
|
+
expect(builder.proc).to eq([:original, :called])
|
289
|
+
expect(builder.callable).to eq([:original, :called])
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
context "with singular alias parameter" do
|
295
|
+
subject do
|
296
|
+
Class.new do
|
297
|
+
extend Assembler
|
298
|
+
|
299
|
+
assemble_from_options :foo, :alias => :bar
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
it "creates an alias" do
|
304
|
+
expect(subject.new(bar: :bar).instance_variable_get(:@foo)).to eq(:bar)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
context "with enumerable alias parameter" do
|
309
|
+
subject do
|
310
|
+
Class.new do
|
311
|
+
extend Assembler
|
312
|
+
|
313
|
+
assemble_from_options :foo, :alias => [:bar, :baz]
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
it "creates aliases" do
|
318
|
+
expect(subject.new(bar: :bar).instance_variable_get(:@foo)).to eq(:bar)
|
319
|
+
expect(subject.new(baz: :baz).instance_variable_get(:@foo)).to eq(:baz)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
context "with singular aliases parameter" do
|
324
|
+
subject do
|
325
|
+
Class.new do
|
326
|
+
extend Assembler
|
327
|
+
|
328
|
+
assemble_from_options :foo, aliases: :bar
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
it "creates an alias" do
|
333
|
+
expect(subject.new(bar: :bar).instance_variable_get(:@foo)).to eq(:bar)
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
context "with enumerable aliases parameter" do
|
338
|
+
subject do
|
339
|
+
Class.new do
|
340
|
+
extend Assembler
|
341
|
+
|
342
|
+
assemble_from_options :foo, aliases: [:bar, :baz]
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
it "creates aliases" do
|
347
|
+
expect(subject.new(bar: :bar).instance_variable_get(:@foo)).to eq(:bar)
|
348
|
+
expect(subject.new(baz: :baz).instance_variable_get(:@foo)).to eq(:baz)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
context "when called more than once for the same key" do
|
353
|
+
subject do
|
354
|
+
Class.new do
|
355
|
+
extend Assembler
|
356
|
+
|
357
|
+
assemble_from_options :foo
|
358
|
+
assemble_from_options :foo, default: :foo, coerce: :to_sym, aliases: [:bar]
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
it "re-writes default" do
|
363
|
+
expect(subject.new.instance_variable_get(:@foo)).to eq(:foo)
|
364
|
+
end
|
365
|
+
|
366
|
+
it "re-writes aliases (method)" do
|
367
|
+
expect(subject.new(bar: :bar).instance_variable_get(:@foo)).to eq(:bar)
|
368
|
+
end
|
369
|
+
|
370
|
+
it "re-writes aliases (block)" do
|
371
|
+
expect(subject.new { |s| s.bar = :bar}.instance_variable_get(:@foo)).to eq(:bar)
|
372
|
+
end
|
373
|
+
|
374
|
+
it "re-writes coercions (method)" do
|
375
|
+
expect(subject.new(foo: 'foo').instance_variable_get(:@foo)).to eq(:foo)
|
376
|
+
end
|
377
|
+
|
378
|
+
it "re-writes coercions (block)" do
|
379
|
+
expect(subject.new { |s| s.foo = 'foo'}.instance_variable_get(:@foo)).to eq(:foo)
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
@@ -1,6 +1,3 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'assembler'
|
3
|
-
|
4
1
|
describe Assembler do
|
5
2
|
describe "#assemble_from" do
|
6
3
|
context "without parameters" do
|
@@ -82,6 +79,25 @@ describe Assembler do
|
|
82
79
|
end
|
83
80
|
}.to raise_error(NoMethodError)
|
84
81
|
end
|
82
|
+
|
83
|
+
it "provides accessors in the builder object" do
|
84
|
+
subject.new do |builder|
|
85
|
+
builder.foo = :new_foo
|
86
|
+
expect(builder.foo).to eq(:new_foo)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
it "pre-fills default values in the builder accessors" do
|
91
|
+
subject.new(foo: 'foo') do |builder|
|
92
|
+
expect(builder.bar).to eq('bar')
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
it "incorporates contructor args in the builder accessors" do
|
97
|
+
subject.new(foo: 'foo', bar: 'new_bar') do |builder|
|
98
|
+
expect(builder.bar).to eq('new_bar')
|
99
|
+
end
|
100
|
+
end
|
85
101
|
end
|
86
102
|
|
87
103
|
context "when called more than once" do
|
@@ -0,0 +1,80 @@
|
|
1
|
+
describe Assembler do
|
2
|
+
describe "#before_assembly" do
|
3
|
+
context "with no other assembler helpers called" do
|
4
|
+
let(:klass) do
|
5
|
+
Class.new do
|
6
|
+
extend Assembler
|
7
|
+
|
8
|
+
before_assembly do
|
9
|
+
@before = true
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :before
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
subject { klass.new }
|
17
|
+
|
18
|
+
it "calls the before block" do
|
19
|
+
expect(subject.before).to be_true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "with a more complex declaration" do
|
24
|
+
let(:klass) do
|
25
|
+
Class.new do
|
26
|
+
extend Assembler
|
27
|
+
|
28
|
+
assemble_with :middle
|
29
|
+
|
30
|
+
before_assembly do
|
31
|
+
@before = true
|
32
|
+
@middle = 'before'
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :before, :middle
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
subject do
|
40
|
+
klass.new(middle: 'middle')
|
41
|
+
end
|
42
|
+
|
43
|
+
it "calls the before block" do
|
44
|
+
expect(subject.before).to be_true
|
45
|
+
end
|
46
|
+
|
47
|
+
it "calls the block before running the rest of the initializer" do
|
48
|
+
expect(subject.middle).to eq('middle')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "with two before hooks defined" do
|
53
|
+
let(:klass) do
|
54
|
+
Class.new do
|
55
|
+
extend Assembler
|
56
|
+
|
57
|
+
before_assembly do
|
58
|
+
hooks << :first
|
59
|
+
end
|
60
|
+
|
61
|
+
before_assembly do
|
62
|
+
hooks << :second
|
63
|
+
end
|
64
|
+
|
65
|
+
def hooks
|
66
|
+
@hooks ||= []
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
subject do
|
72
|
+
klass.new
|
73
|
+
end
|
74
|
+
|
75
|
+
it "calls the before hooks in order of declaration" do
|
76
|
+
expect(subject.hooks).to eq([:first, :second])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: assembler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Hamill
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-04-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -79,17 +79,22 @@ files:
|
|
79
79
|
- ".rspec"
|
80
80
|
- ".travis.yml"
|
81
81
|
- CHANGES.md
|
82
|
+
- CONTRIBUTING.md
|
82
83
|
- Gemfile
|
83
84
|
- LICENSE.txt
|
84
85
|
- README.md
|
86
|
+
- RELEASING.md
|
85
87
|
- Rakefile
|
86
88
|
- assembler.gemspec
|
87
89
|
- lib/assembler.rb
|
88
90
|
- lib/assembler/builder.rb
|
89
91
|
- lib/assembler/initializer.rb
|
90
|
-
- lib/assembler/
|
92
|
+
- lib/assembler/parameter.rb
|
91
93
|
- lib/assembler/version.rb
|
92
|
-
- spec/
|
94
|
+
- spec/after_assembly_spec.rb
|
95
|
+
- spec/assemble_from_options_spec.rb
|
96
|
+
- spec/assemble_from_spec.rb
|
97
|
+
- spec/before_assembly_spec.rb
|
93
98
|
- spec/spec_helper.rb
|
94
99
|
homepage: https://github.com/benhamill/assembler#readme
|
95
100
|
licenses:
|
@@ -116,5 +121,8 @@ signing_key:
|
|
116
121
|
specification_version: 4
|
117
122
|
summary: Block-based initializers for your objects.
|
118
123
|
test_files:
|
119
|
-
- spec/
|
124
|
+
- spec/after_assembly_spec.rb
|
125
|
+
- spec/assemble_from_options_spec.rb
|
126
|
+
- spec/assemble_from_spec.rb
|
127
|
+
- spec/before_assembly_spec.rb
|
120
128
|
- spec/spec_helper.rb
|
data/lib/assembler/parameters.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
module Assembler
|
2
|
-
class Parameters
|
3
|
-
def initialize(params={})
|
4
|
-
@params = params
|
5
|
-
end
|
6
|
-
|
7
|
-
def fetch(key, &block)
|
8
|
-
params.fetch(key.to_sym) do
|
9
|
-
params.fetch(key.to_s) do
|
10
|
-
block.call
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
attr_reader :params
|
18
|
-
end
|
19
|
-
end
|