uber 0.0.2 → 0.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 58b218477b8c36e49f66c8bd47175bf605613ef1
4
- data.tar.gz: bde8206f4395aa6469f9dfe84eb3651449acd0e3
3
+ metadata.gz: ca88c9e957495cdf978405ff7c9c7f67d6d0258a
4
+ data.tar.gz: 151fb75aea52f7485dd8eee7d86afac8b80b61ab
5
5
  SHA512:
6
- metadata.gz: f08f9af53d76f9800741b6eacd147d031a356b40e309172c1323cf5a80b2f1f70271c1f383f7fbcb2681ac72eaf163b64fc364a36888523688cbbb8147185d22
7
- data.tar.gz: 8b6a3e28f375e50b829fe60bf5a2a5ab50daafee28cd5e6334d8488e201b85a12ed915ed0e3c1c84938f5006f5ec55a8b1be07141895bdecc8472c24bfa8348e
6
+ metadata.gz: 4748f03b0f45606e62428bf2d14a7ad5ab22c29b68f887c012faafb738110d4279c0c818098ba5c3b2f7c531d65dab13710ff53ec50f13f8d00aa6a21cb36e85
7
+ data.tar.gz: ce170857e5789abd29ea165be5aae4ee4ecb527d80de5224bf1a70c0c232461adade8483a6d1ad3af030c64a83eed1bb0c549aa57d56ebfeda5f768322be0f7b
data/CHANGES.md CHANGED
@@ -1,3 +1,7 @@
1
+ # 0.0.3
2
+
3
+ * Add `Options` and `Options::Value´ for abstracting dynamic options.
4
+
1
5
  # 0.0.2
2
6
 
3
7
  * Add `::inheritable_attr`.
data/README.md CHANGED
@@ -56,19 +56,89 @@ It's similar to ActiveSupport's `class_attribute` but with a simpler implementat
56
56
  This module is very popular amongst numerous gems like Cells, Representable, Roar and Reform.
57
57
 
58
58
 
59
- # Options
59
+ # Dynamic Options
60
60
 
61
- Implements the pattern of defining configuration options and evaluating them at run-time.
61
+ Implements the pattern of defining configuration options and dynamically evaluating them at run-time.
62
+
63
+ Usually DSL methods accept a number of options that can either be static values, symbolized instance method names, or blocks (lambdas/Procs).
64
+
65
+ Here's an example from Cells.
66
+
67
+ ```ruby
68
+ cache :show, tags: lambda { Tag.last }, expire_in: 5.mins, ttl: :time_to_live
69
+ ```
70
+
71
+ Usually, when processing these options, you'd have to check every option for its type, evaluate the `tag:` lambda in a particular context, call the `#time_to_live` instance method, etc.
72
+
73
+ This is abstracted in `Uber::Options` and could be implemented like this.
74
+
75
+ ```ruby
76
+ options = Uber::Options.new(tags: lambda { Tag.last }, expire_in: 5.mins, ttl: :time_to_live)
77
+ ```
78
+
79
+ Just initialize `Options` with your actual options hash. While this usually happens on class level at compile-time, evaluating the hash happens at run-time.
80
+
81
+ ```ruby
82
+ class User < ActiveRecord::Base # this could be any Ruby class.
83
+ # .. lots of code
84
+
85
+ def time_to_live
86
+ "n/a"
87
+ end
88
+ end
89
+
90
+ user = User.find(1)
91
+
92
+ options.evaluate(user, *args) #=> {tags: "hot", expire_in: 300, ttl: "n/a"}
93
+ ```
94
+
95
+ ## Evaluating Dynamic Options
96
+
97
+ To evaluate the options to a real hash, the following happens:
98
+
99
+ * The `tags:` lambda is executed in `user` context (using `instance_exec`). This allows accessing instance variables or calling instance methods. All `*args` are passed as block parameters to the lambda.
100
+ * Nothing is done with `expires_in`'s value, it is static.
101
+ * `user.time_to_live?` is called as the symbol `:time_to_live` indicates that this is an instance method.
102
+
103
+ The default behaviour is to treat `Proc`s, lambdas and symbolized `:method` names as dynamic options, everything else is considered static. This is a pattern well-known from Rails and other frameworks.
104
+
105
+ ## Evaluating Elements
106
+
107
+ If you wanna evaluate a single option element, use `#eval`.
108
+
109
+ ```ruby
110
+ options.eval(:ttl, user) #=> "n/a"
111
+ ```
112
+
113
+ ## Single Values
114
+
115
+ Sometimes you don't need an entire hash but a dynamic value, only.
116
+
117
+ ```ruby
118
+ value = Uber::Options::Value.new(lambda { |volume| volume < 0 ? 0 : volume })
119
+
120
+ value.evaluate(context, -122.18) #=> 0
121
+ ```
122
+
123
+ Use `Options::Value#evaluate` to handle single values.
124
+
125
+ ## Performance
126
+
127
+ Evaluating an options hash can be time-consuming. When `Options` contains static elements only, it behaves *and performs* like an ordinary hash.
62
128
 
63
- Usually DSL methods accept a number of options that can either be static values, instance method names as symbols, or blocks (lambdas/Procs).
64
129
 
65
130
  Uber::Options.new volume: 9, track: lambda { |s| s.track }
66
131
 
67
132
 
68
- Note that `Options` behaves *and performs* like an ordinary hash when all options are static.
133
+ dynamic: true
69
134
 
70
135
  only use for declarative assets, not at runtime (use a hash)
71
136
 
137
+ # Undocumented Features
138
+
139
+ (Please don't read this!)
140
+
141
+ * You can enforce treating values as dynamic (or not): `Uber::Options::Value.new("time_to_live", dynamic: true)` will always run `#time_to_live` as an instance method on the context, even thou it is not a symbol.
72
142
 
73
143
  # License
74
144
 
data/lib/uber/options.rb CHANGED
@@ -41,29 +41,42 @@ module Uber
41
41
 
42
42
  class Value # TODO: rename to Value.
43
43
  def initialize(value, options={})
44
- @value = value || true
45
- @options = options
44
+ @value, @options = value, options
45
+
46
+ return if @options.has_key?(:dynamic)
47
+
48
+ # conventional behaviour:
49
+ @callable = @options[:dynamic] = true if @value.kind_of?(Proc)
50
+ @options[:dynamic] = true if @value.is_a?(Symbol)
46
51
  end
47
52
 
48
53
  def evaluate(context, *args)
49
- return true if @value.is_a?(TrueClass)
54
+ return @value unless dynamic?
50
55
 
51
56
  evaluate_for(context, *args)
52
57
  end
53
58
 
54
59
  def dynamic?
55
- @options[:instance_method] || @value.kind_of?(Proc)
60
+ @options[:dynamic]
56
61
  end
57
62
 
58
63
  private
59
- def evaluate_for(context, *args)
60
- return proc!(context, *args) unless @value.kind_of?(Proc)
61
- @value.call(context, *args) # TODO: change to context.instance_exec and deprecate first argument.
64
+ def evaluate_for(*args)
65
+ return method!(*args) unless callable?
66
+ # TODO: change to context.instance_exec and deprecate first argument.
67
+ proc!(*args)
68
+ end
69
+
70
+ def method!(context, *args)
71
+ context.send(@value, *args)
62
72
  end
63
73
 
64
74
  def proc!(context, *args)
65
- return context.send(@value, *args) if @options[:instance_method]
66
- @value
75
+ context.instance_exec(*args, &@value)
76
+ end
77
+
78
+ def callable?
79
+ @callable
67
80
  end
68
81
  end
69
82
  end
data/lib/uber/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Uber
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -21,6 +21,9 @@ class InheritanceTest < MiniTest::Spec
21
21
 
22
22
  CODE_BLOCK = lambda { |base| base.class_eval { extend ClassMethods } } # i want that to be executed at every include
23
23
 
24
+ instance_exec do
25
+ @block = CODE_BLOCK # this would happen in inherited_included do .. end
26
+ end
24
27
 
25
28
  def self.included(includer) #
26
29
  # CODE_BLOCK.call(base)
data/test/options_test.rb CHANGED
@@ -3,18 +3,39 @@ require 'uber/options'
3
3
 
4
4
  class UberOptionTest < MiniTest::Spec
5
5
  Value = Uber::Options::Value
6
+ let (:object) { Object.new }
6
7
 
7
8
  describe "#dynamic?" do
8
- it { Value.new(1).dynamic?.must_equal false }
9
- it { Value.new(true).dynamic?.must_equal false }
10
- it { Value.new(:loud).dynamic?.must_equal false }
9
+ it { Value.new(1).dynamic?.must_equal nil }
10
+ it { Value.new(true).dynamic?.must_equal nil }
11
+ it { Value.new("loud").dynamic?.must_equal nil }
12
+ it { Value.new(:loud, :dynamic => false).dynamic?.must_equal false }
11
13
 
12
14
  it { Value.new(lambda {}).dynamic?.must_equal true }
13
15
  it { Value.new(Proc.new{}).dynamic?.must_equal true }
14
- it { Value.new(:method, :instance_method => true).dynamic?.must_equal true }
16
+ it { Value.new(:method).dynamic?.must_equal true }
15
17
  end
16
18
 
19
+ describe "#evaluate" do
20
+ let (:version) { Module.new { def version; 999 end } }
21
+
22
+ it { Value.new(nil).evaluate(Object.new).must_equal nil }
23
+ # it { Value.new(nil, :dynamic => true).evaluate(Object.new).must_equal nil } # DISCUSS: should we handle that?
24
+
25
+ it { Value.new(true).evaluate(Object.new).must_equal true }
26
+
27
+ it { Value.new(:version).evaluate(object.extend(version)).must_equal 999 }
28
+ it { Value.new("version", :dynamic => true).evaluate(object.extend(version)).must_equal 999 }
29
+ it { Value.new(:version, :dynamic => false).evaluate(object.extend(version)).must_equal :version }
30
+ end
31
+
32
+ describe "passing options" do
33
+ let (:version) { Module.new { def version(*args); args.inspect end } }
34
+ let (:block) { Proc.new { |*args| args.inspect } }
17
35
 
36
+ it { Value.new(:version).evaluate(object.extend(version), 1, 2, 3).must_equal "[1, 2, 3]" }
37
+ it { Value.new(block).evaluate(object, 1, 2, 3).must_equal "[1, 2, 3]" }
38
+ end
18
39
 
19
40
  # it "speed" do
20
41
  # require "benchmark"
@@ -34,6 +55,8 @@ class UberOptionTest < MiniTest::Spec
34
55
  # end
35
56
  end
36
57
 
58
+ # TODO: test passing arguments to block and method optionally.
59
+
37
60
  class UberOptionsTest < MiniTest::Spec
38
61
  Options = Uber::Options
39
62
 
@@ -46,7 +69,7 @@ class UberOptionsTest < MiniTest::Spec
46
69
 
47
70
  describe "#evaluate" do
48
71
 
49
- it { dynamic.evaluate(999).must_equal({:volume =>1, :style => "Punkrock", :track => "999"}) }
72
+ it { dynamic.evaluate(Object.new, 999).must_equal({:volume =>1, :style => "Punkrock", :track => "999"}) }
50
73
 
51
74
  describe "static" do
52
75
  let (:static) { Options.new(:volume =>1, :style => "Punkrock") }
@@ -67,6 +90,6 @@ class UberOptionsTest < MiniTest::Spec
67
90
  describe "#eval" do
68
91
  it { dynamic.eval(:volume, 999).must_equal 1 }
69
92
  it { dynamic.eval(:style, 999).must_equal "Punkrock" }
70
- it { dynamic.eval(:track, 999).must_equal "999" }
93
+ it { dynamic.eval(:track, Object.new, 999).must_equal "999" }
71
94
  end
72
95
  end
metadata CHANGED
@@ -1,41 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uber
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-06 00:00:00.000000000 Z
11
+ date: 2014-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - '>='
18
18
  - !ruby/object:Gem::Version
19
19
  version: 0.10.1
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
26
  version: 0.10.1
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
33
  version: 5.0.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
40
  version: 5.0.0
41
41
  description: A gem-authoring framework.
@@ -45,7 +45,7 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
- - ".gitignore"
48
+ - .gitignore
49
49
  - CHANGES.md
50
50
  - Gemfile
51
51
  - LICENSE
@@ -70,17 +70,17 @@ require_paths:
70
70
  - lib
71
71
  required_ruby_version: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - '>='
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  required_rubygems_version: !ruby/object:Gem::Requirement
77
77
  requirements:
78
- - - ">="
78
+ - - '>='
79
79
  - !ruby/object:Gem::Version
80
80
  version: '0'
81
81
  requirements: []
82
82
  rubyforge_project:
83
- rubygems_version: 2.2.1
83
+ rubygems_version: 2.0.3
84
84
  signing_key:
85
85
  specification_version: 4
86
86
  summary: Gem-authoring tools like class method inheritance in modules, dynamic options