building_blocks 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +22 -0
- data/.yardopts +7 -0
- data/Gemfile +24 -0
- data/Guardfile +8 -0
- data/LICENSE +22 -0
- data/README.md +107 -0
- data/Rakefile +10 -0
- data/building_blocks.gemspec +22 -0
- data/lib/building_blocks.rb +59 -0
- data/lib/building_blocks/builders/builder_builder.rb +341 -0
- data/lib/building_blocks/version.rb +4 -0
- data/test/test_helper.rb +10 -0
- data/test/unit/builders/builder_builder_test.rb +406 -0
- data/test/unit/building_blocks_test.rb +136 -0
- metadata +91 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a8b00f6d31a66e4ee00c4617a1ea5542407a060d
|
4
|
+
data.tar.gz: d68f14603537f03ce13da0d30ccc642e5354153e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 96a5948aa3fe772601b35ecbea6bcdbfcda56ebe8d0895a21a486077c04d808c2918fdc92a8199ec95854d2685e47cfe526198967c7a68a37c6e08e8bcf9415f
|
7
|
+
data.tar.gz: d7f7ec68e9dc53d0b60c913f54489326db92ac8eb16660aa3285839207523429f2158acce609c2d4ce27d5af928ddf9bfa13e52735d53da03dddb6581bef8a14
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
group :doc do
|
6
|
+
gem 'yard', require: false
|
7
|
+
end
|
8
|
+
|
9
|
+
group :test do
|
10
|
+
gem 'coveralls', :require => false
|
11
|
+
gem 'faker'
|
12
|
+
gem 'minitest'
|
13
|
+
gem 'mocha', :require => false
|
14
|
+
gem 'shoulda-context'
|
15
|
+
end
|
16
|
+
|
17
|
+
# Dev and test gems not needed for CI
|
18
|
+
unless ENV['CI']
|
19
|
+
group :development, :test do
|
20
|
+
gem 'guard'
|
21
|
+
gem 'guard-minitest'
|
22
|
+
gem 'pry-debugger', :platforms => :mri
|
23
|
+
end
|
24
|
+
end
|
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Interval Braining
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# BuildingBlocks
|
2
|
+
[](https://travis-ci.org/interval-braining/building_blocks)
|
3
|
+
[](https://coveralls.io/r/interval-braining/building_blocks)
|
4
|
+
[](https://codeclimate.com/github/interval-braining/building_blocks)
|
5
|
+
[](https://gemnasium.com/interval-braining/building_blocks)
|
6
|
+
|
7
|
+
Simple DSL for defining Builder classes with customized DSLs.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'building_blocks'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install building_blocks
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
BuildingBlocks strives to make it easy to create customizable block-based DSLs
|
26
|
+
for building all variety of objects.
|
27
|
+
|
28
|
+
This is achieved through the use of Builder objects. At their core, Builders are
|
29
|
+
any object that defines a `build` method that takes in a block and returns an
|
30
|
+
Object. How that block is used and what Object is returned can depend greatly on
|
31
|
+
the Builder being used. In some cases the block could be ignored entirely. In
|
32
|
+
other cases the Object returned could be a JSON string. It all depends on the
|
33
|
+
Builder.
|
34
|
+
|
35
|
+
Because Builders are just Plain Old Ruby Objects, there are no restrictions on
|
36
|
+
how Builders can be defined or what they can accomplish during their build
|
37
|
+
process. However, since there are a few common patterns employed by most
|
38
|
+
Builders, BuildingBlocks offers a Buillder Builder that allows for defining
|
39
|
+
Builder objects using a simple DSL.
|
40
|
+
|
41
|
+
### BuilderBuilder DSL
|
42
|
+
Though BuildingBlocks is not itself a Builder, it does implement a `build`
|
43
|
+
method that delegates all calls to the configured
|
44
|
+
`BuildingBlocks.default_builder`. By default the `default_builder` is configured
|
45
|
+
to use `BuildingBlocks::Builders::BuilderBuilder`, a sort of meta-Builder in that
|
46
|
+
it's a Builder that also happens to build other valid Builders.
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
# Creating a new builder class using the default builder DSL
|
50
|
+
Point = Struct.new(:x, :y)
|
51
|
+
PointBuilder = BuildingBlocks.build do
|
52
|
+
resource_class Point
|
53
|
+
attribute :x
|
54
|
+
attribute :y
|
55
|
+
end
|
56
|
+
|
57
|
+
# Creating a new Point instance using PointBuilder
|
58
|
+
# Both the block arg, builder, and self are the PointBuilder instance
|
59
|
+
point = PointBuilder.build do |builder|
|
60
|
+
x 1
|
61
|
+
y 1
|
62
|
+
end
|
63
|
+
# => #<struct Point x=1, y=1>
|
64
|
+
|
65
|
+
|
66
|
+
# A more complex example of the default builder DSL
|
67
|
+
class Circle
|
68
|
+
attr_accessor :radius
|
69
|
+
attr_reader :center
|
70
|
+
|
71
|
+
def center=(point)
|
72
|
+
raise ArgumentError, 'Bad point' unless point.is_a?(Point)
|
73
|
+
@center = point
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
CircleBuilder = BuildingBlocks.build do
|
78
|
+
resource_class Circle
|
79
|
+
attribute :radius
|
80
|
+
builder :center, :center=, PointBuilder
|
81
|
+
delegate :x, :x=, :center
|
82
|
+
delegate :y, :y=, :center
|
83
|
+
end
|
84
|
+
|
85
|
+
circle = CircleBuilder.build do |c|
|
86
|
+
radius 5
|
87
|
+
center do |m|
|
88
|
+
x 5
|
89
|
+
y 5
|
90
|
+
end
|
91
|
+
|
92
|
+
# Override the previously set value for y
|
93
|
+
y 1
|
94
|
+
end
|
95
|
+
# => #<Circle @center=#<struct Point x=5, y=1>, @radius=5>
|
96
|
+
```
|
97
|
+
|
98
|
+
More information on the default builder definition DSL can be found in the
|
99
|
+
[docs](http://rubydoc.info/gems/building_blocks/0.0.1/frames)
|
100
|
+
|
101
|
+
## Contributing
|
102
|
+
|
103
|
+
1. Fork it ( https://github.com/interval-braining/building_blocks/fork )
|
104
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
105
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
106
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
107
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'building_blocks/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'building_blocks'
|
8
|
+
spec.version = BuildingBlocks::VERSION
|
9
|
+
spec.authors = ['Danny Guinther']
|
10
|
+
spec.email = ['dannyguinther@gmail.com']
|
11
|
+
spec.summary = %q{Block-based Object Builders}
|
12
|
+
spec.description = %q{Simple DSL for defining block-based Object builders}
|
13
|
+
spec.homepage = 'https://github.com/interval-braining/building_blocks'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.test_files = spec.files.grep(%r{^test/})
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
21
|
+
spec.add_development_dependency 'rake', '~> 0'
|
22
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'building_blocks/version'
|
3
|
+
require 'building_blocks/builders/builder_builder'
|
4
|
+
|
5
|
+
# Primary BuildingBlocks object providing an interface to build/define other
|
6
|
+
# builders using builders that build new builder objects.
|
7
|
+
module BuildingBlocks
|
8
|
+
|
9
|
+
class << self
|
10
|
+
extend Forwardable
|
11
|
+
def_delegator :default_builder, :build
|
12
|
+
end
|
13
|
+
|
14
|
+
# The default builder used for defining new builders when {build build}
|
15
|
+
# is invoked.
|
16
|
+
# @see build
|
17
|
+
# @see BuildingBlocks::Builders::BuilderBuilder
|
18
|
+
DEFAULT_DEFINITION_BUILDER = BuildingBlocks::Builders::BuilderBuilder
|
19
|
+
|
20
|
+
# Returns the object currently configured for use when defining new builders
|
21
|
+
# using {build build}. If a custom value has not been configured the
|
22
|
+
# {DEFAULT_DEFINITION_BUILDER default builder} is returned.
|
23
|
+
# @return [#build] The builder to be used for defining new builders when
|
24
|
+
# {build build} is invoked.
|
25
|
+
# @see build
|
26
|
+
# @see DEFAULT_DEFINITION_BUILDER
|
27
|
+
def self.default_builder
|
28
|
+
return @default_builder || DEFAULT_DEFINITION_BUILDER
|
29
|
+
end
|
30
|
+
|
31
|
+
# Sets the class that is used when defining new builders when using {build
|
32
|
+
# build} to the provided **builder**. If a value of `nil` or `false` is
|
33
|
+
# provided the {DEFAULT_DEFINITION_BUILDER default builder} will be
|
34
|
+
# used.
|
35
|
+
# @param [#build] builder The object to be used when defining new builders
|
36
|
+
# when using {build build}.
|
37
|
+
# @raise [ArgumentError] Raised if the object provided does not implement
|
38
|
+
# a #build method and is neither nil nor false.
|
39
|
+
# @return [#build] The builder argument provided is returned.
|
40
|
+
# @see DEFAULT_DEFINITION_BUILDER
|
41
|
+
# @see build
|
42
|
+
def self.default_builder=(builder)
|
43
|
+
if !!builder && !builder.respond_to?(:build)
|
44
|
+
raise ArgumentError, 'Expected object that responds to #build'
|
45
|
+
end
|
46
|
+
@default_builder = builder
|
47
|
+
end
|
48
|
+
|
49
|
+
# Namespace for builder classes
|
50
|
+
module Builders; end
|
51
|
+
|
52
|
+
# @!method self.build(*args, &block)
|
53
|
+
# Builds a new object using the the {default_builder default builder}. Any
|
54
|
+
# **args** or **block** provided are delegated directly to the `build` method
|
55
|
+
# of the {default_builder default builder}.
|
56
|
+
# @return [Object] The result of invoking `build` on the
|
57
|
+
# {#default_builder default builder}.
|
58
|
+
# @see default_builder
|
59
|
+
end
|
@@ -0,0 +1,341 @@
|
|
1
|
+
module BuildingBlocks
|
2
|
+
module Builders
|
3
|
+
|
4
|
+
# A builder that builds other builders using a minimal definition DSL.
|
5
|
+
# In brief, classes generated by this builder offer a number of built-in DSL
|
6
|
+
# macros that are used to describe how methods called against instances of
|
7
|
+
# the generated builder class are translated into actions to be taken on the
|
8
|
+
# {InstanceMethods#instance object instance} being built by the generated
|
9
|
+
# builder instance.
|
10
|
+
class BuilderBuilder
|
11
|
+
|
12
|
+
# Generates a new builder class with a number of built-in DSL methods used
|
13
|
+
# to describe how actions taken against instances of the builder class are
|
14
|
+
# translated into actions to be taken on the {InstanceMethods#instance
|
15
|
+
# object instance} being built. Further information on the available DSL
|
16
|
+
# methods can be found in {ClassInstanceMethods}.
|
17
|
+
# @return [Class] The generated builder class
|
18
|
+
# @see ClassInstanceMethods
|
19
|
+
def self.build
|
20
|
+
klass = Class.new
|
21
|
+
klass.extend(ClassInstanceMethods)
|
22
|
+
klass.send(:include, InstanceMethods)
|
23
|
+
klass.module_eval(&Proc.new) if block_given?
|
24
|
+
klass
|
25
|
+
end
|
26
|
+
|
27
|
+
# Methods available to classes generated by {BuilderBuilder
|
28
|
+
# BuilderBuilder}. Provides a generic, declarative DSL for describing
|
29
|
+
# custom builder classes.
|
30
|
+
module ClassInstanceMethods
|
31
|
+
include Forwardable
|
32
|
+
|
33
|
+
# The default means by which a new instance of the
|
34
|
+
# {ClassInstanceMethods#resource_class resource class} is initialized.
|
35
|
+
# @see {ClassInstanceMethods#initialize_with)
|
36
|
+
DEFAULT_INITIALIZE_WITH = lambda { |instance| resource_class.new }
|
37
|
+
|
38
|
+
# Declares a delegate method that will be available to instances of the
|
39
|
+
# generated builder class that describes a simple attribute that can be
|
40
|
+
# delegated directly to an existing method affiliated with the object
|
41
|
+
# instance being built.
|
42
|
+
#
|
43
|
+
# Though the underlying use of {delegate delegate} gives this method an
|
44
|
+
# enormous amount of potential power, the {attribute attribute} method
|
45
|
+
# is intended for use in fairly simple situations where delegation is
|
46
|
+
# required and the `attribute` descriptor provides some semantic value.
|
47
|
+
# More complex interactions should consider using the
|
48
|
+
# {delegate delegate} method directly to give library users some insight
|
49
|
+
# into the intention for more complex behavior.
|
50
|
+
# @param [String,Symbol] attr_name The name of the method that will be
|
51
|
+
# available to instances of the generated builder class that when
|
52
|
+
# invoked will trigger the **receiver_method** on the **receiver**.
|
53
|
+
# @param [String,Symbol] receiver_method The name of the method that
|
54
|
+
# should be invoked on the **receiver** object when the **attr_name**
|
55
|
+
# method is invoked. Defaults to a setter form of **attr_name**.
|
56
|
+
# @param [String,Symbol] receiver The object or method of the
|
57
|
+
# generated builder instance that should receive invocations of
|
58
|
+
# **receiver_method** when the **attr_name** method is called.
|
59
|
+
# Defaults to the {InstanceMethods#instance object instance} being
|
60
|
+
# built.
|
61
|
+
#
|
62
|
+
# If a String or Symbol value is provided for **receiver**, the value
|
63
|
+
# provided is resolved against the {InstanceMethods#instance object
|
64
|
+
# instance} being built at runtime to determine the appropriate
|
65
|
+
# receiver.
|
66
|
+
#
|
67
|
+
# For example if a value of :foo were provided for the receiver
|
68
|
+
# argument, at runtime, the receiver would be resolved to the object
|
69
|
+
# returned by invoking :foo on the {InstanceMethods#instance object
|
70
|
+
# instance} being built. Once resolved, the **receiver_method** would
|
71
|
+
# then be invoked on the receiver with any approprate arguments.
|
72
|
+
# @return [Symbol] The Symbol identifier form of **attr_name**
|
73
|
+
# identifying the method that will be available to instances of the
|
74
|
+
# generated builder class.
|
75
|
+
# @see #delegate
|
76
|
+
def attribute(attr_name, receiver_method = :"#{attr_name}=", receiver = nil)
|
77
|
+
delegate(attr_name, receiver_method, receiver)
|
78
|
+
return attr_name.to_sym
|
79
|
+
end
|
80
|
+
|
81
|
+
# By default, builds a new instance of the configured {#resource_class
|
82
|
+
# resource class}. This is achieved by evaluating the configured
|
83
|
+
# {#initialize_with} proc in the context of an instance of the generated
|
84
|
+
# builder class to generate a new object that will be used by the
|
85
|
+
# builder instance. If a block is given, the block is evaluated in the
|
86
|
+
# context of the builder instance after initialization.
|
87
|
+
# @param [Proc] block Optional block evaluated by the generated builder
|
88
|
+
# class instance after the {InstanceMethods#instance object instance}
|
89
|
+
# being built has been initialized.
|
90
|
+
# @return [Object] Returns the generated {InstanceMethods#instance
|
91
|
+
# object instance}.
|
92
|
+
# @see #initialize_with
|
93
|
+
def build
|
94
|
+
instance = instance_eval(&self.initialize_with)
|
95
|
+
builder = new(instance)
|
96
|
+
builder.instance_eval(&Proc.new) if block_given?
|
97
|
+
instance
|
98
|
+
end
|
99
|
+
|
100
|
+
# Declares a method, **attr_name**, available to instances of the
|
101
|
+
# generated builder class, that when called delegates to a
|
102
|
+
# **builder_class** to generate an object that is then passed to the
|
103
|
+
# **receiver_method** of the **receiver**.
|
104
|
+
#
|
105
|
+
# If a **builder_class** is given, the provided **builder_class** is
|
106
|
+
# used to generate the object for the receiver. If no **builder_class**
|
107
|
+
# is given and instead a **block** is provided, the given block is
|
108
|
+
# treated as an anonymous builder definition that is passed to
|
109
|
+
# {BuildingBlocks.build} to generate a new builder class that will be
|
110
|
+
# used later when **attr_name** is invoked on an instance of the
|
111
|
+
# generated builder class.
|
112
|
+
#
|
113
|
+
# See {InstanceMethods#example_builder_attr} for details of the method
|
114
|
+
# invoked when **attr_name** is called on an instance of the generated
|
115
|
+
# builder class.
|
116
|
+
#
|
117
|
+
# @param [String,Symbol] attr_name The name that will be used to define
|
118
|
+
# the method that will be available to instances of the generated
|
119
|
+
# builder class that when invoked will trigger the **builder_class**.
|
120
|
+
# @param [String,Symbol] receiver_method The name of the method that
|
121
|
+
# should be invoked upon the **receiver** object when the
|
122
|
+
# **attr_name** method is invoked. Defaults to a setter form of
|
123
|
+
# **attr_name**.
|
124
|
+
# @param [Class] builder_class The builder class that should be used
|
125
|
+
# to generate new objects when the **attr_name** method is invoked.
|
126
|
+
# Cannot be used in conjunction with a given **block**.
|
127
|
+
# @param [Proc,String,Symbol] receiver The object or method of the
|
128
|
+
# generated builder instance that should receive invocations of
|
129
|
+
# **receiver_method** when the **attr_name** method is called.
|
130
|
+
# Defaults to the {InstanceMethods#instance object instance} being
|
131
|
+
# built.
|
132
|
+
#
|
133
|
+
# If a String or Symbol value is provided for **receiver**, the value
|
134
|
+
# provided is resolved against the {InstanceMethods#instance object
|
135
|
+
# instance} being built at runtime to determine the appropriate
|
136
|
+
# receiver.
|
137
|
+
#
|
138
|
+
# For example if a value of :foo were provided for the **receiver**
|
139
|
+
# argument, at runtime, the **receiver** would be resolved to the
|
140
|
+
# object returned by invoking :foo on the {InstanceMethods#instance
|
141
|
+
# object instance} being built. Once resolved, the **receiver_method**
|
142
|
+
# would then be invoked on the **receiver** with any approprate
|
143
|
+
# arguments.
|
144
|
+
#
|
145
|
+
# If a Proc is provided for the **receiver** argument, the Proc will
|
146
|
+
# be evaluated at runtime in the context of the
|
147
|
+
# {InstanceMethods#instance object instance} being built and should
|
148
|
+
# return the intended **receiver** object.
|
149
|
+
# @param [Proc] block A block may be provided in place of a predefined
|
150
|
+
# **builder_class**. If such a block is provided, the given block is
|
151
|
+
# evaluated by {BuildingBlocks.build} to generate a new anonymous
|
152
|
+
# builder class. Cannot be used in conjunction with a predefined
|
153
|
+
# **builder_class**.
|
154
|
+
# @raise [ArgumentError] Raised if both a **builder_class** and a
|
155
|
+
# **block** are given or neither a **builder_class** nor **block** are
|
156
|
+
# given.
|
157
|
+
# @return [Symbol] The Symbol identifier form of **attr_name**
|
158
|
+
# identifying the method that will be available to instances of the
|
159
|
+
# generated builder class.
|
160
|
+
# @see BuildingBlocks.build
|
161
|
+
# @see #define_builder_method
|
162
|
+
# @see InstanceMethods#example_builder_attr
|
163
|
+
def builder(attr_name, receiver_method = "#{attr_name}=", builder_class = nil, receiver = nil)
|
164
|
+
if block_given? ^ builder_class.nil?
|
165
|
+
raise ArgumentError, 'Either a builder class or a builder definition is required'
|
166
|
+
elsif block_given?
|
167
|
+
builder_class = BuildingBlocks.build(&Proc.new)
|
168
|
+
end
|
169
|
+
|
170
|
+
if receiver.is_a?(Proc)
|
171
|
+
receiver_proc = receiver
|
172
|
+
elsif receiver.is_a?(Symbol) || receiver.is_a?(String)
|
173
|
+
receiver_proc = instance_eval("lambda { |i| @instance.#{receiver} }")
|
174
|
+
else
|
175
|
+
receiver_proc = lambda { |i| @instance }
|
176
|
+
end
|
177
|
+
|
178
|
+
define_builder_method(attr_name, receiver_method, builder_class, receiver_proc)
|
179
|
+
attr_name.to_sym
|
180
|
+
end
|
181
|
+
|
182
|
+
# Declares a delegate method that will be available to instances of the
|
183
|
+
# generated builder class that describes a behavior that should be
|
184
|
+
# delegated from the builder instance to the **receiver_method** of the
|
185
|
+
# derived **receiver**.
|
186
|
+
#
|
187
|
+
# @param [String,Symbol] attr_name An identifer that will be used to
|
188
|
+
# name the delegate method defined on the builder instance.
|
189
|
+
# @param [String,Symbol] receiver_method The method that should be
|
190
|
+
# invoked on the **receiver** when the delegate method is invoked.
|
191
|
+
# Defaults to the same value provided for **attr_name**.
|
192
|
+
# @param [String,Symbol] receiver The object or method of the
|
193
|
+
# generated builder instance that should receive invocations of
|
194
|
+
# **receiver_method** when the **attr_name** method is called.
|
195
|
+
# Defaults to the {InstanceMethods#instance object instance} being
|
196
|
+
# built.
|
197
|
+
#
|
198
|
+
# If a String or Symbol value is provided for **receiver**, the value
|
199
|
+
# provided is resolved against the {InstanceMethods#instance object
|
200
|
+
# instance} being built at runtime to determine the appropriate
|
201
|
+
# **receiver**.
|
202
|
+
#
|
203
|
+
# For example if a value of :foo were provided for the **receiver**
|
204
|
+
# argument, at runtime, the **receiver** would be resolved to the
|
205
|
+
# object returned by invoking :foo on the {InstanceMethods#instance
|
206
|
+
# object instance} being built. Once resolved, the **receiver_method**
|
207
|
+
# would then be invoked on the **receiver** with any approprate
|
208
|
+
# arguments.
|
209
|
+
# @return [Symbol] The Symbol identifier form of **attr_name**
|
210
|
+
# identifying the method that will be available to instances of the
|
211
|
+
# generated builder class.
|
212
|
+
def delegate(attr_name, receiver_method = attr_name, receiver = nil)
|
213
|
+
receiver = receiver.nil? || receiver.empty? ? :@instance : :"@instance.#{receiver}"
|
214
|
+
def_delegator(receiver, receiver_method, attr_name)
|
215
|
+
attr_name.to_sym
|
216
|
+
end
|
217
|
+
|
218
|
+
# When no block is given, returns the proc currently configured to be
|
219
|
+
# invoked when a new instance of {#resource_class resource class} is
|
220
|
+
# needed. If a custom proc has not been configured, returns the
|
221
|
+
# {DEFAULT_INITIALIZE_WITH default initialization proc}.
|
222
|
+
#
|
223
|
+
# When a block is given, the method acts as a setter and the provided
|
224
|
+
# block is preserved and later used to initialize new instances of
|
225
|
+
# {#resource_class resource class} as they are required.
|
226
|
+
# @return [Proc] The proc that will be used to initialize new instances
|
227
|
+
# of {#resouce_class resource class}.
|
228
|
+
def initialize_with
|
229
|
+
@initialize_with = Proc.new if block_given?
|
230
|
+
@initialize_with || DEFAULT_INITIALIZE_WITH
|
231
|
+
end
|
232
|
+
|
233
|
+
# Sets a custom proc to be used when initializing new instances of
|
234
|
+
# {#resource_class resource class}.
|
235
|
+
# @param [Proc] initializer The proc to be used to initialize new
|
236
|
+
# instances or {resource_class resource class}.
|
237
|
+
# @raise [ArgumentError] Raised if the provided **initializer** is
|
238
|
+
# missing or is not a proc.
|
239
|
+
# @return [Proc] The **initializer** argument provided is returned.
|
240
|
+
def initialize_with=(initializer)
|
241
|
+
unless initializer.is_a?(Proc)
|
242
|
+
raise ArgumentError, "Expected proc initializer, got #{initializer.class.name}"
|
243
|
+
end
|
244
|
+
@initialize_with = initializer
|
245
|
+
end
|
246
|
+
|
247
|
+
# The class that the generated builder class should generate instances
|
248
|
+
# of. When no value is passed for the **klass** argument, returns the
|
249
|
+
# currently configured resource class. If a value is provided for
|
250
|
+
# **klass**, the method acts as a setter and the provided **klass**
|
251
|
+
# argument is preserved and later used for the resource class of the
|
252
|
+
# generated builder class. For further information on how instances of
|
253
|
+
# the {#resource_class resource class} are built, see {#initialize_with}.
|
254
|
+
# @return [Object] The class that the generated builder class will
|
255
|
+
# generate instances of.
|
256
|
+
# @see #initialize_with
|
257
|
+
def resource_class(klass = nil)
|
258
|
+
@resource_class = klass unless klass.nil?
|
259
|
+
@resource_class
|
260
|
+
end
|
261
|
+
|
262
|
+
# Sets the class that the generated builder class will generate
|
263
|
+
# instances of. For further information on how instances of the
|
264
|
+
# {resource_class resource class} are built, see {#initialize_with}.
|
265
|
+
# @return [Object] The value given for the **klass** argument is
|
266
|
+
# returned.
|
267
|
+
# @see #initialize_with
|
268
|
+
def resource_class=(klass)
|
269
|
+
@resource_class = klass
|
270
|
+
end
|
271
|
+
|
272
|
+
private
|
273
|
+
|
274
|
+
# Handles the wiring required to invoke a builder when **attr_name** is
|
275
|
+
# invoked.
|
276
|
+
def define_builder_method(attr_name, receiver_method, builder_class, receiver_proc)
|
277
|
+
define_method(attr_name) do |existing_object = nil, &block|
|
278
|
+
begin
|
279
|
+
if block.nil? ^ !!existing_object
|
280
|
+
raise ArgumentError, 'Either a static instance or a builder proc is required'
|
281
|
+
end
|
282
|
+
receiver = instance_eval(&receiver_proc)
|
283
|
+
instance = block.nil? ? existing_object : builder_class.build(&block)
|
284
|
+
receiver.send(receiver_method, instance)
|
285
|
+
instance
|
286
|
+
rescue Exception
|
287
|
+
$@.delete_if{|s| %r"#{Regexp.quote(__FILE__)}"o =~ s}
|
288
|
+
::Kernel::raise
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Methods available to instances of a generated builder class.
|
295
|
+
module InstanceMethods
|
296
|
+
# Initializes a new instance of the generated builder class. Requires
|
297
|
+
# the {ClassInstanceMethods#resource_class resource class} **instance**
|
298
|
+
# that the builder will populate to be provided.
|
299
|
+
# @param [Object] instance The object that should be populated by the
|
300
|
+
# builder instance.
|
301
|
+
def initialize(instance)
|
302
|
+
@instance = instance
|
303
|
+
end
|
304
|
+
|
305
|
+
# Returns the {InstanceMethods#instance object instance} being built by
|
306
|
+
# the builder instance.
|
307
|
+
# @return [Object] The {InstanceMethods#instance object instance} being
|
308
|
+
# built by the builder.
|
309
|
+
def instance
|
310
|
+
return @instance
|
311
|
+
end
|
312
|
+
|
313
|
+
# @!method example_builder_attr(existing_object = nil, &block)
|
314
|
+
# Reference for instance methods defined via {ClassInstanceMethods#builder}.
|
315
|
+
#
|
316
|
+
# This method is not actually defined, but serves as a reference for
|
317
|
+
# the instance method defined when an attribute is defined on the
|
318
|
+
# parent builder class using {ClassInstanceMethods#builder}.
|
319
|
+
#
|
320
|
+
# Invokes the `receiver_method` of `receiver` with the provided
|
321
|
+
# **existing_object** or a new object generated by invoking the
|
322
|
+
# *build* method of the derived `builder_class` with the given
|
323
|
+
# **block**.
|
324
|
+
# @param [Object] existing_object An existing object to use as the
|
325
|
+
# argument to `receiver_method`. Cannot be used in conjunction
|
326
|
+
# with a block.
|
327
|
+
# @param [Proc] block A block to be given to the *build* method of
|
328
|
+
# the derived `builder_class` to generate a new object. Cannot be
|
329
|
+
# used in conjunction with a provided **existing_object**.
|
330
|
+
# @raise [ArgumentError] Raised if both an **existing_object** and
|
331
|
+
# **block** are given or neither an **existing_object** nor
|
332
|
+
# **block** are provided.
|
333
|
+
# @return [Object] The provided **existing_object** or the object
|
334
|
+
# generated by invoking the *build* method of the `builder_class`
|
335
|
+
# with the given **block**.
|
336
|
+
# @see ClassInstanceMethods#builder
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,406 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module BuildingBlocks
|
4
|
+
module Builders
|
5
|
+
class BuilderBuilderTest < MiniTest::Test
|
6
|
+
|
7
|
+
context 'BuilderBuilder class methods' do
|
8
|
+
|
9
|
+
should 'generate a new Builder class without block given' do
|
10
|
+
builder = BuilderBuilder.build
|
11
|
+
assert_kind_of Class, builder
|
12
|
+
assert builder.ancestors.include?(BuildingBlocks::Builders::BuilderBuilder::InstanceMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
should 'generate a new Builder class with block given' do
|
17
|
+
new_block_arg = new_self = nil
|
18
|
+
# Validates block form
|
19
|
+
builder = BuilderBuilder.build do |subclass|
|
20
|
+
# Leave marker of instance evaluation
|
21
|
+
define_singleton_method(:builder_evaluated) { true }
|
22
|
+
# Test usage of block argument
|
23
|
+
subclass.resource_class = Hash
|
24
|
+
# Set up variables for evaluation back in the test
|
25
|
+
new_self = self
|
26
|
+
new_block_arg = subclass
|
27
|
+
end
|
28
|
+
|
29
|
+
# Validate DSL context variables
|
30
|
+
assert_equal builder, new_block_arg
|
31
|
+
assert_equal builder, new_self
|
32
|
+
|
33
|
+
# Validate assignments and markers
|
34
|
+
assert_equal Hash, builder.resource_class
|
35
|
+
assert_equal true, builder.builder_evaluated
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
context 'BuilderBuilder generated Builder class methods' do
|
42
|
+
|
43
|
+
setup do
|
44
|
+
@struct = struct = Struct.new(:key, :value)
|
45
|
+
@dict_builder = BuildingBlocks.build do
|
46
|
+
resource_class struct
|
47
|
+
attribute(:key)
|
48
|
+
attribute(:value)
|
49
|
+
end
|
50
|
+
@builder = BuildingBlocks.build
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
context '::attribute' do
|
55
|
+
|
56
|
+
should 'raise ArgumentError unless attribute name provided' do
|
57
|
+
assert_raises(ArgumentError) { BuildingBlocks.build { attribute } }
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
should 'delegate calls to attr_name to receiver via receiver_method' do
|
62
|
+
@builder.expects(:def_delegator).with(:'@instance.c', :b, :a,)
|
63
|
+
@builder.attribute(:a, :b, :c)
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
should 'delegate to attr_name= on receiver by default' do
|
68
|
+
@builder.expects(:def_delegator).with(:@instance, :a=, :a,)
|
69
|
+
@builder.attribute(:a)
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
should 'delegate directly to the @instance by default' do
|
74
|
+
@builder.expects(:def_delegator).with(:@instance, :a=, :a)
|
75
|
+
@builder.attribute(:a)
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
should 'return the Symbol form of the provided attr_name' do
|
80
|
+
result = @builder.attribute(:a)
|
81
|
+
assert_equal :a, result
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
context '::builder' do
|
88
|
+
|
89
|
+
should 'raise an error unless a Builder class xor proc is provided' do
|
90
|
+
assert_raises(ArgumentError) do
|
91
|
+
BuildingBlocks.build { builder(:foo) }
|
92
|
+
end
|
93
|
+
|
94
|
+
dict_builder = @dict_builder
|
95
|
+
assert_raises(ArgumentError) do
|
96
|
+
BuildingBlocks.build { builder(:foo, :foo=, dict_builder) { |i| } }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
should 'raise an error unless an attr_name is provided' do
|
102
|
+
assert_raises(ArgumentError) { BuildingBlocks.build { builder } }
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
should 'return the Symbol form of the provided attr_name' do
|
107
|
+
dict_builder = @dict_builder
|
108
|
+
result = @builder.builder(:foo, :value=, dict_builder)
|
109
|
+
assert_equal :foo, result
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
should 'take a custom receiver method name' do
|
114
|
+
dict_builder, struct = @dict_builder, @struct
|
115
|
+
builder = BuildingBlocks.build do
|
116
|
+
resource_class struct
|
117
|
+
builder(:foo, :value=, dict_builder)
|
118
|
+
end
|
119
|
+
instance = builder.build { foo { key(:a); value(:b) } }
|
120
|
+
assert_equal :a, instance.value.key
|
121
|
+
assert_equal :b, instance.value.value
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
should 'take a custom receiver proc' do
|
126
|
+
dict_builder, struct = @dict_builder, @struct
|
127
|
+
builder = BuildingBlocks.build do
|
128
|
+
resource_class struct
|
129
|
+
builder(:foo, :value=, dict_builder, lambda { |i| String })
|
130
|
+
end
|
131
|
+
String.expects(:value=)
|
132
|
+
builder.build { foo { key(:a); value(:b) } }
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
should 'should accept a proc that is a Builder definition' do
|
137
|
+
struct = @struct
|
138
|
+
builder = BuildingBlocks.build do
|
139
|
+
resource_class struct
|
140
|
+
builder(:foo, :value=) do |i|
|
141
|
+
resource_class struct
|
142
|
+
attribute :bar, :key=
|
143
|
+
end
|
144
|
+
end
|
145
|
+
instance = builder.build { foo { bar :a } }
|
146
|
+
assert_equal :a, instance.value.key
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
should 'accept a custom builder class' do
|
151
|
+
dict_builder, struct = @dict_builder, @struct
|
152
|
+
db = dict_builder.build
|
153
|
+
dict_builder.expects(:build).returns(db)
|
154
|
+
builder = BuildingBlocks.build do
|
155
|
+
resource_class struct
|
156
|
+
builder(:foo, :value=, dict_builder)
|
157
|
+
end
|
158
|
+
instance = builder.build { foo { |i| } }
|
159
|
+
assert_equal db, instance.value
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
should 'accept a custom receiver identifier' do
|
164
|
+
dict_builder, struct = @dict_builder, @struct
|
165
|
+
builder = BuildingBlocks.build do
|
166
|
+
resource_class struct
|
167
|
+
builder(:value, :value=, dict_builder)
|
168
|
+
builder(:foo, :value=, dict_builder, :value)
|
169
|
+
end
|
170
|
+
instance = builder.build do
|
171
|
+
value { key(:a) }
|
172
|
+
foo { |i| value(:b) }
|
173
|
+
end
|
174
|
+
assert_equal :b, instance.value.value.value
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
context '::delegate' do
|
181
|
+
|
182
|
+
should 'raise ArgumentError unless method_name provided' do
|
183
|
+
assert_raises(ArgumentError) { @builder.delegate }
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
should 'create a delegator to @instance.receiver.receiver_method aliased to method_name' do
|
188
|
+
@builder.expects(:def_delegator).with(:'@instance.receiver', :receiver_method, :method_name)
|
189
|
+
@builder.delegate(:method_name, :receiver_method, :receiver)
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
should 'create a delegator to @instance.receiver_method aliased to method_name by default' do
|
194
|
+
@builder.expects(:def_delegator).with(:@instance, :receiver_method, :method_name)
|
195
|
+
@builder.delegate(:method_name, :receiver_method)
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
should 'create a delegator to @instance.method_name aliased to method_name by default' do
|
200
|
+
@builder.expects(:def_delegator).with(:@instance, :method_name, :method_name)
|
201
|
+
@builder.delegate(:method_name)
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
should 'return the symbol identifier form of attr_name' do
|
206
|
+
assert_equal :method_name, @builder.delegate('method_name')
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
context '::new' do
|
213
|
+
|
214
|
+
should 'accept a block and evaluate the block on the builder instance' do
|
215
|
+
struct = @dict_builder.build do
|
216
|
+
key :a
|
217
|
+
value 'a'
|
218
|
+
end
|
219
|
+
assert_equal :a, struct.key
|
220
|
+
assert_equal 'a', struct.value
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
should 'return the generated instance when no block given' do
|
225
|
+
the_struct = @struct.new
|
226
|
+
the_struct.key = :a
|
227
|
+
the_struct.value = 'a'
|
228
|
+
@struct.expects(:new).returns(the_struct)
|
229
|
+
|
230
|
+
struct = @dict_builder.build
|
231
|
+
assert_equal :a, struct.key
|
232
|
+
assert_equal 'a', struct.value
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
should 'utilize a custom initialize_with proc if available' do
|
237
|
+
@dict_builder.initialize_with do |builder|
|
238
|
+
instance = resource_class.new
|
239
|
+
instance.key = :b
|
240
|
+
instance.value = 'b'
|
241
|
+
instance
|
242
|
+
end
|
243
|
+
|
244
|
+
struct = @dict_builder.build
|
245
|
+
assert_equal :b, struct.key
|
246
|
+
assert_equal 'b', struct.value
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
|
252
|
+
context '::initialize_with' do
|
253
|
+
|
254
|
+
setup do
|
255
|
+
@initializer = lambda { |i| Hash.new }
|
256
|
+
@dict_builder.initialize_with(&@initializer)
|
257
|
+
end
|
258
|
+
|
259
|
+
|
260
|
+
should 'set @initialize_with if given a block' do
|
261
|
+
assert_equal @dict_builder.initialize_with, @initializer
|
262
|
+
assert_equal @dict_builder.instance_variable_get(:@initialize_with), @initializer
|
263
|
+
end
|
264
|
+
|
265
|
+
|
266
|
+
should 'return @initialize_with if no block given' do
|
267
|
+
assert_equal @dict_builder.instance_variable_get(:@initialize_with), @dict_builder.initialize_with
|
268
|
+
end
|
269
|
+
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
context '::initialize_with=' do
|
274
|
+
|
275
|
+
setup do
|
276
|
+
@initializer = lambda { |i| Hash.new }
|
277
|
+
end
|
278
|
+
|
279
|
+
|
280
|
+
should 'raise an error if a non-proc is provided' do
|
281
|
+
assert_raises(ArgumentError) do
|
282
|
+
@dict_builder.initialize_with = :foo
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
|
287
|
+
should 'set @initialize_with if given a proc' do
|
288
|
+
@dict_builder.initialize_with = @initializer
|
289
|
+
assert_equal @dict_builder.initialize_with, @initializer
|
290
|
+
assert_equal @dict_builder.instance_variable_get(:@initialize_with), @initializer
|
291
|
+
end
|
292
|
+
|
293
|
+
end
|
294
|
+
|
295
|
+
|
296
|
+
context '::resource_class' do
|
297
|
+
|
298
|
+
should 'return the current resource_class when no arguments are given' do
|
299
|
+
assert_equal nil, @builder.resource_class
|
300
|
+
@builder.resource_class = @struct
|
301
|
+
assert_equal @struct, @builder.resource_class
|
302
|
+
end
|
303
|
+
|
304
|
+
|
305
|
+
should 'set the current resource_class when an argument is given' do
|
306
|
+
@builder.resource_class(@struct)
|
307
|
+
assert_equal @struct, @builder.resource_class
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
should 'set and return the argument given when given an argument' do
|
312
|
+
assert_equal @struct, @builder.resource_class(@struct)
|
313
|
+
end
|
314
|
+
|
315
|
+
end
|
316
|
+
|
317
|
+
|
318
|
+
context '::resource_class=' do
|
319
|
+
|
320
|
+
should 'set the current resource_class' do
|
321
|
+
@builder.resource_class = @struct
|
322
|
+
assert_equal @struct, @builder.resource_class
|
323
|
+
end
|
324
|
+
|
325
|
+
|
326
|
+
should 'return the argument given' do
|
327
|
+
assert_equal @struct, @builder.resource_class = @struct
|
328
|
+
end
|
329
|
+
|
330
|
+
end
|
331
|
+
|
332
|
+
end
|
333
|
+
|
334
|
+
|
335
|
+
context 'BuilderBuilder generated Builder class instance methods' do
|
336
|
+
|
337
|
+
setup do
|
338
|
+
@struct = struct = Struct.new(:key, :value)
|
339
|
+
@dict_builder = BuildingBlocks.build do
|
340
|
+
resource_class struct
|
341
|
+
attribute(:key)
|
342
|
+
attribute(:value)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
|
347
|
+
context '::builder generated instance method' do
|
348
|
+
|
349
|
+
setup do
|
350
|
+
dict_builder, struct = @dict_builder, @struct
|
351
|
+
@builder_instance = BuildingBlocks.build do
|
352
|
+
resource_class struct
|
353
|
+
builder(:foo, :value=, dict_builder)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
|
358
|
+
should 'raise ArgumentError if given static instance and block or neither' do
|
359
|
+
assert_raises(ArgumentError) { @builder_instance.build { foo } }
|
360
|
+
assert_raises(ArgumentError) do
|
361
|
+
@builder_instance.build { foo(true) {|i| } }
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
|
366
|
+
should 'pass a static instance to the receiver method of the receiver' do
|
367
|
+
instance = @builder_instance.build { foo(true) }
|
368
|
+
assert_equal true, instance.value
|
369
|
+
end
|
370
|
+
|
371
|
+
|
372
|
+
should 'evaluate builder definition and send instance to the receiver method of receiver' do
|
373
|
+
instance = @builder_instance.build { foo { value(true) } }
|
374
|
+
assert_equal true, instance.value.value
|
375
|
+
end
|
376
|
+
|
377
|
+
end
|
378
|
+
|
379
|
+
|
380
|
+
context '#initialize' do
|
381
|
+
|
382
|
+
should 'require an instance' do
|
383
|
+
assert_raises(ArgumentError) { @dict_builder.new }
|
384
|
+
struct = @struct.new(:k, :v)
|
385
|
+
builder = @dict_builder.new(struct)
|
386
|
+
assert_equal struct, builder.instance
|
387
|
+
end
|
388
|
+
|
389
|
+
end
|
390
|
+
|
391
|
+
|
392
|
+
context '#instance' do
|
393
|
+
|
394
|
+
should 'return the object instance being built' do
|
395
|
+
struct = @struct.new(:k, :v)
|
396
|
+
builder = @dict_builder.new(struct)
|
397
|
+
assert_equal struct, builder.instance
|
398
|
+
end
|
399
|
+
|
400
|
+
end
|
401
|
+
|
402
|
+
end
|
403
|
+
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class BuildingBlocksTest < MiniTest::Test
|
4
|
+
|
5
|
+
DEFAULT_BUILDER = BuildingBlocks::DEFAULT_DEFINITION_BUILDER
|
6
|
+
|
7
|
+
context 'BuildingBlocks' do
|
8
|
+
|
9
|
+
should 'be defined' do
|
10
|
+
assert BuildingBlocks
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
context 'Factory methods' do
|
15
|
+
|
16
|
+
context '::build' do
|
17
|
+
|
18
|
+
setup do
|
19
|
+
@builder = BuildingBlocks.build
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
should 'return a default Builder instance when no block given' do
|
24
|
+
assert_kind_of Class, @builder
|
25
|
+
assert @builder.ancestors.include?(BuildingBlocks::Builders::BuilderBuilder::InstanceMethods)
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
should 'return an evaluated Builder instance when block is given' do
|
30
|
+
struct = Struct.new(:key, :value)
|
31
|
+
|
32
|
+
dict_builder = BuildingBlocks.build do |b|
|
33
|
+
resource_class struct
|
34
|
+
attribute :key
|
35
|
+
attribute :value
|
36
|
+
end
|
37
|
+
|
38
|
+
struct_dict_builder = BuildingBlocks.build do |i|
|
39
|
+
resource_class struct
|
40
|
+
attribute(:key)
|
41
|
+
builder(:value, :value=, dict_builder)
|
42
|
+
delegate(:inner_value, :value=, 'value')
|
43
|
+
end
|
44
|
+
|
45
|
+
dict = dict_builder.build do |b|
|
46
|
+
key(:a)
|
47
|
+
value(:b)
|
48
|
+
end
|
49
|
+
assert_equal :a, dict.key
|
50
|
+
assert_equal :b, dict.value
|
51
|
+
|
52
|
+
struct_dict = struct_dict_builder.build do |b|
|
53
|
+
key(:c)
|
54
|
+
value do
|
55
|
+
key(:d)
|
56
|
+
end
|
57
|
+
inner_value(:e)
|
58
|
+
end
|
59
|
+
assert_equal :c, struct_dict.key
|
60
|
+
assert_equal :d, struct_dict.value.key
|
61
|
+
assert_equal :e, struct_dict.value.value
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
context 'configuration' do
|
70
|
+
|
71
|
+
setup do
|
72
|
+
@original_config = BuildingBlocks.default_builder
|
73
|
+
@simple_builder = Class.new do
|
74
|
+
attr_accessor :args, :block
|
75
|
+
def self.build(*args)
|
76
|
+
instance = new
|
77
|
+
instance.block = Proc.new if block_given?
|
78
|
+
instance.args = args
|
79
|
+
instance
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
teardown do
|
86
|
+
BuildingBlocks.default_builder = @original_config
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
context '::default_builder' do
|
91
|
+
|
92
|
+
should 'return @default_defintion_builder or DEFAULT_DEFINITION_BUILDER' do
|
93
|
+
BuildingBlocks.default_builder = nil
|
94
|
+
assert_equal BuildingBlocks.default_builder, DEFAULT_BUILDER
|
95
|
+
BuildingBlocks.default_builder = @simple_builder
|
96
|
+
assert_equal BuildingBlocks.default_builder, @simple_builder
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
context '::default_builder=' do
|
103
|
+
|
104
|
+
[nil, false].each do |value|
|
105
|
+
should "accept #{value.inspect} to reset the default_builder" do
|
106
|
+
BuildingBlocks.default_builder = @simple_builder
|
107
|
+
assert_equal BuildingBlocks.default_builder, @simple_builder
|
108
|
+
BuildingBlocks.default_builder = value
|
109
|
+
assert_equal BuildingBlocks.default_builder, DEFAULT_BUILDER
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
should 'raise an error if the proivded object does not respond to #build' do
|
115
|
+
BuildingBlocks.default_builder = nil
|
116
|
+
assert_equal BuildingBlocks.default_builder, DEFAULT_BUILDER
|
117
|
+
assert_raises(ArgumentError) do
|
118
|
+
BuildingBlocks.default_builder = Class.new
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
should 'set the default_builder' do
|
124
|
+
BuildingBlocks.default_builder = nil
|
125
|
+
assert_equal BuildingBlocks.default_builder, DEFAULT_BUILDER
|
126
|
+
BuildingBlocks.default_builder = @simple_builder
|
127
|
+
assert_equal BuildingBlocks.default_builder, @simple_builder
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: building_blocks
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Danny Guinther
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Simple DSL for defining block-based Object builders
|
42
|
+
email:
|
43
|
+
- dannyguinther@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- ".travis.yml"
|
50
|
+
- ".yardopts"
|
51
|
+
- Gemfile
|
52
|
+
- Guardfile
|
53
|
+
- LICENSE
|
54
|
+
- README.md
|
55
|
+
- Rakefile
|
56
|
+
- building_blocks.gemspec
|
57
|
+
- lib/building_blocks.rb
|
58
|
+
- lib/building_blocks/builders/builder_builder.rb
|
59
|
+
- lib/building_blocks/version.rb
|
60
|
+
- test/test_helper.rb
|
61
|
+
- test/unit/builders/builder_builder_test.rb
|
62
|
+
- test/unit/building_blocks_test.rb
|
63
|
+
homepage: https://github.com/interval-braining/building_blocks
|
64
|
+
licenses:
|
65
|
+
- MIT
|
66
|
+
metadata: {}
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 2.2.2
|
84
|
+
signing_key:
|
85
|
+
specification_version: 4
|
86
|
+
summary: Block-based Object Builders
|
87
|
+
test_files:
|
88
|
+
- test/test_helper.rb
|
89
|
+
- test/unit/builders/builder_builder_test.rb
|
90
|
+
- test/unit/building_blocks_test.rb
|
91
|
+
has_rdoc:
|