Class: Briefly::Builder
- Inherits:
-
Object
- Object
- Briefly::Builder
- Defined in:
- lib/briefly/builder.rb
Overview
Receives the DSL. define's block and Facade::Control#configure's block are +instance_eval+'d here, never on the facade, so DSL verbs can never collide with shortcut names.
The block only collects; #compile! then validates, and Facade::Control#configure installs.
Defined Under Namespace
Classes: Plan
Instance Attribute Summary collapse
-
#facade ⇒ Briefly::Facade
readonly
The facade under construction, for packs that need lifecycle hooks.
Instance Method Summary collapse
-
#compile! ⇒ Briefly::Builder::Plan
private
Validates everything collected in this pass, and recursively every namespace pass it collected.
-
#initialize(facade, defs, children) ⇒ Builder
constructor
private
A new instance of Builder.
-
#namespace(name) { ... } ⇒ Symbol
Declares a namespace: a shortcut returning a child facade, so
App.db.queryworks. -
#rescue_from(error_class, *names) {|error, shortcut_name| ... } ⇒ self
Registers a facade-wide error handler: one applying to every shortcut, consulted after each shortcut's own handlers.
-
#shortcut(canonical, *aliases) { ... } ⇒ Briefly::Shortcut
Declares a shortcut, or fetches an already-declared one to refine.
-
#use(pack) ⇒ self
Applies a pack: any object responding to
#install(builder), or a name register'd for one.
Constructor Details
#initialize(facade, defs, children) ⇒ Builder
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns a new instance of Builder.
21 22 23 24 25 26 27 |
# File 'lib/briefly/builder.rb', line 21 def initialize(facade, defs, children) @facade = facade @defs = defs @children = children @rescues = [] @pending = {} end |
Instance Attribute Details
#facade ⇒ Briefly::Facade (readonly)
Returns the facade under construction, for packs that need lifecycle hooks.
15 16 17 |
# File 'lib/briefly/builder.rb', line 15 def facade @facade end |
Instance Method Details
#compile! ⇒ Briefly::Builder::Plan
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Validates everything collected in this pass, and recursively every namespace pass it collected. Facade::Control#configure installs the result. Validation of the whole tree finishes before any of it is installed.
140 141 142 143 144 145 146 147 148 |
# File 'lib/briefly/builder.rb', line 140 def compile! @defs.each_value do |defn| # `parameters`, not `arity`: a `{ |&blk| }` body has arity 0 but its block would be dropped. next unless defn.memoized? && !defn.body.parameters.empty? raise Error, "cannot memoize #{defn.canonical}: its body takes arguments" end Plan.new(@defs, @rescues, @children, @pending.map { |name, builder| [@children[name], builder.compile!] }) end |
#namespace(name) { ... } ⇒ Symbol
Declares a namespace: a shortcut returning a child facade, so App.db.query works. The block is
the child's own DSL — shortcut (and the shortcut it returns), use, rescue_from, and namespaces.
The child is created once and reused by later passes, so its memos survive configure.
Facade::Control#clear_memos! cascades into it. Two limits, both deliberate: a child body cannot
reach a root shortcut by bare name, and a root rescue_from does not scope into the child.
The child's pass is collected, not run: its builder is held until #compile! has validated the
whole tree, so a later failure in this pass cannot leave an already-reachable child mutated. One
builder per name, so a second namespace(:db) in the same pass extends the first rather than
replacing it.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/briefly/builder.rb', line 61 def namespace(name, &block) raise ArgumentError, "namespace(#{name.inspect}) requires a block" unless block validate_name!(name) child = @children[name] || Facade.new pending = @pending[name] || child.send(:__prepare) pending.instance_eval(&block) # Routes through `shortcut` for purge and validation, which drops the child this call just # resolved — so re-register both afterwards. An external `shortcut(name)` gets no such reprieve. shortcut(name) { child } # The proc above is a literal in this file. Point the compiled method at the caller's block instead. @defs[name].source_location = block.source_location @children[name] = child @pending[name] = pending name end |
#rescue_from(error_class, *names) {|error, shortcut_name| ... } ⇒ self
Registers a facade-wide error handler: one applying to every shortcut, consulted after each
shortcut's own handlers. The handler's return value becomes the shortcut's return value. To guard a
single shortcut, chain onto it instead — shortcut(name).rescue_from(error_class) { ... } — which
is why this verb takes no shortcut names.
Because {} binds to the nearest token, rescue_from StandardError { } is a call to a method
named StandardError. Use rescue_from Err do |e| ... end or rescue_from(Err) { |e| ... }.
121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/briefly/builder.rb', line 121 def rescue_from(error_class, *names, &block) Briefly.send(:validate_rescue!, error_class, block) unless names.flatten.empty? raise ArgumentError, "rescue_from(#{error_class}) takes no shortcut names — scope one to its " \ "shortcut with shortcut(name).rescue_from(#{error_class}) { ... }, or omit " \ "names for a facade-wide handler" end @rescues << [error_class, block] self end |
#shortcut(canonical, *aliases) { ... } ⇒ Briefly::Shortcut
Declares a shortcut, or fetches an already-declared one to refine.
With a block, declares the shortcut (redeclaring a name, canonical or alias, silently overrides it)
and returns the Shortcut, so you can chain .memoize or .rescue_from onto it. With no
block, shortcut(name) fetches the shortcut name resolves to (canonical or alias) so a pack's
shortcut can be refined; it never declares, purges, or overrides.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/briefly/builder.rb', line 93 def shortcut(canonical, *aliases, &body) unless body # A bodiless `shortcut(name)` fetches an existing shortcut to refine; aliases are meaningless # there, so a non-empty list means a block was forgotten — fail loudly, don't drop them silently. raise ArgumentError, "shortcut(#{canonical.inspect}, ...) with aliases requires a block" unless aliases.empty? return fetch(canonical) end defn = Shortcut.new(canonical, aliases, body, source_location_for(body)) defn.names.each { |name| validate_name!(name) } purge(defn.names, except: canonical) @defs[canonical] = defn end |
#use(pack) ⇒ self
Applies a pack: any object responding to #install(builder), or a name Briefly.register'd for
one. Any keywords are forwarded to the pack's install. Ruby drops an empty ** splat, so a
pack taking no options needs no keyword parameter.
use Briefly::Rails::DB, base: "SecondaryApplicationRecord"
use "rails/db", base: "SecondaryApplicationRecord"
39 40 41 42 43 |
# File 'lib/briefly/builder.rb', line 39 def use(pack, **) pack = Briefly.pack(pack) unless pack.respond_to?(:install) pack.install(self, **) self end |