Permalink
Browse files

v0.10.0 - Lazy dep parameters, a major new feature.

One weakness of babushka's DSL has always been that deps aren't parameterised.
There were a couple of existing ways to pass information between deps, the most
obvious by using vars:

    dep 'rack app' do
      set :vhost_type, 'unicorn'
      requires 'vhost configured'
    end

    dep 'vhost configured' do
      met? { conf_exists?(var(:vhost_type)) }
    end

This is problematic, because setting state and then triggering a process that
separately makes use of that state is bad design. It's much better to pass the
state around directly, because:

  - the data is much more localised: local arguments can only be read and
    written when they're in scope
  - it's more explicit, and hence more discoverable

I attempted this previously by adding block arguments to deps. Up until today,
it was possible to do this:

    dep 'rack app' do
      requires Dep('vhost configured').with('unicorn')
    end

    dep 'vhost configured' do |vhost_type|
      met? { conf_exists?(vhost_type) }
    end

At first, this seemed like a great idea, but it turned out to be a flawed
design. Firstly, local variables aren't accessible from within methods, so
helper methods defined within deps couldn't access the args like met?{} and
meet{} blocks could. But more problematically, local variables are a
language-level feature in ruby, and so there's no control over how they behave -
their names can't be discovered (on ruby 1.8), and they can't be lazily
evaluated later; their values have to be present at the time the outer dep block
is defined.

The new design does away with block arguments, and uses a different notation to
define dep parameters:

    dep 'vhost configured', :vhost_type do
      met? { conf_exists?(vhost_type) }
    end

Each argument listed is defined on the dep as an instance method. That method
returns a Parameter object which represents the value. You can pass the values
either positionally, or by name (as a hash), when you require the dep...

    dep 'rack app' do
      # also, you can pass them directly to the string now
      requires 'vhost configured'.with('unicorn') # positional arguments
    end

    dep 'rack app' do
      requires 'vhost configured'.with(vhost_type: 'unicorn') # named arguments
    end

... And the values will be made available as methods on the dep, accessible
anywhere within that dep's context. The idea of the Parameter object, though, is
that it provides a lazy wrapper around the value. If you don't pass a value for
a given parameter, the Parameter object that its method exposes will request the
value from the prompt as required (i.e. when something like #to_s is called on
it).

When you pass the arguments positionally, like standard ruby arguments, the
arity has to match: requiring the dep will fail otherwise, complaining that you
didn't pass enough args. When you pass by name, though, you can include as many
or as few of the dep's args as you like - the rest will be lazily requested from
the prompt if and when the values they represent are accessed.

Unlike vars, though, dep arguments are local to each dep - you have to
explicitly pass them onwards for them to propagate. This seems like overhead,
but I'm confident anyone who's written more than a handful of deps will agree
that the alternative, shared vars, is not manageable over time. (And it turns
out passing args around really isn't much of an overhead at all; it just
involves being explicit about data requirements that were already there.)

All the settings for vars are present with parameters, too, like defaults and
choices. But unlike vars, which accept them as an options hash, parameters
expose them as chainable methods.

    dep 'app bundled', :path, :env do
      path.default('.')
      env.ask('Which environment should be bundled?').default('production')
      # ...
    end

I'm hoping that as they evolve, lazy dep parameters can mostly replace vars. Try
them out - find your messiest dep; chances are it's that way because of a
var-related workaround you had to make. Try converting it to use dep parameters
instead, and you'll find that a lot of setup{} blocks, calls to #set & #default,
and other similar noise, become unnecessary, because you can just directly pass
state around and not worry about unintended interactions between shared state
that vars inevitably cause.

Feedback is super welcome, as it always is, to @babushka_app or
http://babushka.me/mailing_list. Bugreports are much appreciated -
http://github.com/benhoskings/babushka/issues.

Share and enjoy! <3

-- Ben
  • Loading branch information...
benhoskings committed Sep 19, 2011
1 parent 6a5c3a2 commit 40054c2e7b9c382ff01ea26b70725a3271ac3eda
Showing with 1 addition and 1 deletion.
  1. +1 −1 lib/babushka.rb
@@ -1,5 +1,5 @@
module Babushka
VERSION = '0.9.8'
VERSION = '0.10.0'
WorkingPrefix = '~/.babushka'
SourcePrefix = '~/.babushka/sources'
BuildPrefix = '~/.babushka/build'

0 comments on commit 40054c2

Please sign in to comment.