uber 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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