module_builder 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +23 -0
- data/LICENSE.md +21 -0
- data/README.md +289 -0
- data/Rakefile +29 -0
- data/lib/module_builder.rb +4 -0
- data/lib/module_builder/buildable.rb +113 -0
- data/lib/module_builder/builder.rb +130 -0
- data/lib/module_builder/error.rb +8 -0
- data/lib/module_builder/version.rb +5 -0
- data/module_builder.gemspec +22 -0
- metadata +69 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e7a6ca0b59291dc18d4c0dc812248ec345b9ea41
|
4
|
+
data.tar.gz: b736892bcd79e3e21dc39f207187314e1410f497
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 448d824a8ac9754dd25df4fee94e108757aa70073a198dc28906f46ac4452f8ff1935524051608775384570a4ecad583b26b32f45726a99e852cdd135b1b2911
|
7
|
+
data.tar.gz: 495736ff1c2209579e8bfbd6e8ebf51661cb043505f142d46fa409532c5a9f4ec902fa1dff33fb21546f6806b5d65b3f431c5b7090a7c484be4a95fb89a6823a
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file. This
|
4
|
+
project adheres to [Semantic Versioning 2.0.0][semver]. Any violations of this
|
5
|
+
scheme are considered to be bugs.
|
6
|
+
|
7
|
+
[semver]: http://semver.org/spec/v2.0.0.html
|
8
|
+
|
9
|
+
## [0.1.0] - 2015-11-21
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- Stateless inclusions via the `Builder#inclusions` extension method.
|
14
|
+
- Stateless extension via the `Builder#extensions` extension method.
|
15
|
+
- Defined hooks via the `Builder#hooks` extension method.
|
16
|
+
- Stateful building via passing state into the Builder constructor and using
|
17
|
+
the resulting state in a defined hook.
|
18
|
+
- Buildable module to mix into modules that you want to be able to build.
|
19
|
+
- Default state via the `Builder#defaults` extension method.
|
20
|
+
- Including a `Buildable` module includes the default built version in the
|
21
|
+
descendant class or module.
|
22
|
+
|
23
|
+
[0.1.0]: https://github.com/michaelherold/module_builder/tree/v0.1.0
|
data/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Michael Herold
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,289 @@
|
|
1
|
+
# ModuleBuilder
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/michaelherold/module_builder.svg)][travis]
|
4
|
+
[![Code Climate](https://codeclimate.com/github/michaelherold/module_builder/badges/gpa.svg)][codeclimate]
|
5
|
+
[![Inline docs](http://inch-ci.org/github/michaelherold/module_builder.svg?branch=master)][inch]
|
6
|
+
|
7
|
+
[codeclimate]: https://codeclimate.com/github/michaelherold/module_builder
|
8
|
+
[inch]: http://inch-ci.org/github/michaelherold/module_builder
|
9
|
+
[travis]: https://travis-ci.org/michaelherold/module_builder
|
10
|
+
|
11
|
+
ModuleBuilder gives you the ability to create modules that are customizable for
|
12
|
+
different situations. Are you creating a module that has an adapter, but don't
|
13
|
+
want to expose a setter on the including class because it's not a public API?
|
14
|
+
ModuleBuilder can help with that! Do you have two implementations of some
|
15
|
+
behavior that have different tradeoffs? Do you want to offer both through one
|
16
|
+
easy-to-use syntax? ModuleBuilder can do that too!
|
17
|
+
|
18
|
+
Come see what ModuleBuilder will help you with today!
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Add this line to your application's Gemfile:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem "module_builder"
|
26
|
+
```
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install module_builder
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
ModuleBuilder revolves around the concept of Builders that are responsible for
|
39
|
+
building modules based on your specification. For convenience, there is a
|
40
|
+
`Buildable` module that you can mix in to give your module building
|
41
|
+
superpowers.
|
42
|
+
|
43
|
+
You configure Builders through two channels: class-level configuration and
|
44
|
+
instance-level state. There are three types of class-level configuration:
|
45
|
+
stateless inclusions, stateless extensions, and defined hooks.
|
46
|
+
|
47
|
+
Stateless methods do not give much more power than just using a standard set of
|
48
|
+
`include`s and `extend`s. However, they help you organize your code in an
|
49
|
+
easily extensible fashion.
|
50
|
+
|
51
|
+
You can use instance-level state within defined hooks to customize the behavior
|
52
|
+
of the built module. If you need to conditionally define a method based on the
|
53
|
+
configuration, you want to look here.
|
54
|
+
|
55
|
+
### Stateless Inclusions
|
56
|
+
|
57
|
+
The builder simply `include`s stateless inclusions into the built module. There
|
58
|
+
are no customization hooks here, just a single place to specify all the modules
|
59
|
+
you want to `include` in your built module.
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
class StatelessInclusionBuilder < ModuleBuilder::Builder
|
63
|
+
def inclusions
|
64
|
+
[Comparable, Enumerable]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
StatelessInclusionBuilder.new.module.ancestors
|
69
|
+
#=> [#<Module>, Enumerable, Comparable]
|
70
|
+
```
|
71
|
+
|
72
|
+
### Stateless Extensions
|
73
|
+
|
74
|
+
The builder adds all stateless extensions into the `Module#extended` hook of
|
75
|
+
the built module so they are extended onto anything that extends the built
|
76
|
+
module.
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
module Quack
|
80
|
+
def quack
|
81
|
+
"quack"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class QuackingBuilder < ModuleBuilder::Builder
|
86
|
+
def extensions
|
87
|
+
[Quack]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class Duck
|
92
|
+
extend QuackingBuilder.new.module
|
93
|
+
end
|
94
|
+
|
95
|
+
Duck.quack #=> "quack"
|
96
|
+
```
|
97
|
+
|
98
|
+
### Defined Hooks
|
99
|
+
|
100
|
+
Defined hooks are where you can do the heavy customization when building a
|
101
|
+
module. They are arbitrary methods that the builder invokes during its
|
102
|
+
initialization. Add any behavior that you want to make customizable via the
|
103
|
+
state that you give to the builder as a defined hook.
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
module Walk
|
107
|
+
def walk
|
108
|
+
"step, step, step"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class WalkingBuilder < ModuleBuilder::Builder
|
113
|
+
def hooks
|
114
|
+
[:rename_walk]
|
115
|
+
end
|
116
|
+
|
117
|
+
def inclusions
|
118
|
+
[Walk]
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def rename_walk
|
124
|
+
return unless @walk_method
|
125
|
+
|
126
|
+
@module.__send__(:alias_method, @walk_method, :walk)
|
127
|
+
@module.__send__(:undef_method, :walk)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class Duck
|
132
|
+
include WalkingBuilder.new(:walk_method => :waddle).module
|
133
|
+
end
|
134
|
+
|
135
|
+
Duck.new.waddle #=> "step, step, step"
|
136
|
+
Duck.new.walk #=> NoMethodError
|
137
|
+
```
|
138
|
+
|
139
|
+
### Buildable Module
|
140
|
+
|
141
|
+
Explicitly instantiating a Builder and accessing the module that it built is
|
142
|
+
clunky. To gain easy access to a consistent syntax for your module, you can
|
143
|
+
include the `Buildable` module and specify the builder you want to use when
|
144
|
+
building your module.
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
class MyBuilder < ModuleBuilder::Builder
|
148
|
+
end
|
149
|
+
|
150
|
+
module BuildableExample
|
151
|
+
include ModuleBuilder::Buildable
|
152
|
+
|
153
|
+
builder MyBuilder
|
154
|
+
end
|
155
|
+
|
156
|
+
module IncludingModule
|
157
|
+
include BuildableExample.new(:state => "value")
|
158
|
+
end
|
159
|
+
```
|
160
|
+
|
161
|
+
`Buildable` defaults to using a `Builder` defined within the current module.
|
162
|
+
You can rely on that instead of using the DSL for specifying the builder.
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
module OtherBuildableExample
|
166
|
+
include ModuleBuilder::Buildable
|
167
|
+
|
168
|
+
class Builder < ModuleBuilder::Builder
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
module OtherIncludingModule
|
173
|
+
include OtherBuildableExample.new(:my_config_value => "awesome")
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
When a `Buildable` module is included without the use of its constructor, the
|
178
|
+
default version of the module is included in the descendant class or module.
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
module BuildableExample
|
182
|
+
include ModuleBuilder::Buildable
|
183
|
+
|
184
|
+
class Builder < ModuleBuilder::Builder
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
module IncludingModule
|
189
|
+
include BuildableExample
|
190
|
+
end
|
191
|
+
```
|
192
|
+
|
193
|
+
## Development
|
194
|
+
|
195
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
196
|
+
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
197
|
+
prompt that will allow you to experiment.
|
198
|
+
|
199
|
+
When writing code, you can use the helper application [Guard][guard] to
|
200
|
+
automatically run tests and coverage tools whenever you modify and save a file.
|
201
|
+
This helps to eliminate the tedium of running tests manually and reduces the
|
202
|
+
change that you will accidentally forget to run the tests. To use Guard, run
|
203
|
+
`bundle exec guard`.
|
204
|
+
|
205
|
+
Before committing code, run `rake` to check that the code conforms to the style
|
206
|
+
guidelines of the project, that all of the tests are green (if you're writing a
|
207
|
+
feature; if you're only submitting a failing test, then it does not have to
|
208
|
+
pass!), and that the changes are sufficiently documented.
|
209
|
+
|
210
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
211
|
+
release a new version, update the version number in `version.rb`, and then run
|
212
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
213
|
+
git commits and tags, and push the `.gem` file to [rubygems.org][rubygems].
|
214
|
+
|
215
|
+
[guard]: http://guardgem.org
|
216
|
+
[rubygems]: https://rubygems.org
|
217
|
+
|
218
|
+
## Contributing
|
219
|
+
|
220
|
+
Bug reports and pull requests are welcome on GitHub at
|
221
|
+
https://github.com/michaelherold/module_builder. This project is intended to be
|
222
|
+
a safe, welcoming space for collaboration, and contributors are expected to
|
223
|
+
adhere to the [Contributor Covenant][covenant] code of conduct.
|
224
|
+
|
225
|
+
[covenant]: http://contributor-covenant.org
|
226
|
+
|
227
|
+
## Supported Ruby Versions
|
228
|
+
|
229
|
+
This library aims to support and is [tested against][travis] the following Ruby
|
230
|
+
versions:
|
231
|
+
|
232
|
+
* Ruby 1.9.3
|
233
|
+
* Ruby 2.0
|
234
|
+
* Ruby 2.1
|
235
|
+
* Ruby 2.2
|
236
|
+
* JRuby 1.7 (in Ruby 1.9 mode)
|
237
|
+
* JRuby 9.0
|
238
|
+
|
239
|
+
If something doesn't work on one of these versions, it's a bug.
|
240
|
+
|
241
|
+
This library may inadvertently work (or seem to work) on other Ruby versions,
|
242
|
+
however support will only be provided for the versions listed above.
|
243
|
+
|
244
|
+
If you would like this library to support another Ruby version or
|
245
|
+
implementation, you may volunteer to be a maintainer. Being a maintainer
|
246
|
+
entails making sure all tests run and pass on that implementation. When
|
247
|
+
something breaks on your implementation, you will be responsible for providing
|
248
|
+
patches in a timely fashion. If critical issues for a particular implementation
|
249
|
+
exist at the time of a major release, support for that Ruby version may be
|
250
|
+
dropped.
|
251
|
+
|
252
|
+
## Versioning
|
253
|
+
|
254
|
+
This library aims to adhere to [Semantic Versioning 2.0.0][semver]. Violations
|
255
|
+
of this scheme should be reported as bugs. Specifically, if a minor or patch
|
256
|
+
version is released that breaks backward compatibility, that version should be
|
257
|
+
immediately yanked and/or a new version should be immediately released that
|
258
|
+
restores compatibility. Breaking changes to the public API will only be
|
259
|
+
introduced with new major versions. As a result of this policy, you can (and
|
260
|
+
should) specify a dependency on this gem using the [Pessimistic Version
|
261
|
+
Constraint][pessimistic] with two digits of precision. For example:
|
262
|
+
|
263
|
+
spec.add_dependency "module_builder", "~> 0.1"
|
264
|
+
|
265
|
+
[pessimistic]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
|
266
|
+
[semver]: http://semver.org/spec/v2.0.0.html
|
267
|
+
|
268
|
+
## Credits
|
269
|
+
|
270
|
+
The original implementation of this library was based on the [Builder][builder]
|
271
|
+
within the [virtus] gem by Piotr Solnica, which I used for inspiration. Pieces
|
272
|
+
of it live on, but the current product expands on the original.
|
273
|
+
|
274
|
+
The idea for the library came from [a conversation][conversation] about the
|
275
|
+
best way to configure a module once it is included in a class. [Grégory
|
276
|
+
Horion][gregory]'s comment lead me down the path of using `Module.new` as the
|
277
|
+
base for this library.
|
278
|
+
|
279
|
+
[builder]: https://github.com/solnic/virtus/blob/3248a465643b86d7fcb0c16fe6937293adbd1055/lib/virtus/builder.rb
|
280
|
+
[conversation]: https://github.com/intridea/hashie/pull/262
|
281
|
+
[gregory]: http://gregory.io
|
282
|
+
[piotr]: http://solnic.eu
|
283
|
+
[virtus]: https://github.com/solnic/virtus
|
284
|
+
|
285
|
+
## License
|
286
|
+
|
287
|
+
The gem is available as open source under the terms of the [MIT License][license].
|
288
|
+
|
289
|
+
[license]: http://opensource.org/licenses/MIT.
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require "bundler"
|
2
|
+
Bundler.setup
|
3
|
+
Bundler::GemHelper.install_tasks
|
4
|
+
|
5
|
+
require "inch/rake"
|
6
|
+
Inch::Rake::Suggest.new(:inch)
|
7
|
+
|
8
|
+
require "rspec/core/rake_task"
|
9
|
+
RSpec::Core::RakeTask.new(:spec)
|
10
|
+
|
11
|
+
require "rubocop/rake_task"
|
12
|
+
RuboCop::RakeTask.new(:rubocop)
|
13
|
+
|
14
|
+
require "yard/rake/yardoc_task"
|
15
|
+
YARD::Rake::YardocTask.new(:yard)
|
16
|
+
|
17
|
+
task :mutant do
|
18
|
+
command = [
|
19
|
+
"bundle exec mutant",
|
20
|
+
"--include lib",
|
21
|
+
"--require module_builder",
|
22
|
+
"--use rspec",
|
23
|
+
"ModuleBuilder*",
|
24
|
+
].join(" ")
|
25
|
+
|
26
|
+
system command
|
27
|
+
end
|
28
|
+
|
29
|
+
task :default => [:spec, :rubocop, :yard, :inch]
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module ModuleBuilder
|
2
|
+
# Gives a module the ability to be customized via a {ModuleBuilder::Builder}
|
3
|
+
# in a class-like manner via a {::new} method.
|
4
|
+
module Buildable
|
5
|
+
# Extends the {ClassMethods} module onto any descendent.
|
6
|
+
#
|
7
|
+
# @param [Module] descendent the descendent module.
|
8
|
+
# @return [void]
|
9
|
+
def self.included(descendent)
|
10
|
+
descendent.extend(ClassMethods)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Gives a module that mixes in Buildable a small DSL for specifying the
|
14
|
+
# class to use when building the module and the ability to easily build a
|
15
|
+
# new copy of the module using a given state.
|
16
|
+
module ClassMethods
|
17
|
+
# Sets and accesses the builder class for a buildable module.
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# class MyBuilder < ModuleBuilder::Builder
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# module MyBuiltModule
|
24
|
+
# include ModuleBuilder::Buildable
|
25
|
+
#
|
26
|
+
# builder MyBuilder
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# @note When used as a class method, it declaratively sets the builder
|
30
|
+
# class. When used without a parameter, it uses the `:not_set` symbol
|
31
|
+
# as a marker to set itself into reader mode.
|
32
|
+
#
|
33
|
+
# @param [Class] builder_class the class to instantiate when building the
|
34
|
+
# module.
|
35
|
+
# @raise [ModuleBuilder::UnspecifiedBuilder] if the builder is not found.
|
36
|
+
# @return [Class] the builder class.
|
37
|
+
def builder(builder_class = :not_set)
|
38
|
+
if builder_class.equal?(:not_set)
|
39
|
+
builder_or_fail
|
40
|
+
else
|
41
|
+
@builder = builder_class
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Includes the default version of the built module when included without
|
46
|
+
# a constructor.
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# module MyBuildableModule
|
50
|
+
# include ModuleBuilder::Buildable
|
51
|
+
#
|
52
|
+
# class Builder < ModuleBuilder::Builder
|
53
|
+
# end
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# class MyUncustomizedClass
|
57
|
+
# include MyBuildableModule
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# @param [Class, Module] descendant the including class or module.
|
61
|
+
# @return [void]
|
62
|
+
def included(descendant)
|
63
|
+
descendant.__send__(:include, new)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Builds a module with the configured builder class using the given
|
67
|
+
# state.
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# module MyBuildableModule
|
71
|
+
# include ModuleBuilder::Buildable
|
72
|
+
#
|
73
|
+
# class Builder < ModuleBuilder::Builder
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# class MyClass
|
78
|
+
# include MyBuildableModule.new(:example => "value")
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# @param [Hash] state the state to use when configuring the builder.
|
82
|
+
# @return [Module] the newly built module.
|
83
|
+
def new(state = {})
|
84
|
+
builder.new(state).module
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# Fetches the configured or default builder class for the module.
|
90
|
+
#
|
91
|
+
# @raise [ModuleBuilder::UnspecifiedBuilder] if the builder is not found.
|
92
|
+
# @return [Class] the builder class.
|
93
|
+
def builder_or_fail
|
94
|
+
if @builder
|
95
|
+
@builder
|
96
|
+
else
|
97
|
+
default_builder
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Fetches the default builder for the module.
|
102
|
+
#
|
103
|
+
# @raise [ModuleBuilder::UnspecifiedBuilder] if there is no default
|
104
|
+
# Builder within the module.
|
105
|
+
# @return [Class] the default builder class.
|
106
|
+
def default_builder
|
107
|
+
const_get("Builder")
|
108
|
+
rescue NameError
|
109
|
+
raise UnspecifiedBuilder
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module ModuleBuilder
|
2
|
+
# Builds a module based on instance-level state and class-level configuration.
|
3
|
+
#
|
4
|
+
# This class is intended to be subclassed, not used as-is. There are several
|
5
|
+
# methods to override in order to give the builder the behavior you want. You
|
6
|
+
# can also define setup methods that are specified in the {Builder#hooks}
|
7
|
+
# array for arbitrary setup based on the state passed into the builder's
|
8
|
+
# constructor.
|
9
|
+
class Builder
|
10
|
+
# The module built by the builder.
|
11
|
+
#
|
12
|
+
# @!attribute [r] module
|
13
|
+
# @return [Module] the Module built by the builder
|
14
|
+
attr_reader :module
|
15
|
+
|
16
|
+
# Creates a new module builder that uses the specified base module as a
|
17
|
+
# foundation for its built module and sets any other specified key/value
|
18
|
+
# pairs as instance variables on the builder.
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# ModuleBuilder::Builder.new(base: Module.new)
|
22
|
+
#
|
23
|
+
# @param [Hash] state the state to use for defined hooks.
|
24
|
+
# @option state [Module] :base (Module.new) the module to use as a base on
|
25
|
+
# which to build.
|
26
|
+
def initialize(state = {})
|
27
|
+
state = [builder_defaults, defaults, state].reduce(&:merge)
|
28
|
+
@module = state.delete(:base)
|
29
|
+
|
30
|
+
state.each_pair do |attr, value|
|
31
|
+
instance_variable_set("@#{attr}", value)
|
32
|
+
end
|
33
|
+
|
34
|
+
add_extended_hook
|
35
|
+
add_inclusions
|
36
|
+
add_defined_hooks
|
37
|
+
end
|
38
|
+
|
39
|
+
# The defaults for any state values that require them.
|
40
|
+
#
|
41
|
+
# @note This can be overridden in a subclass with any default values for
|
42
|
+
# state variables that are needed in defined hooks.
|
43
|
+
#
|
44
|
+
# @return [Hash] the default state for defined hooks.
|
45
|
+
def defaults
|
46
|
+
{}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Lists the modules to be added into the Module#extended hook
|
50
|
+
# of the built module.
|
51
|
+
#
|
52
|
+
# @note This can be overridden in a subclass with any modules that
|
53
|
+
# should be extended onto modules that extend the built module.
|
54
|
+
#
|
55
|
+
# @return [Array<Module>] the modules to be extended onto any
|
56
|
+
# modules that extend the built module.
|
57
|
+
def extensions
|
58
|
+
[]
|
59
|
+
end
|
60
|
+
|
61
|
+
# Lists the methods that should be called when building a module.
|
62
|
+
#
|
63
|
+
# @note This can be overridden in a subclass with any methods that
|
64
|
+
# should be called to build the built module.
|
65
|
+
#
|
66
|
+
# @return [Array<Symbol>] the methods to call when building the
|
67
|
+
# module.
|
68
|
+
def hooks
|
69
|
+
[]
|
70
|
+
end
|
71
|
+
|
72
|
+
# Lists the modules to be included into the built module.
|
73
|
+
#
|
74
|
+
# @note This can be overridden in a subclass to automatically
|
75
|
+
# include modules in the built module.
|
76
|
+
#
|
77
|
+
# @return [Array<Module>] the modules to be included into the built
|
78
|
+
# module.
|
79
|
+
def inclusions
|
80
|
+
[]
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# Performs every method listed in the builder's {#hooks}.
|
86
|
+
#
|
87
|
+
# @return [void]
|
88
|
+
def add_defined_hooks
|
89
|
+
hooks.each { |hook| __send__(hook) }
|
90
|
+
end
|
91
|
+
|
92
|
+
# Adds the modules listed by the builder's {#extensions} to the
|
93
|
+
# Module#extended hook, for extension onto any modules that
|
94
|
+
# extend the built module
|
95
|
+
#
|
96
|
+
# @return [void]
|
97
|
+
def add_extended_hook
|
98
|
+
within_context do |context|
|
99
|
+
@module.define_singleton_method :extended do |object|
|
100
|
+
context.extensions.each { |extension| object.extend(extension) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Includes the modules listed by the builder's {#inclusions} in the
|
106
|
+
# built module.
|
107
|
+
#
|
108
|
+
# @return [void]
|
109
|
+
def add_inclusions
|
110
|
+
inclusions.each { |mod| @module.__send__(:include, mod) }
|
111
|
+
end
|
112
|
+
|
113
|
+
# Defines the basic defaults that every builder needs.
|
114
|
+
#
|
115
|
+
# @return [Hash] the defaults that every builder needs.
|
116
|
+
def builder_defaults
|
117
|
+
{:base => Module.new}
|
118
|
+
end
|
119
|
+
|
120
|
+
# Gives access to the builder's context when dynamically defining
|
121
|
+
# hooks.
|
122
|
+
#
|
123
|
+
# @api private
|
124
|
+
#
|
125
|
+
# @return [void]
|
126
|
+
def within_context
|
127
|
+
yield self
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module ModuleBuilder
|
2
|
+
# Represents any error that is expected to be raised by {ModuleBuilder}.
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
# Raised when a {ModuleBuilder::Buildable} module cannot find the
|
6
|
+
# {ModuleBuilder::Builder} to use when building itself.
|
7
|
+
class UnspecifiedBuilder < Error; end
|
8
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require File.expand_path("../lib/module_builder/version", __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "module_builder"
|
7
|
+
spec.version = ModuleBuilder::VERSION.dup
|
8
|
+
spec.authors = ["Michael Herold"]
|
9
|
+
spec.email = ["michael.j.herold@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Dynamically build customized modules"
|
12
|
+
spec.description = "Dynamically build customized modules"
|
13
|
+
spec.homepage = "https://github.com/michaelherold/module_builder"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = %w(CHANGELOG.md LICENSE.md README.md Rakefile)
|
17
|
+
spec.files += %w(module_builder.gemspec)
|
18
|
+
spec.files += Dir["lib/**/*.rb"]
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: module_builder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Herold
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-21 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.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
description: Dynamically build customized modules
|
28
|
+
email:
|
29
|
+
- michael.j.herold@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- CHANGELOG.md
|
35
|
+
- LICENSE.md
|
36
|
+
- README.md
|
37
|
+
- Rakefile
|
38
|
+
- lib/module_builder.rb
|
39
|
+
- lib/module_builder/buildable.rb
|
40
|
+
- lib/module_builder/builder.rb
|
41
|
+
- lib/module_builder/error.rb
|
42
|
+
- lib/module_builder/version.rb
|
43
|
+
- module_builder.gemspec
|
44
|
+
homepage: https://github.com/michaelherold/module_builder
|
45
|
+
licenses:
|
46
|
+
- MIT
|
47
|
+
metadata: {}
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 2.4.5.1
|
65
|
+
signing_key:
|
66
|
+
specification_version: 4
|
67
|
+
summary: Dynamically build customized modules
|
68
|
+
test_files: []
|
69
|
+
has_rdoc:
|