Class: Briefly::Builder
- Inherits:
-
Object
- Object
- Briefly::Builder
- Defined in:
- lib/briefly/builder.rb
Overview
Receives the DSL. define's block and Facade#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#configure installs.
Instance Attribute Summary collapse
-
#facade ⇒ Briefly::Facade
readonly
The facade under construction, for packs that need lifecycle hooks.
Instance Method Summary collapse
-
#compile! ⇒ Array
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.
-
#memoize(name, **opts) ⇒ Symbol
Marks an already-declared shortcut as memoized: computed once, cached for the process lifetime.
-
#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 an error handler.
-
#shortcut(canonical, *aliases) { ... } ⇒ Symbol
Declares a shortcut.
-
#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.
16 17 18 19 20 21 22 |
# File 'lib/briefly/builder.rb', line 16 def initialize(facade, defs, children) @facade = facade @defs = defs @children = children @errors = [] @pending = {} end |
Instance Attribute Details
#facade ⇒ Briefly::Facade (readonly)
Returns the facade under construction, for packs that need lifecycle hooks.
10 11 12 |
# File 'lib/briefly/builder.rb', line 10 def facade @facade end |
Instance Method Details
#compile! ⇒ Array
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#configure installs the result. Validation of the whole tree finishes before any of it is installed.
134 135 136 137 138 139 140 141 142 |
# File 'lib/briefly/builder.rb', line 134 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 [@defs, @errors, @children, @pending.map { |name, builder| [@children[name], builder.compile!] }] end |
#memoize(name, **opts) ⇒ Symbol
Marks an already-declared shortcut as memoized: computed once, cached for the process lifetime.
99 100 101 102 103 104 105 |
# File 'lib/briefly/builder.rb', line 99 def memoize(name, **opts) raise ArgumentError, "memoize(#{name.inspect}) got unknown options: #{opts.keys.join(", ")}" unless opts.empty? defn = fetch(name) defn.memoize! defn.canonical 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, memoize, use, rescue_from, and further namespaces.
The child is created once and reused by later passes, so its memos survive configure.
Facade#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.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/briefly/builder.rb', line 56 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 an error handler. The handler's return value becomes the shortcut's return value.
Because {} binds to the nearest token, rescue_from StandardError { } is a call to a method
named StandardError. Use rescue_from Err, :name do |e| ... end or rescue_from(Err, :name) { |e| ... }.
116 117 118 119 120 121 122 123 124 125 |
# File 'lib/briefly/builder.rb', line 116 def rescue_from(error_class, *names, &handler) unless error_class.is_a?(Class) raise ArgumentError, "rescue_from expects the error class first, e.g. rescue_from(StandardError, :name) { }" end raise ArgumentError, "rescue_from(#{error_class}) requires a block" unless handler scoped = names.flatten.map { |name| fetch(name).canonical } @errors << [error_class, (scoped.empty? ? nil : scoped), handler] self end |
#shortcut(canonical, *aliases) { ... } ⇒ Symbol
Declares a shortcut. Redeclaring a name (canonical or alias) silently overrides it.
80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/briefly/builder.rb', line 80 def shortcut(canonical, *aliases, &body) raise ArgumentError, "shortcut(#{canonical.inspect}) requires a block" unless body # A `&:upcase`-style Proc carries no location of its own; fall back to this call site so the compiled # method points at the user's declaration, never a fabricated file. `namespace` overwrites it anyway. location = body.source_location || caller_locations(1, 1).first.then { |l| [l.path, l.lineno] } defn = Definition.new(canonical, aliases, body, location) defn.names.each { |name| validate_name!(name) } purge(defn.names, except: canonical) @defs[canonical] = defn canonical 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"
34 35 36 37 38 |
# File 'lib/briefly/builder.rb', line 34 def use(pack, **) pack = Briefly.pack(pack) unless pack.respond_to?(:install) pack.install(self, **) self end |