Skip to content
Philip (flip) Kromer edited this page Jun 4, 2012 · 6 revisions

gorillib/bucket (WIP -- NOT READY YET) -- freeform storage with definable access

  • A Go::Bucket fulfills the contract of Go::Model

Arbitrary read/writes with []/[]=, defined access with .foo/.foo=

Overriding method_missing is uncouth. It screws up your stack traces, muddies your interface and leads to unassertive code:

```ruby
Settings.defcon = 3
# ... elsewhere ...
if Settings.def_con.to_i <= 1
  WOPR.launch_nukes!
end
```

Here, mispelling def_con as defcon (along with being wussy about the attribute's type and negligent about prescribing a default value) leads to the destruction of humanity.

Nonetheless, for several reasons (prudent laziness, handling configuration of an external component, etc) it's important to handle arbitrary attributes uniformly.

So the rule is that you can get or set anything you like using [] and []= respectively, no need to define it first:

Settings[:whatever] = 3
Settings[:whatever]       #=> 3

You don't get any magic, and the ugly accessor leaves you in no doubt that you're being lazy (ain't judging, just sayin'). When you define a setting you get accessors for that attribute and all associated magic:

Settings.option :defcon, Integer, :doc => 'Current NORAD defense condition', :default => 5, :validates => { :in => 1..5 }
Settings.defcon           #=> 5
Settings.defcon = 0
Settings.validate!        # raises a validation exception

Defined fields' magic works whichever form of access you use -- here, type-converting the value on assignment:

Settings[:defcon]     = '5'   #=> 5
Settings.defcon       = '5'   #=> 5
Settings.receive_defcon('5')  #=> 5

Keys

Keys be lower-cased identifiers: they should match /\A([a-z][a-z0-9\_]*)\z/.

Deep Hash

 Hash.new {|h,k| h[k] = Hash.new(&h.default_proc)}

Dot addressing

Can retrieve keys as 'x.y.z' meaning 'foo[x][y][z]'.

  • On a get, will

    • return the value, if foo[:x][:y][:z] exists
    • return nil, if either foo[:x] or foo[:x][:y] is unset. It will not create foo[:x] or foo[:x][:y].
    • raise an error if either foo[:x] or foo[:x][:y] is set to a value that does not respond to []
  • On a set, will

    • raise an error if either foo[:x] or foo[:x][:y] is set to a value that doesn't respond to []
    • set and return the value; any intermediate buckets (foo[:x] and foo[:x][:y]) will be created if they don't exist.

Examples

how hash-like is this?

  • obj.deep_get(:foo, :bar, :baz) #

  • obj.deep_set(:foo, :bar, :baz, val) # sets foo bar baz.

    • where property has a type, it uses that type
    • otherwise it uses Mash, and calls [] on it
  • TD votes NO on magic recursive hash behavior

    { :buck1 => { :buck2 => { :k3 => 33, :ehsh => {}, :arr => [11, 12, 13] }, :cars => [{ :model => 'ford', :cylinders => 8 }, { :model => 'buick', :cylinders => 6 }], } :buck3 => { :buck4 => { :k4 => 44 } :k5 => nil, } :k6 => 69 }

  • obj[:buck3] # b{ :buck4=>b{ :k4=>44 },:k5=>nil } -- it's a bucket

  • obj[:buck5] = Hash.new

  • obj[:buck5].class # Bucket -- it's converted to bucket

  • obj[:xxx] # nil -- it's not there

  • obj[:xxx][:yyy][:zzz] # fails -- it doesn't try to index into the nil obj[:xxx] cell.

  • obj[:xxx] # nil -- it didn't create the obj[:xxx] object when we tried to read on the previous line.

    c.options_for 'wheels', 'interior.fabric', 'interior.carpeting'

    c[:wheels] # c{ } c[:wheels][:whitewall] # nil c[:wheels][:whitewall] = true # true

    c.interior.fabric.color

  • obj[:k6][:bomb] # raises ArgumentError; obj[:k6] is not a bucket.

  • obj[:'hello-there'] # undefined; 'hello-there'` is not a valid identifier.

  • obj[:buck3][:buck4][:k5] = 55 obj[:buck3] # b{ :buck4=>b{ :k4=>44 },:k5 => 55}

  • obj[:f][:g][:h] = 7 obj[:f] # b{ :g => b{ :h => 7 } }

  • obj[:foo][:bar][:baz] ||= 1 -- hard ?I think?

  • obj[:foo] -- nil

  • obj[:foo][:bar] -- raise

  • obj[:foo][:bar] = 3 --

    • now obj[:foo][:bar] is 3
    • and obj[:foo] is a ?dsl_object?? but

obj[:foo][:bar]

Suppose obj[:foo] is set to a

Seems clear these should do the right thing:

  • obj.merge
  • obj.reverse_merge
  • obj.keys
  • obj.values
  • ... and a few more

Also:

  • obj.to_a?
  • obj.each?
  • other crazy Enumerable properties?

TODO

  • figure out the method structure for
    • read/write/unset of attributes when Hash vs Accessors vs Instance Variables
    • reader/writer: raw vs. hooks, dirty, etc.

Configliere Settings

Configliere lets you define arbitrary attributes of a param, notably:

 [:description]  Documentation for the param, used in the --help message
 [:default]      Sets a default value (applied immediately)
 [:env_var]      Environment variable to adopt (applied immediately, and after +:default+)
 [:type]         Converts param's value to the given type, just before the finally block is called
 [:finally]      Block of code to postprocess settings or handle complex configuration.
 [:required]     Raises an error if, at the end of calling resolve!, the param's value is nil.