module Icss::Meta::RecordType

RecordType – class methods for a RecordModel

Endows the model class with

Public Instance Methods

after_receive(after_hook_name, &after_hook) click to toggle source

make a block to run after each time .receive! is invoked

# File lib/icss/type/record_type.rb, line 183
def after_receive(after_hook_name, &after_hook)
  @after_receivers ||= {}
  @after_receivers[after_hook_name] = after_hook
end
after_receivers() click to toggle source

after_receive blocks for self and all ancestors

# File lib/icss/type/record_type.rb, line 189
def after_receivers
  all_f = {} # @after_receivers || {}
  call_ancestor_chain(:after_receivers){|anc_f| all_f.merge!(anc_f) }
  all_f
end
field(field_name, type, schema={}) click to toggle source

Describes a field in a Record object.

Each field has the following attributes:

@param [Symbol] name – a string providing the name of the field

(required)

@param [Class, Icss::Meta::Type] type a schema, or a string or symbol

naming a record definition (required)

  ruby type   kind        avro type     json type   example
  ----------  --------    ---------     ---------   ---------
  NilClass    simple      null          null        nil
  Boolean     simple      boolean       boolean     true
  Integer     simple      int,long      integer     1
  Float       simple      float,double  number      1.1
  String      simple      bytes         string      "\u00FF"
  String      simple      string        string      "foo"
  RecordModel named       record        object      {"a": 1}
  Enum        named       enum          string      "FOO"
  Array       container   array         array       [1]
  Hash        container   map           object      { "a": 1 }
  String      container   fixed         string      "\u00ff"
  XxxFactory  union       union         object
  Time        simple      time          string      "2011-01-02T03:04:05Z"

@option schema [String] :doc – description of field for users (optional)

@option schema [Object] :default – a default value for this field, used

when reading instances that lack this field (optional).
Permitted values depend on the field's schema type, according to the
table below. Default values for union fields correspond to the first
schema in the union. Default values for bytes and fixed fields are
JSON strings, where Unicode code points 0-255 are mapped to unsigned
8-bit byte values 0-255.

@option schema [String] :order – specifies how this field impacts sort

ordering of this record (optional).
Valid values are "ascending" (the default), "descending", or
"ignore". For more details on how this is used, see the the sort
order section below.

@option schema [Boolean] :required – same as :validates => :presence

@option schema [Hash] :validates – sends the validation on to

Icss::Type::Validations. Uses syntax parallel to ActiveModel's:

@option schema [Symbol] :accessor – with :none, no accessor is

created. With +:protected+, +:private+, or +:public+, applies
corresponding access rule.

   :presence     => true
   :uniqueness   => true
   :numericality => true
   :length       => { :minimum => 0, maximum => 2000 }
   :format       => { :with => /.*/ }
   :inclusion    => { :in => [1,2,3] }
   :exclusion    => { :in => [1,2,3] }
# File lib/icss/type/record_type.rb, line 96
def field(field_name, type, schema={})
  field_name = field_name.to_sym
  #
  schema = add_field_schema(field_name, type, schema)
  add_field_accessor(field_name, schema)
  rcvr(field_name, schema)
  add_validator(field_name) if respond_to?(:add_validator)
end
field_named(fn) click to toggle source
# File lib/icss/type/record_type.rb, line 115
def field_named(fn)
  fn = fn.to_sym
  ancestors.each{|anc| hsh = anc.instance_variable_get('@field_schemas') or next ; return(hsh[fn]) if hsh[fn] }
  nil
end
field_names() click to toggle source
# File lib/icss/type/record_type.rb, line 105
def field_names
  all_f = []
  call_ancestor_chain(:field_names){|anc_f| all_f = all_f | anc_f }
  all_f
end
fields() click to toggle source
# File lib/icss/type/record_type.rb, line 111
def fields
  field_schemas.values # _at(*field_names)
end
has_field?(fn) click to toggle source
# File lib/icss/type/record_type.rb, line 121
def has_field?(fn)
  !! field_schemas.has_key?(fn.to_sym)
end
rcvr(field_name, schema={}) click to toggle source

define a receiver attribute.

@param [Symbol] field_name - name of the receiver property @param [Class] type - a

@option [Object] :default - After any receive! operation, attribute is set to this value unless attr_set? is true @option [Class] :items - For collections (Array, Hash, etc), the type of the collection's items

# File lib/icss/type/record_type.rb, line 134
def rcvr(field_name, schema={})
  return if schema[:receiver] == :none
  klass = schema[:type]
  define_metamodel_method("receive_#{field_name}") do |val|
    _set_field_val(field_name, klass.receive(val))
  end
  _register_rcvr_for(field_name, "receive_#{field_name}")
  add_after_receivers(field_name)
end
rcvr_alias(fake_attr, field_name) click to toggle source
# File lib/icss/type/record_type.rb, line 144
def rcvr_alias(fake_attr, field_name)
  _register_rcvr_for(fake_attr, "receive_#{field_name}")
end
rcvr_remaining(field_name, schema={}) click to toggle source

Defines a receiver for attributes sent to receive! that are

  • not defined as receivers

  • field's name does not start with '_'

@example

class Foo ; include RecordModel
  field  :bob, String
  rcvr_remaining :other_params
end
foo_obj = Foo.receive(:bob => 'hi, bob", :joe => 'hi, joe')
# => <Foo @bob='hi, bob' @other_params={ :joe => 'hi, joe' }>
# File lib/icss/type/record_type.rb, line 172
def rcvr_remaining(field_name, schema={})
  schema[:values] ||= Object
  field(field_name, Hash, schema)
  after_receive(:rcvr_remaining) do |hsh|
    remaining_vals_hsh = hsh.reject{|k,v| (self.class.has_field?(k)) || (k.to_s =~ /^_/) }
    self.send("receive_#{field_name}", remaining_vals_hsh)
  end
  add_after_receivers(field_name)
end
receive(*args) click to toggle source

Returns a new instance with the given hash used to set all rcvrs.

All args up to the last one are passed to the initializer. The last arg must be a hash – its attributes are set on the newly-created object

@param hsh [Hash] attr-value pairs to set on the newly created object. @param *args [Array] arguments to pass to the constructor @return [Object] a new instance

# File lib/icss/type/record_type.rb, line 29
def receive *args
  hsh = args.pop
  raise ArgumentError, "#{self} can't receive '#{hsh.inspect}' (it isn't hashlike)" unless hsh.respond_to?(:[]) && hsh.respond_to?(:has_key?)
  obj = self.new(*args)
  obj.receive!(hsh)
end
to_schema() click to toggle source
# File lib/icss/type/record_type.rb, line 149
def to_schema
  {
    :name      => fullname,
    :namespace => namespace,
    :type      => :record,
    :is_a      => (respond_to?(:is_a) ? is_a : []),
    :doc       => doc,
    :fields    => fields.map(&:to_hash),
   }.compact_blank
end

Protected Instance Methods

_rcvr_methods() click to toggle source
# File lib/icss/type/record_type.rb, line 269
def _rcvr_methods
  all_f = {}
  call_ancestor_chain(:_rcvr_methods){|anc_f| all_f.merge!(anc_f) }
  all_f
end
_register_rcvr_for(attr_name, rcvr_meth) click to toggle source
# File lib/icss/type/record_type.rb, line 275
def _register_rcvr_for(attr_name, rcvr_meth)
  @_rcvr_methods ||= {}
  @_rcvr_methods[attr_name.to_sym] = rcvr_meth.to_sym
end
add_after_receivers(field_name) click to toggle source

Adds after_receivers to implement some of the options to .field

@option schema [Object] :default – if field is unset by time of

after_receive, the field will be set to a copy of this value

@option schema [Hash] :replace – if value is in the hash

class Foo < Icss::Thing
  field :temperature, Integer, :replace => { 9999 => nil }
end
f = Foo.receive({:temperature => 9999})
# #<Foo:0x10156c820 @temperature=nil>
# File lib/icss/type/record_type.rb, line 292
def add_after_receivers(field_name)
  schema = field_named(field_name)
  set_field_default(field_name, schema[:default]) if schema.has_key?(:default)
  if schema.has_key?(:replace)
    repl = schema[:replace]
    after_receive(:"replace_#{field_name}") do
      val = self.send(field_name)
      if repl.has_key?(val)
        self._set_field_val(field_name, repl[val])
      end
    end
  end
  # super(field_name) if defined?(super)
end
add_field_accessor(field_name, schema) click to toggle source
# File lib/icss/type/record_type.rb, line 260
def add_field_accessor(field_name, schema)
  visibility = schema[:accessor] || :public
  reader_meth = field_name ; writer_meth = "#{field_name}=" ; attr_name = "@#{field_name}"
  unless (visibility == :none)
    define_metamodel_method(reader_meth, visibility){    instance_variable_get(attr_name)    }
    define_metamodel_method(writer_meth, visibility){|v| instance_variable_set(attr_name, v) }
  end
end
add_field_schema(name, type, schema) click to toggle source

register the field schema internally. To preserve field order for 1.8.7, we track field names as an array

# File lib/icss/type/record_type.rb, line 239
def add_field_schema(name, type, schema)
  @field_names ||= [] ; @field_schemas ||= {}
  @field_names = @field_names | [name]
  schema = schema.symbolize_keys.merge({ :name => name })
  #
  # FIXME: this is terrible, especially given what's in TypeFactory anyway.
  #
  schema[:type] =
  case
  when type == Hash  && schema.has_key?(:values) then Icss::Meta::TypeFactory.receive({ :type => :hash,  :values => schema.delete(:values)})
  when type == Array && schema.has_key?(:items)  then Icss::Meta::TypeFactory.receive({ :type => :array, :items  => schema.delete(:items) })
  when type == Hash  then IdenticalHashFactory
  when type == Array then IdenticalArrayFactory
  else
    Icss::Meta::TypeFactory.receive(type)
  end
  fld = (field_schemas[name] || make_field_schema).merge(schema)
  fld[:parent] = self
  @field_schemas[name] = fld
end
call_ancestor_chain(attr) { |instance_variable_get| ... } click to toggle source

yield, in turn, the given instance variable from each ancestor having that variable. Ancestors are called from parent to great-grandparent

So you're asking yourself “Self, why not just call .super?

Consider:

class Base
  extend(Icss::Meta::RecordType)
  field :smurfiness, Integer
end
class Poppa < Base
  field :height, Integer
end

Poppa.field_names calls Icss::Meta::RecordType – it's the first member of its inheritance chain to define the method. We want it to do so for each ancestor that has added fields.

# File lib/icss/type/record_type.rb, line 216
def call_ancestor_chain(attr)
  # if caller.length > 200
  #   puts "\n\n********\n#{self}\n#{attr}\n#{ancestors.inspect}"
  #   puts caller.grep(%r{lib/icss})
  # end
  ivar = "@#{attr}"
  self.ancestors.reverse.each do |ancestor|
    yield(ancestor.instance_variable_get(ivar)) if ancestor.instance_variable_defined?(ivar)
  end
end
field_schemas() click to toggle source
# File lib/icss/type/record_type.rb, line 231
def field_schemas
  all_f = {}
  call_ancestor_chain(:field_schemas){|anc_f| all_f.merge!(anc_f) }
  all_f
end
make_field_schema() click to toggle source

create new field_schema as a RecordField object, not Hash

# File lib/icss/type/record_field.rb, line 72
def make_field_schema
  RecordField.new
end
set_field_default(field_name, default_val=nil, &blk) click to toggle source
# File lib/icss/type/record_type.rb, line 308
def set_field_default(field_name, default_val=nil, &blk)
  blk = default_val if default_val.is_a?(Proc)
  if blk
    after_receive(:"default_#{field_name}") do
      val = instance_exec(&blk)
      self._set_field_val(field_name, val) unless attr_set?(field_name)
    end
  else
    after_receive(:"default_#{field_name}") do
      val = default_val.try_dup
      self._set_field_val(field_name, val) unless attr_set?(field_name)
    end
  end
end