uber 0.0.15 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -0
- data/CHANGES.md +8 -1
- data/Gemfile +1 -1
- data/README.md +98 -90
- data/lib/uber/builder.rb +22 -78
- data/lib/uber/inheritable_attr.rb +2 -2
- data/lib/uber/option.rb +16 -0
- data/lib/uber/options.rb +10 -23
- data/lib/uber/version.rb +2 -32
- data/test/builder-benchmark.rb +17 -0
- data/test/builder_test.rb +26 -17
- data/test/option_test.rb +47 -0
- data/test/options_test.rb +1 -85
- data/uber.gemspec +4 -4
- metadata +13 -13
- data/lib/uber/uber_version.rb +0 -3
- data/test/version_test.rb +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8e46b835cbac5bfe2249537db1825b8cc2402fd8
|
4
|
+
data.tar.gz: ca87915c839fab9bcdbcb011e88fccfecf6c589f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c570b323ea4e9064d50d1d5c9681ce3071e03368b0d094e15d2ab0de838d148c7ccd0f87bd863364b1754743e8f97a9c6c83dff7c4357da4e9d3696e1e0c985
|
7
|
+
data.tar.gz: 33b188c6b3665ca9d0ec98b79c337ae1e5916a624e279aaaab7f0bc2c2320626df876af2837c9106602584ffeaeb8e52cfcef1a69e0013b57b421fe1d78f1e58
|
data/.travis.yml
CHANGED
data/CHANGES.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# 0.1.0
|
2
|
+
|
3
|
+
* Introduce `Uber::Option` as a replacement for `Uber::Options::Value`. It is still there, but deprecated.
|
4
|
+
* New API for `Uber::Builder`: You now add builders to `Uber::Builder::Builders` and simply call that instance while passing in context and args. This allows very simple reusable builders that can be used anywhere.
|
5
|
+
* `Uber::Options` now uses `Uber::Option`.
|
6
|
+
* Removing `Uber::Version` as this is done nicely by `Gem::Version`.
|
7
|
+
|
1
8
|
# 0.0.15
|
2
9
|
|
3
10
|
* `Value#evaluate` is now `#call`. This will make it easier to introduce Null objects.
|
@@ -55,4 +62,4 @@
|
|
55
62
|
|
56
63
|
# 0.0.2
|
57
64
|
|
58
|
-
* Add `::inheritable_attr`.
|
65
|
+
* Add `::inheritable_attr`.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -12,7 +12,7 @@ Add this line to your application's Gemfile:
|
|
12
12
|
gem 'uber'
|
13
13
|
```
|
14
14
|
|
15
|
-
|
15
|
+
Uber runs with Ruby >= 1.9.3.
|
16
16
|
|
17
17
|
# Inheritable Class Attributes
|
18
18
|
|
@@ -147,39 +147,61 @@ Note how you simply pass an instance of the callable object into the hash instea
|
|
147
147
|
options = Uber::Options.new(tags: Tags.new)
|
148
148
|
```
|
149
149
|
|
150
|
-
##
|
150
|
+
## Option
|
151
151
|
|
152
|
-
|
152
|
+
`Uber::Option` implements the pattern of taking an option, such as a proc, instance method name, or static value, and evaluate it at runtime without knowing the option's implementation.
|
153
|
+
|
154
|
+
Creating `Option` instances via `::[]` usually happens on class-level in DSL methods.
|
153
155
|
|
154
156
|
```ruby
|
155
|
-
options
|
157
|
+
with_proc = Uber::Option[ ->(options) { "proc: #{options.inspect}" } ]
|
158
|
+
with_static = Uber::Option[ "Static value" ]
|
159
|
+
with_method = Uber::Option[ :name_of_method ]
|
160
|
+
|
161
|
+
def name_of_method(options)
|
162
|
+
"method: #{options.inspect}"
|
163
|
+
end
|
156
164
|
```
|
157
165
|
|
158
|
-
|
166
|
+
Use `#call` to evaluate the options at runtime.
|
159
167
|
|
160
|
-
|
168
|
+
```ruby
|
169
|
+
with_proc.(1, 2) #=> "proc: [1, 2]"
|
170
|
+
with_static.(1, 2) #=> "Static value" # arguments are ignored
|
171
|
+
with_method.(self, 1, 2) #=> "method: [1, 2]" # first arg is context
|
172
|
+
```
|
173
|
+
|
174
|
+
It's also possible to evaluate a callable object. It has to be marked with `Uber::Callable` beforehand.
|
161
175
|
|
162
176
|
```ruby
|
163
|
-
|
177
|
+
class MyCallable
|
178
|
+
include Uber::Callable
|
164
179
|
|
165
|
-
|
166
|
-
|
180
|
+
def call(context, *args)
|
181
|
+
"callable: #{args.inspect}, #{context}"
|
182
|
+
end
|
183
|
+
end
|
167
184
|
|
168
|
-
|
185
|
+
with_callable = Uber::Option[ MyCallable.new ]
|
186
|
+
```
|
169
187
|
|
170
|
-
|
188
|
+
The context is passed as first argument.
|
171
189
|
|
172
190
|
```ruby
|
173
|
-
|
174
|
-
value = Uber::Options::Value.new(lambda { volume })
|
175
|
-
|
176
|
-
value.evaluate(nil) #=> 99
|
191
|
+
with_callable.(Object, 1, 2) #=> "callable: [1, 2] Object"
|
177
192
|
```
|
178
193
|
|
194
|
+
You can also make blocks being `instance_exec`ed on the context, giving a unique API to all option types.
|
179
195
|
|
180
|
-
|
196
|
+
```ruby
|
197
|
+
with_instance_proc = Uber::Option[ ->(options) { "proc: #{options.inspect} #{self}" }, instance_exec: true ]
|
198
|
+
```
|
199
|
+
|
200
|
+
The first argument now becomes the context, exactly the way it works for the method and callable type.
|
181
201
|
|
182
|
-
|
202
|
+
```ruby
|
203
|
+
with_instance_proc.(Object, 1, 2) #=> "proc [1, 2] Object"
|
204
|
+
```
|
183
205
|
|
184
206
|
|
185
207
|
# Delegates
|
@@ -216,129 +238,115 @@ song.title #=> "helloween!"
|
|
216
238
|
|
217
239
|
Note how `#title` calls the original title and then downcases the string.
|
218
240
|
|
219
|
-
|
220
241
|
# Builder
|
221
242
|
|
222
|
-
|
223
|
-
the class to conditionally build (sub-)classes based on the incoming parameters.
|
243
|
+
Builders are good for polymorphically creating objects without having to know where that happens. You define a builder with conditions in one class, and that class takes care of creating the actual desired class.
|
224
244
|
|
225
|
-
|
245
|
+
## Declarative Interface
|
246
|
+
|
247
|
+
Include `Uber::Builder` to leverage the `::builds` method for adding builders, and `::build!` to run those builders in a given context and with arbitrary options.
|
226
248
|
|
227
|
-
## Block Syntax
|
228
249
|
|
229
250
|
```ruby
|
230
|
-
|
251
|
+
require "uber/builder"
|
252
|
+
|
253
|
+
class User
|
231
254
|
include Uber::Builder
|
232
255
|
|
233
|
-
builds do |
|
234
|
-
|
256
|
+
builds do |options|
|
257
|
+
Admin if params[:admin]
|
235
258
|
end
|
236
259
|
end
|
237
260
|
|
238
|
-
class
|
261
|
+
class Admin
|
239
262
|
end
|
240
263
|
```
|
241
264
|
|
242
|
-
|
265
|
+
Note that you can call `builds` as many times as you want per class.
|
243
266
|
|
244
|
-
|
245
|
-
class Listener
|
246
|
-
def self.build(params)
|
247
|
-
class_builder.call(params).
|
248
|
-
new(params)
|
249
|
-
end
|
250
|
-
end
|
251
|
-
```
|
252
|
-
|
253
|
-
As you can see, it's still up to you to _instantiate_ the object, the builder only helps you computing the concrete class.
|
267
|
+
Run the builders using `::build!`.
|
254
268
|
|
255
269
|
```ruby
|
256
|
-
|
257
|
-
|
270
|
+
User.build!(User, {}) #=> User
|
271
|
+
User.build!(User, { admin: true }) #=> Admin
|
258
272
|
```
|
273
|
+
The first argument is the context in which the builder blocks will be executed. This is also the default return value if all builders returned a falsey value.
|
259
274
|
|
260
|
-
|
261
|
-
|
262
|
-
Setting up builders using the proc syntax allows to call `return` in the block. This is our preferred way to define builders.
|
263
|
-
|
264
|
-
```ruby
|
265
|
-
build ->(params) do
|
266
|
-
return SignedIn if params[:current_user]
|
267
|
-
return Admin if params[:admin]
|
268
|
-
Default
|
269
|
-
end
|
270
|
-
```
|
275
|
+
All following arguments will be passed straight through to the procs.
|
271
276
|
|
272
|
-
|
277
|
+
Your API should communicate `User` as the only public class, since the builder hides details about computing the concrete class.
|
273
278
|
|
274
|
-
|
279
|
+
### Builder: Procs
|
275
280
|
|
276
|
-
You
|
281
|
+
You may also use procs instead of blocks.
|
277
282
|
|
278
283
|
```ruby
|
279
|
-
|
284
|
+
class User
|
285
|
+
include Uber::Builder
|
280
286
|
|
281
|
-
|
282
|
-
|
287
|
+
builds ->(options) do
|
288
|
+
return SignedIn if params[:current_user]
|
289
|
+
return Admin if params[:admin]
|
290
|
+
Anonymous
|
291
|
+
end
|
283
292
|
end
|
284
293
|
```
|
285
294
|
|
286
|
-
|
295
|
+
Note that this allows `return`s in the block.
|
287
296
|
|
288
|
-
##
|
297
|
+
## Builder: Direct API
|
289
298
|
|
290
|
-
|
299
|
+
In case you don't want the `builds` DSL, you can instantiate a `Builders` object yourself and add builders to it using `#<<`.
|
291
300
|
|
292
301
|
```ruby
|
293
|
-
|
294
|
-
|
302
|
+
MyBuilders = Uber::Builder::Builders.new
|
303
|
+
MyBuilders << ->(options) do
|
304
|
+
return Admin if options[:admin]
|
295
305
|
end
|
296
306
|
```
|
297
307
|
|
298
|
-
|
299
|
-
|
300
|
-
## More On Builders
|
301
|
-
|
302
|
-
Note that builders are _not_ inherited to subclasses. This allows instantiating subclasses directly without running builders.
|
303
|
-
|
304
|
-
This pattern is used in [Cells](https://github.com/apotonick/cells), [Trailblazer](https://github.com/apotonick/trailblazer) and soon Reform and Representable/Roar, too.
|
305
|
-
|
306
|
-
# Version
|
308
|
+
Note that you can call `Builders#<<` multiple times per instance.
|
307
309
|
|
308
|
-
|
310
|
+
Invoke the builder using `#call`.
|
309
311
|
|
310
312
|
```ruby
|
311
|
-
|
313
|
+
MyBuilders.call(User, {}) #=> User
|
314
|
+
MyBuilders.call(User, { admin: true }) #=> Admin
|
312
315
|
```
|
313
316
|
|
314
|
-
|
317
|
+
Again, the first object is the context/default return value, all other arguments are passed to the builder procs.
|
315
318
|
|
316
|
-
|
317
|
-
version >= "1.1" #=> true
|
318
|
-
version >= "1.3" #=> false
|
319
|
-
```
|
319
|
+
## Builder: Contexts
|
320
320
|
|
321
|
-
|
321
|
+
Every proc is `instance_exec`ed in the context you pass into `build!` (or `call`), allowing you to define generic, shareable builders.
|
322
322
|
|
323
323
|
```ruby
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
324
|
+
MyBuilders = Uber::Builder::Builders.new
|
325
|
+
MyBuilders << ->(options) do
|
326
|
+
return self::Admin if options[:admin] # note the self:: !
|
327
|
+
end
|
328
328
|
|
329
|
-
|
329
|
+
class User
|
330
|
+
class Admin
|
331
|
+
end
|
332
|
+
end
|
330
333
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
+
class Instructor
|
335
|
+
class Admin
|
336
|
+
end
|
337
|
+
end
|
334
338
|
```
|
335
339
|
|
340
|
+
Now, depending on the context class, the builder will return different classes.
|
336
341
|
|
337
|
-
|
338
|
-
|
339
|
-
(
|
342
|
+
```ruby
|
343
|
+
MyBuilders.call(User, {}) #=> User
|
344
|
+
MyBuilders.call(User, { admin: true }) #=> User::Admin
|
345
|
+
MyBuilders.call(Instructor, {}) #=> Instructor
|
346
|
+
MyBuilders.call(Instructor, { admin: true }) #=> Instructor::Admin
|
347
|
+
```
|
340
348
|
|
341
|
-
|
349
|
+
Don't forget the `self::` when writing generic builders, and write tests.
|
342
350
|
|
343
351
|
# License
|
344
352
|
|
data/lib/uber/builder.rb
CHANGED
@@ -1,97 +1,41 @@
|
|
1
|
-
require "uber/
|
1
|
+
require "uber/option"
|
2
2
|
|
3
3
|
module Uber
|
4
|
-
# When included, allows to add builder on the class level.
|
5
|
-
#
|
6
|
-
# class Operation
|
7
|
-
# include Uber::Builder
|
8
|
-
#
|
9
|
-
# builds do |params|
|
10
|
-
# SignedIn if params[:current_user]
|
11
|
-
# end
|
12
|
-
#
|
13
|
-
# class SignedIn
|
14
|
-
# end
|
15
|
-
#
|
16
|
-
# The class then has to call the builder to compute a class name using the build blocks you defined.
|
17
|
-
#
|
18
|
-
# def self.build(params)
|
19
|
-
# class_builder.call(params).
|
20
|
-
# new(params)
|
21
|
-
# end
|
22
4
|
module Builder
|
23
5
|
def self.included(base)
|
24
|
-
base.
|
25
|
-
|
26
|
-
@builders ||= []
|
27
|
-
end
|
28
|
-
|
29
|
-
extend ClassMethods
|
30
|
-
end
|
6
|
+
base.extend DSL
|
7
|
+
base.extend Build
|
31
8
|
end
|
32
9
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
@builders = @constant.builders # only dependency, must be a Cell::Base subclass.
|
39
|
-
end
|
10
|
+
class Builders < Array
|
11
|
+
def call(context, *args)
|
12
|
+
each do |block|
|
13
|
+
klass = block.(context, *args) and return klass # Uber::Value#call()
|
14
|
+
end
|
40
15
|
|
41
|
-
|
42
|
-
build_class_for(*args)
|
16
|
+
context
|
43
17
|
end
|
44
18
|
|
45
|
-
|
46
|
-
|
47
|
-
@builders.each do |blk|
|
48
|
-
klass = run_builder_block(blk, *args) and return klass
|
49
|
-
end
|
50
|
-
@constant
|
19
|
+
def <<(proc)
|
20
|
+
super Uber::Option[proc, instance_exec: true]
|
51
21
|
end
|
22
|
+
end
|
52
23
|
|
53
|
-
|
54
|
-
|
24
|
+
module DSL
|
25
|
+
def builders
|
26
|
+
@builders ||= Builders.new
|
55
27
|
end
|
56
|
-
end
|
57
28
|
|
58
|
-
module ClassMethods
|
59
|
-
# Adds a builder to the cell class. Builders are used in #cell to find out the concrete
|
60
|
-
# class for rendering. This is helpful if you frequently want to render subclasses according
|
61
|
-
# to different circumstances (e.g. login situations) and you don't want to place these deciders in
|
62
|
-
# your view code.
|
63
|
-
#
|
64
|
-
# Passes the model and options from #cell into the block.
|
65
|
-
#
|
66
|
-
# Multiple build blocks are ORed, if no builder matches the building cell is used.
|
67
|
-
#
|
68
|
-
# Example:
|
69
|
-
#
|
70
|
-
# Consider two different user box cells in your app.
|
71
|
-
#
|
72
|
-
# class AuthorizedUserBox < UserInfoBox
|
73
|
-
# end
|
74
|
-
#
|
75
|
-
# class AdminUserBox < UserInfoBox
|
76
|
-
# end
|
77
|
-
#
|
78
|
-
# Now you don't want to have deciders all over your views - use a declarative builder.
|
79
|
-
#
|
80
|
-
# UserInfoBox.build do |model, options|
|
81
|
-
# AuthorizedUserBox if options[:is_signed_in]
|
82
|
-
# AdminUserBox if model.admin?
|
83
|
-
# end
|
84
|
-
#
|
85
|
-
# In your view #cell will instantiate the right class for you now.
|
86
29
|
def builds(proc=nil, &block)
|
87
|
-
builders <<
|
30
|
+
builders << (proc || block)
|
88
31
|
end
|
32
|
+
end
|
89
33
|
|
90
|
-
|
91
|
-
#
|
92
|
-
def
|
93
|
-
|
34
|
+
module Build
|
35
|
+
# Call this from your class to compute the concrete target class.
|
36
|
+
def build!(context, *args)
|
37
|
+
builders.(context, *args)
|
94
38
|
end
|
95
|
-
end
|
39
|
+
end
|
96
40
|
end
|
97
41
|
end
|
@@ -2,7 +2,7 @@ module Uber
|
|
2
2
|
module InheritableAttr
|
3
3
|
|
4
4
|
def inheritable_attr(name, options={})
|
5
|
-
instance_eval
|
5
|
+
instance_eval <<-RUBY, __FILE__, __LINE__ + 1
|
6
6
|
def #{name}=(v)
|
7
7
|
@#{name} = v
|
8
8
|
end
|
@@ -11,7 +11,7 @@ module Uber
|
|
11
11
|
return @#{name} if instance_variable_defined?(:@#{name})
|
12
12
|
@#{name} = InheritableAttribute.inherit_for(self, :#{name}, #{options})
|
13
13
|
end
|
14
|
-
|
14
|
+
RUBY
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.inherit_for(klass, name, options={})
|
data/lib/uber/option.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "uber/callable"
|
2
|
+
|
3
|
+
module Uber
|
4
|
+
class Option
|
5
|
+
def self.[](value, options={}) # TODO: instance_exec: true
|
6
|
+
if value.is_a?(Proc)
|
7
|
+
return ->(context, *args) { context.instance_exec(*args, &value) } if options[:instance_exec]
|
8
|
+
return value
|
9
|
+
end
|
10
|
+
|
11
|
+
return value if value.is_a?(Uber::Callable)
|
12
|
+
return ->(context, *args){ context.send(value, *args) } if value.is_a?(Symbol)
|
13
|
+
->(*) { value }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/uber/options.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
require
|
1
|
+
require "uber/callable"
|
2
|
+
require "uber/option"
|
2
3
|
|
3
4
|
module Uber
|
4
5
|
class Options < Hash
|
@@ -6,41 +7,27 @@ module Uber
|
|
6
7
|
@static = options
|
7
8
|
|
8
9
|
options.each do |k,v|
|
9
|
-
self[k] =
|
10
|
-
@static = nil if option.dynamic?
|
10
|
+
self[k] = Option[v, instance_exec: true]
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
# 1.100000 0.060000 1.160000 ( 1.159762) original
|
15
|
-
# 0.120000 0.010000 0.130000 ( 0.135803) return self
|
16
|
-
# 0.930000 0.060000 0.990000 ( 0.997095) without v.evaluate
|
17
|
-
|
18
14
|
# Evaluates every element and returns a hash. Accepts context and arbitrary arguments.
|
19
15
|
def evaluate(context, *args)
|
20
|
-
return @static unless dynamic?
|
21
|
-
|
22
|
-
evaluate_for(context, *args)
|
23
|
-
end
|
24
|
-
|
25
|
-
# Evaluates a single value.
|
26
|
-
def eval(key, *args)
|
27
|
-
self[key].evaluate(*args)
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
def evaluate_for(context, *args)
|
32
16
|
{}.tap do |evaluated|
|
33
17
|
each do |k,v|
|
34
|
-
evaluated[k] = v.
|
18
|
+
evaluated[k] = v.(context, *args)
|
35
19
|
end
|
36
20
|
end
|
37
21
|
end
|
38
22
|
|
39
|
-
|
40
|
-
|
23
|
+
# Evaluates a single value.
|
24
|
+
def eval(key, *args)
|
25
|
+
self[key].(*args)
|
41
26
|
end
|
42
27
|
|
28
|
+
private
|
43
29
|
|
30
|
+
# DEPRECATED! PLEASE USE UBER::OPTION.
|
44
31
|
class Value # TODO: rename to Value.
|
45
32
|
def initialize(value, options={})
|
46
33
|
@value, @dynamic = value, options[:dynamic]
|
@@ -103,4 +90,4 @@ module Uber
|
|
103
90
|
end
|
104
91
|
end
|
105
92
|
end
|
106
|
-
end
|
93
|
+
end
|
data/lib/uber/version.rb
CHANGED
@@ -1,33 +1,3 @@
|
|
1
1
|
module Uber
|
2
|
-
|
3
|
-
|
4
|
-
@version = Gem::Version.new(version)
|
5
|
-
major, minor, patch = @version.segments
|
6
|
-
|
7
|
-
self[:major] = major || 0
|
8
|
-
self[:minor] = minor || 0
|
9
|
-
self[:patch] = patch || 0
|
10
|
-
end
|
11
|
-
|
12
|
-
def >=(version)
|
13
|
-
major, minor, patch = parse(version)
|
14
|
-
|
15
|
-
self[:major] > major or
|
16
|
-
(self[:major] == major and self[:minor] >= minor and self[:patch] >= patch)
|
17
|
-
end
|
18
|
-
|
19
|
-
def ~(*versions)
|
20
|
-
!! versions.find do |v|
|
21
|
-
major, minor, patch = parse(v)
|
22
|
-
|
23
|
-
self[:major] == major and self[:minor] == minor
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
def parse(version)
|
29
|
-
major, minor, patch = Gem::Version.new(version).segments
|
30
|
-
[major||0, minor||0, patch||0]
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
2
|
+
VERSION = "0.1.0"
|
3
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "uber/options"
|
3
|
+
require "uber/option"
|
4
|
+
require "benchmark/ips"
|
5
|
+
|
6
|
+
proc = ->(options) { "great" }
|
7
|
+
|
8
|
+
value = Uber::Options::Value.new(proc)
|
9
|
+
option = Uber::Option[proc, instance_exec: true]
|
10
|
+
|
11
|
+
Benchmark.ips do |x|
|
12
|
+
x.report(:value) { value.(self, {}) }
|
13
|
+
x.report(:option) { option.(self, {}) }
|
14
|
+
end
|
15
|
+
|
16
|
+
# value 946.110k (± 2.4%) i/s - 4.766M in 5.040395s
|
17
|
+
# option 1.583M (± 1.6%) i/s - 7.928M in 5.009953s
|
data/test/builder_test.rb
CHANGED
@@ -17,7 +17,7 @@ class BuilderTest < MiniTest::Spec
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def self.build(options)
|
20
|
-
|
20
|
+
build!(self, options).new
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -40,7 +40,7 @@ class BuilderTest < MiniTest::Spec
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def self.build(options)
|
43
|
-
|
43
|
+
build!(self, options).new
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
@@ -66,7 +66,7 @@ class BuilderTest < MiniTest::Spec
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def self.build(options)
|
69
|
-
|
69
|
+
build!(self, options).new
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
@@ -94,10 +94,17 @@ class BuilderScopeTest < MiniTest::Spec
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def self.build(context, options={})
|
97
|
-
|
97
|
+
build!(context, options)
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
|
+
it do
|
102
|
+
Song.build(self.class).must_equal BuilderScopeTest::Hit
|
103
|
+
|
104
|
+
# this runs BuilderScopeTest::builder_method and returns self.
|
105
|
+
Song.build(self.class, from_builder_method: true).must_equal BuilderScopeTest
|
106
|
+
end
|
107
|
+
|
101
108
|
class Evergreen
|
102
109
|
class Hit
|
103
110
|
end
|
@@ -110,7 +117,7 @@ class BuilderScopeTest < MiniTest::Spec
|
|
110
117
|
self.builders = Song.builders
|
111
118
|
|
112
119
|
def self.build(context, options={})
|
113
|
-
|
120
|
+
build!(context, options)
|
114
121
|
end
|
115
122
|
|
116
123
|
def self.builder_method(options)
|
@@ -118,21 +125,23 @@ class BuilderScopeTest < MiniTest::Spec
|
|
118
125
|
end
|
119
126
|
end
|
120
127
|
|
121
|
-
it do
|
122
|
-
Song.build(self.class).must_equal BuilderScopeTest::Hit
|
123
|
-
|
124
|
-
# this runs BuilderScopeTest::builder_method and returns self.
|
125
|
-
Song.build(self.class, from_builder_method: true).must_equal BuilderScopeTest
|
126
|
-
|
127
|
-
# since the class_builder gets cached, this won't change.
|
128
|
-
Song.build(Song).must_equal BuilderScopeTest::Hit
|
129
|
-
end
|
130
|
-
|
131
|
-
|
132
128
|
it do
|
133
129
|
# running the "copied" block in Evergreen will reference the correct @context.
|
134
130
|
Evergreen.build(Evergreen).must_equal BuilderScopeTest::Evergreen::Hit
|
135
131
|
|
136
132
|
Evergreen.build(Evergreen, from_builder_method: true).must_equal BuilderScopeTest::Evergreen
|
137
133
|
end
|
138
|
-
|
134
|
+
|
135
|
+
#---
|
136
|
+
# Builders API
|
137
|
+
# Builders#call
|
138
|
+
# Builders#<<
|
139
|
+
A = Class.new
|
140
|
+
MyBuilders = Uber::Builder::Builders.new
|
141
|
+
MyBuilders << ->(options) do
|
142
|
+
return Song if options[:hit]
|
143
|
+
end
|
144
|
+
|
145
|
+
it { MyBuilders.call(A, {}).new.must_be_instance_of A }
|
146
|
+
it { MyBuilders.call(A, { hit: true }).new.must_be_instance_of Song }
|
147
|
+
end
|
data/test/option_test.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "uber/option"
|
3
|
+
|
4
|
+
class OptionTest < Minitest::Spec
|
5
|
+
Option = Uber::Option
|
6
|
+
|
7
|
+
# proc
|
8
|
+
it { Option[ ->(*args) { "proc! #{args.inspect}" } ].(1,2).must_equal "proc! [1, 2]" }
|
9
|
+
it { Option[ lambda { "proc!" } ].().must_equal "proc!" }
|
10
|
+
|
11
|
+
# proc with instance_exec
|
12
|
+
it { Option[ ->(*args) { "#{self.class} #{args.inspect}" } ].(Object, 1, 2).must_equal "OptionTest [Object, 1, 2]" }
|
13
|
+
it { Option[ ->(*args) { "#{self} #{args.inspect}" }, instance_exec: true ].(Object, 1, 2).must_equal "Object [1, 2]" }
|
14
|
+
|
15
|
+
# static
|
16
|
+
it { Option[true].().must_equal true }
|
17
|
+
it { Option[nil].().must_equal nil }
|
18
|
+
it { Option[false].().must_equal false }
|
19
|
+
# args are ignored.
|
20
|
+
it { Option[true].(1,2,3).must_equal true }
|
21
|
+
|
22
|
+
# instance method
|
23
|
+
class Hello
|
24
|
+
def hello(*args); "Hello! #{args.inspect}" end
|
25
|
+
end
|
26
|
+
it { Option[:hello].(Hello.new).must_equal "Hello! []" }
|
27
|
+
it { Option[:hello].(Hello.new, 1, 2).must_equal "Hello! [1, 2]" }
|
28
|
+
|
29
|
+
#---
|
30
|
+
# Callable
|
31
|
+
class Callio
|
32
|
+
include Uber::Callable
|
33
|
+
def call(); "callable!" end
|
34
|
+
end
|
35
|
+
|
36
|
+
it { Option[Callio.new].().must_equal "callable!" }
|
37
|
+
end
|
38
|
+
|
39
|
+
# require "benchmark/ips"
|
40
|
+
|
41
|
+
# method = Uber::Option[->(context, options) { context }]
|
42
|
+
# proc = Uber::Option[A.new { |context, options| context }]
|
43
|
+
|
44
|
+
# Benchmark.ips do |x|
|
45
|
+
# x.report(:method) { method.(Object, {}) }
|
46
|
+
# x.report(:proc) { proc.(Object, {}) }
|
47
|
+
# end
|
data/test/options_test.rb
CHANGED
@@ -1,96 +1,12 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
require 'uber/options'
|
3
3
|
|
4
|
-
class UberOptionTest < MiniTest::Spec
|
5
|
-
Value = Uber::Options::Value
|
6
|
-
let (:object) { Object.new }
|
7
|
-
|
8
|
-
class Callable
|
9
|
-
include Uber::Callable
|
10
|
-
def call(*); 999 end
|
11
|
-
end
|
12
|
-
|
13
|
-
describe "#dynamic?" do
|
14
|
-
it { Value.new(1).dynamic?.must_equal false }
|
15
|
-
it { Value.new(true).dynamic?.must_equal false }
|
16
|
-
it { Value.new("loud").dynamic?.must_equal false }
|
17
|
-
it { Value.new(:loud, :dynamic => false).dynamic?.must_equal false }
|
18
|
-
it { Value.new("loud", :dynamic => true).dynamic?.must_equal true }
|
19
|
-
|
20
|
-
it { Value.new(lambda {}).dynamic?.must_equal true }
|
21
|
-
it { Value.new(Proc.new{}).dynamic?.must_equal true }
|
22
|
-
it { Value.new(:method).dynamic?.must_equal true }
|
23
|
-
|
24
|
-
# Uber::Callable
|
25
|
-
it { Value.new(Callable.new).dynamic?.must_equal true }
|
26
|
-
end
|
27
|
-
|
28
|
-
describe "#call" do
|
29
|
-
let (:version) { Module.new { def version; 999 end } }
|
30
|
-
|
31
|
-
it { Value.new(nil).(Object.new).must_equal nil }
|
32
|
-
# it { Value.new(nil, :dynamic => true).(Object.new).must_equal nil } # DISCUSS: should we handle that?
|
33
|
-
|
34
|
-
it { Value.new(true).(Object.new).must_equal true }
|
35
|
-
|
36
|
-
it { Value.new(:version).(object.extend(version)).must_equal 999 }
|
37
|
-
it { Value.new("version", :dynamic => true).(object.extend(version)).must_equal 999 }
|
38
|
-
it { Value.new(:version, :dynamic => false).(object.extend(version)).must_equal :version }
|
39
|
-
it { Value.new(lambda { self }).(object).must_equal object }
|
40
|
-
it { Value.new(lambda { self }).(nil).must_equal self }
|
41
|
-
|
42
|
-
it { Value.new(lambda { :loud }, :dynamic => true).(object).must_equal :loud }
|
43
|
-
|
44
|
-
# Uber::Callable
|
45
|
-
it { Value.new(Callable.new).(nil).must_equal 999 }
|
46
|
-
end
|
47
|
-
|
48
|
-
it "#call is aliased to evaluate" do
|
49
|
-
Value.new(Callable.new).(nil).must_equal 999
|
50
|
-
end
|
51
|
-
|
52
|
-
describe "passing options" do
|
53
|
-
let (:version) { Module.new { def version(*args); args.inspect end } }
|
54
|
-
let (:block) { Proc.new { |*args| args.inspect } }
|
55
|
-
let (:callable) { (Class.new { include Uber::Callable; def call(*args); args.inspect; end }).new }
|
56
|
-
|
57
|
-
it { Value.new(:version).(object.extend(version), 1, 2, 3).must_equal "[1, 2, 3]" }
|
58
|
-
it { Value.new(block).(object, 1, 2, 3).must_equal "[1, 2, 3]" }
|
59
|
-
it { Value.new(callable).(Object, 1, 2, 3).must_equal "[Object, 1, 2, 3]" }
|
60
|
-
end
|
61
|
-
|
62
|
-
# it "speed" do
|
63
|
-
# require "benchmark"
|
64
|
-
|
65
|
-
# options = 1000000.times.collect do
|
66
|
-
# Uber::Options.new(expires: false)
|
67
|
-
# end
|
68
|
-
|
69
|
-
# time = Benchmark.measure do
|
70
|
-
# options.each do |opt|
|
71
|
-
# opt.evaluate(nil)
|
72
|
-
# end
|
73
|
-
# end
|
74
|
-
|
75
|
-
# puts "good results"
|
76
|
-
# puts time
|
77
|
-
# end
|
78
|
-
end
|
79
|
-
|
80
|
-
# TODO: test passing arguments to block and method optionally.
|
81
|
-
|
82
4
|
class UberOptionsTest < MiniTest::Spec
|
83
5
|
Options = Uber::Options
|
84
6
|
|
85
7
|
let (:dynamic) { Options.new(:volume =>1, :style => "Punkrock", :track => Proc.new { |i| i.to_s }) }
|
86
8
|
|
87
|
-
describe "#dynamic?" do
|
88
|
-
it { Options.new(:volume =>1, :style => "Punkrock").send(:dynamic?).must_equal false }
|
89
|
-
it { Options.new(:style => Proc.new{}, :volume =>1).send(:dynamic?).must_equal true }
|
90
|
-
end
|
91
|
-
|
92
9
|
describe "#evaluate" do
|
93
|
-
|
94
10
|
it { dynamic.evaluate(Object.new, 999).must_equal({:volume =>1, :style => "Punkrock", :track => "999"}) }
|
95
11
|
|
96
12
|
describe "static" do
|
@@ -114,4 +30,4 @@ class UberOptionsTest < MiniTest::Spec
|
|
114
30
|
it { dynamic.eval(:style, 999).must_equal "Punkrock" }
|
115
31
|
it { dynamic.eval(:track, Object.new, 999).must_equal "999" }
|
116
32
|
end
|
117
|
-
end
|
33
|
+
end
|
data/uber.gemspec
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
require File.expand_path('../lib/uber/
|
2
|
+
require File.expand_path('../lib/uber/version', __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ["Nick Sutterer"]
|
6
6
|
gem.email = ["apotonick@gmail.com"]
|
7
7
|
gem.description = %q{A gem-authoring framework.}
|
8
|
-
gem.summary = %q{Gem-authoring tools like
|
8
|
+
gem.summary = %q{Gem-authoring tools like generic builders, dynamic options and more.}
|
9
9
|
gem.homepage = "https://github.com/apotonick/uber"
|
10
10
|
gem.license = "MIT"
|
11
11
|
|
@@ -16,6 +16,6 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.require_paths = ["lib"]
|
17
17
|
gem.version = Uber::VERSION
|
18
18
|
|
19
|
-
gem.add_development_dependency "rake"
|
20
|
-
gem.add_development_dependency "minitest"
|
19
|
+
gem.add_development_dependency "rake"
|
20
|
+
gem.add_development_dependency "minitest"
|
21
21
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: uber
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-11-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: minitest
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: '0'
|
41
41
|
description: A gem-authoring framework.
|
42
42
|
email:
|
43
43
|
- apotonick@gmail.com
|
@@ -57,16 +57,17 @@ files:
|
|
57
57
|
- lib/uber/callable.rb
|
58
58
|
- lib/uber/delegates.rb
|
59
59
|
- lib/uber/inheritable_attr.rb
|
60
|
+
- lib/uber/option.rb
|
60
61
|
- lib/uber/options.rb
|
61
|
-
- lib/uber/uber_version.rb
|
62
62
|
- lib/uber/version.rb
|
63
|
+
- test/builder-benchmark.rb
|
63
64
|
- test/builder_test.rb
|
64
65
|
- test/delegates_test.rb
|
65
66
|
- test/inheritable_attr_test.rb
|
66
67
|
- test/inheritance_test.rb
|
68
|
+
- test/option_test.rb
|
67
69
|
- test/options_test.rb
|
68
70
|
- test/test_helper.rb
|
69
|
-
- test/version_test.rb
|
70
71
|
- test/zeugs.rb
|
71
72
|
- uber.gemspec
|
72
73
|
homepage: https://github.com/apotonick/uber
|
@@ -89,18 +90,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
90
|
version: '0'
|
90
91
|
requirements: []
|
91
92
|
rubyforge_project:
|
92
|
-
rubygems_version: 2.
|
93
|
+
rubygems_version: 2.6.3
|
93
94
|
signing_key:
|
94
95
|
specification_version: 4
|
95
|
-
summary: Gem-authoring tools like
|
96
|
-
and more.
|
96
|
+
summary: Gem-authoring tools like generic builders, dynamic options and more.
|
97
97
|
test_files:
|
98
|
+
- test/builder-benchmark.rb
|
98
99
|
- test/builder_test.rb
|
99
100
|
- test/delegates_test.rb
|
100
101
|
- test/inheritable_attr_test.rb
|
101
102
|
- test/inheritance_test.rb
|
103
|
+
- test/option_test.rb
|
102
104
|
- test/options_test.rb
|
103
105
|
- test/test_helper.rb
|
104
|
-
- test/version_test.rb
|
105
106
|
- test/zeugs.rb
|
106
|
-
has_rdoc:
|
data/lib/uber/uber_version.rb
DELETED
data/test/version_test.rb
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
|
4
|
-
class VersionTest < MiniTest::Spec
|
5
|
-
subject { Uber::Version.new("1.2.3") } # Rails version
|
6
|
-
|
7
|
-
it { subject.~("1.0").must_equal false }
|
8
|
-
it { subject.~("1.1").must_equal false }
|
9
|
-
it { subject.~("1.2").must_equal true }
|
10
|
-
it { subject.~("1.3").must_equal false }
|
11
|
-
it { subject.~("2.2").must_equal false }
|
12
|
-
it { subject.~("1.0", "1.1").must_equal false }
|
13
|
-
it { subject.~("1.0", "1.1", "1.2").must_equal true }
|
14
|
-
it { subject.~("1.2", "1.3").must_equal true }
|
15
|
-
|
16
|
-
it { (subject >= "1.2.4").must_equal false }
|
17
|
-
it { (subject >= "1.2.2").must_equal true }
|
18
|
-
it { (subject >= "0.3.1").must_equal true }
|
19
|
-
it { (subject >= "0.3.6").must_equal true }
|
20
|
-
it { (subject >= "0.3").must_equal true }
|
21
|
-
it { (subject >= "0.2.4").must_equal true }
|
22
|
-
it { (subject >= "0.2").must_equal true }
|
23
|
-
it { (subject >= "1.2").must_equal true }
|
24
|
-
it { (subject >= "1.1").must_equal true }
|
25
|
-
it { (subject >= "1.3").must_equal false }
|
26
|
-
it { (subject >= "2.1").must_equal false }
|
27
|
-
end
|