Skip to content

structured data classes

Philip (flip) Kromer edited this page Jun 4, 2012 · 3 revisions

gorillib's structured data classes -- Record, Model, Builder and Bucket

Overview

Gorillib provides these general flavors of model:

  • Gorillib::Record: lightweight structured records. Easily assemble a dynamic data structure that enables both rich behavior and generic manipulation, serialization and transformation. Especially useful when you just need to pull something from JSON, attach some functionality, and get it back on the wire.

    ```ruby
    class Place
      include Gorillib::Record
      # fields can be simple...
      field :name, String
      field :country_id, String, :doc => 'Country code (2-letter alpha) containing the place'
      # ... or complext
      field :geo, GeoCoordinates, :doc => 'geographic location of the place'
    end
    
    class GeoCoordinates
      include Gorillib::Record
      field :latitude,  Float, :doc => 'latitude in decimal degrees; negative numbers are south of the equator'
      field :longitude, Float, :doc => 'longitude in decimal degrees; negative numbers are west of Greenwich'
    end
    
    # It's simple to instantiate complex nested data structures
    lunch_spot = Place.receive({ :name => "Torchy's Tacos", :country_id => "us",
      :geo => { :latitude => "30.295", :longitude => "-97.745" }})
    ```
    
  • Gorillib::Model: rich structured models offering predictable magic with a disciplined footprint. Comparable to ActiveRecord or Datamapper, but for a world dominated by JSON+HTTP, not relational databases

    ```ruby
    class GeoCoordinates
      include Gorillib::Model
      field :latitude,  Float, :doc => 'latitude in decimal degrees; negative numbers are south of the equator', :validates => { :numericality => { :>= =>  -90, :<= =>  90  } }
      field :longitude, Float, :doc => 'longitude in decimal degrees; negative numbers are west of Greenwich', :validates => { :numericality => { :>= => -180, :<= => 180  } }
    end
    position = GeoCoordinates.from_tuple(30.295, -97.745)
    # A Gorillib::Model obeys the ActiveModel contract
    GeoCoordinates.model_name.human # => 'Geo coordinates'
    ```
    
  • Gorillib::Builder: foundation models for elegant ruby DSLs (Domain-Specific Languages). Relaxes ruby syntax to enable highly-readable specification of complex behavior.

    ```ruby
    workflow(:bake_pie) do
      step   :make_crust
      step   :add_filling
      step   :bake
      step   :cool
    end
    ```
    
  • Gorillib::Bucket (?name?): record-style access to freeform hashes. Provide a disciplined interface on top of arbitrarily-structured data. Used by Configliere and others.

     ```ruby
     # pre-defining a field gives you special powers...
     Settings.define :port,   Integer, :doc => 'API server port number', :default => 80
     # but you can still store or read anything you'd like...
     Settings[:shout] = "SAN DIMAS HIGH SCHOOL FOOTBALL RULES"
     # and treat the object as a hash when you'd like to
     conn = Connections.open(Settings.merge(user_overrides))
     ```
    

Decisions

  • initializer - does not inject an initializer; if you want one, do alias_method :initialize, :receive!

  • frills --

    • does inject: inspect on the class, inspect, to_s on the instance
    • does inject: accessors (foo, foo=) for each field.
    • does inject: == on the instance
    • does not define: schema, initialize
  • field defaults - evaluated on first read, at which point its value is fixed on the record.

  • define_metamodel_record -- visibility=false does not remove an existing method from the metamodel

  • ?? hash vs mash -- does attributes return a mash or hash?

  • are these basic functionality:

    • extra_attributes -- ??
    • default -- ??
  • Record:

    • class methods -- field, fields, field_names, has_field?, metamodel, receive, inspect
    • instance methods -- read_attribute, write_attribute, unset_attribute, attribute_set?, attributes, receive!, update, inspect, to_s, ==
    • with each attribute -- receive_foo, foo=, foo
  • Builder:

    • defining classes before they're used

Features

Record

  • Record::Ordered -- sort order on fields (and thus record)
  • Record::FieldAliasing -- aliases for fields and receivers
  • Record::Defaults -- default values
  • Record::Schema -- icss schema
  • Record::HashAccessors -- adds [], []=, delete, keys, #has_key?, to_hash, and update. This allows it to be hashlike, but you must include that explicitly.
  • Record::Hashlike -- mixes in Record::HashAccessors and Gorillib::Hashlike, making it behave in almost every respect like a hash. Use this when you want something that will behave like a hash but be a record. If you want something to be a hash but behave like a record, use the Gorillib::MashRecord

Builder

  • Builder::GetsetField --

HashRecord

Model

  • Model::Naming
  • Model::Conversion --

active_model / active_model_lite

From active_model or active_model_lite:

  • Model::Callbacks --
  • Model::Dirty --
  • Model::Serialization --
  • Model::Validations -- implies Model::Errors, Model::Callbacks

Why, in a world with ActiveRecord, Datamapper, Hashie, Struct, ..., do we need yet another damn model framework?

ActiveRecord and Datamapper excel in a world where data (and truth) live in the database. ActiveRecord sets the standard for elegant magic, but is fairly heavyweight (I don't want to include a full XML serialization suite just so I can validate records). This often means it's overkill for the myriad flyweight scripts, Goliath apps, and such that we deploy. Datamapper does a remarkable job of delivering power while still being light on its toes, but ultimately is too tightly bound to an ORM view of the world. Hashie, Structs and OStructs behave as both hashes and records. In my experience, this interface is too generous -- their use leads to mealymouthed code.

More importantly, our data spends most of its time on the wire or being handled as an opaque blob of data; a good amount of time being handled as a generic bundle of properties; and (though most important) a relatively small amount of time as an active, assertive object. So type conversion and validation are fundamental actions, but shouldn't crud up my critical path or be required. Models should offer predictable and disciplined features, but be accessable as generic bags of facts.