uber 0.0.15 → 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 +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
|