building_blocks 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/interval-braining/building_blocks.png)](https://travis-ci.org/interval-braining/building_blocks)
|
3
|
+
[![Coverage Status](https://coveralls.io/repos/interval-braining/building_blocks/badge.png)](https://coveralls.io/r/interval-braining/building_blocks)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/interval-braining/building_blocks.png)](https://codeclimate.com/github/interval-braining/building_blocks)
|
5
|
+
[![Dependency Status](https://gemnasium.com/interval-braining/building_blocks.png)](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:
|