module_builder 0.1.0
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/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
|
+
[][travis]
|
4
|
+
[][codeclimate]
|
5
|
+
[][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:
|