Base functionality for JSON validation and presenter (rendering) layers. Subclass this to define a schema against which validation of inbound data or rendering of outbound data can be performed. Call schema in the subclass to declare, via the DSL, the shape of the schema.

Methods
G
I
R
S
V
W
Class Public methods
get_schema()

Return the schema graph. See also get_schema_definition.

# File lib/hoodoo/presenters/base.rb, line 299
def self.get_schema
  @schema
end
get_schema_definition()

Read back the block that defined the schema graph. See also get_schema.

# File lib/hoodoo/presenters/base.rb, line 306
def self.get_schema_definition
  @schema_definition
end
is_internationalised?()

Does this presenter use internationalisation? Returns true if so, else false.

# File lib/hoodoo/presenters/base.rb, line 293
def self.is_internationalised?
  @schema.is_internationalised?
end
render( data, uuid = nil, created_at = nil, language = 'en-nz', created_by = nil, updated_at = nil )

Given some data that should conform to the subclass presenter's schema, render it to go from the input Ruby Hash, to an output Ruby Hash which will include default values - if any - present in the schema and will drop input fields not present in that schema. In essence, this takes data which may have been programatically generated and sanitises it to produce valid, with-defaults guaranteed valid output.

Field kind is taken from the class name. If concerned about clashes between resource names and ActiveRecord model names, put your resource classes inside a module for namespacing - for example:

module Resources
  class Product < Hoodoo::Presenters::Base
    schema do
      ...
    end
  end
end

Only the class “leaf” name is used to infer the resource kind - in the above case, that's Product.

Any field with a schema giving a default value will only appear should a value for that field be omitted in the input data. If the data provides, for example, an explicit nil value then a corresponding explicit nil will be rendered, regardless of defaults.

For belt-and-braces, unless subsequent profiling shows performance issues, callers should call validate first to self-check their internal data against the schema prior to rendering. That way, coding errors will be discovered immediately, rather than hidden / obscured by the rendered sanitisation.

Since rendering top-level nil is not valid JSON, should nil be provided as input, it'll be treated as an empty hash (“{}”) instead.

This is quite a low-level call. For a higher level renderer which Hoodoo service resource implementations will probably want to use for returning resource representations in responses, see ::render_in.

data

Hash to be represented. Data within this is compared against the schema being called to ensure that correct information is returned and unknown data is ignored.

uuid

Unique ID of the resource instance that is to be represented. If nil / omitted, this is assumed to be a rendering of a type or other non-resource like item. Otherwise the field is mandatory.

created_at

Date/Time of instance creation. Only required if UUID has been provided. This is a Ruby DateTime instance or similar, NOT a string!

language

Optional language. If the type/resource being rendered is internationalised but this is omitted, then a value of “en-nz” is used as a default.

created_by

Optional fingerprint of the Caller whose credentials were used to create the Session under which the resource instance was created. Absent if omitted.

updated_at

Optional Date/Time of instance update. This is a Ruby DateTime instance or similar, NOT a string!

# File lib/hoodoo/presenters/base.rb, line 96
def self.render( data,
                 uuid       = nil,
                 created_at = nil,
                 language   = 'en-nz',
                 created_by = nil,
                 updated_at = nil
                )
  target = {}
  data   = data || {}

  @schema.render( data, target )

  # Common fields are added after rendering the data in case there are
  # any same-named field collisions - platform defaults should take
  # precedence, overwriting previous definitions intentionally.

  unless ( uuid.nil? )

    raise "Can't render a Resource with a nil 'created_at'" if created_at.nil?

    # Field "kind" is taken from the class name; this is a class method
    # so "self.name" yields "Hoodoo::Data::Resources::..." or similar.
    # We could just use "split", but that creates an intermediate Array
    # which uses more RAM and is about half the speed (or worse) of the
    # following alternative (it turns out ActiveSupport 4's #demodulize
    # uses much the same approach).

    name  = self.name
    index = ( name.rindex( '::' ) || -2 ) + 2
    kind  = name[ index .. -1 ]

    target.merge!( {
      'id'         => uuid,
      'kind'       => kind,
      'created_at' => Hoodoo::Utilities.standard_datetime( created_at.to_datetime )
    } )

    target[ 'updated_at' ] = Hoodoo::Utilities.standard_datetime( updated_at.to_datetime ) unless updated_at.nil?
    target[ 'created_by' ] = created_by unless created_by.nil?
    target[ 'language'   ] = language if self.is_internationalised?()

  end

  return target
end
render_in( context, data, options = {} )

A higher level version of ::render, typically called from Hoodoo services in their resource implementation code.

As with ::render, data is rendered according to the schema of the object the ::render_in message is sent to. Options specify things like UUID and created-at date. Language information for internationalised fields can be given, but if omitted comes from the given request context data.

Additional facilites exist over and above ::render - security scoping information in the resource via its secured_with field is made available through options (see below), along with support for embedded or referenced resource information.

context

A Hoodoo::Services::Context instance, which is usually the value passed to a service implementation in calls like Hoodoo::Services::Implementation#list or Hoodoo::Services::Implementation#show.

data

Hash to be represented. Data within this is compared against the schema being called to ensure that correct information is returned and unknown data is ignored.

options

Options hash, see below.

The options keys are Symbols, used as follows:

uuid

Same as the uuid parameter to ::render, except mandatory.

created_at

Same as the created_at parameter to ::render, except mandatory.

updated_at

Optional value expressing the time the resource was last updated.

created_by

Optional fingerprint of the Caller whose credentials were used to create the Session under which the resource instance was created.

language

Optional value for resource's language field; taken from the context parameter if omitted.

embeds

A Hoodoo::Presenters::Embedding::Embeds instance that contains (fully rendered) resources which are to be embedded in this rendered representation. Optional.

references

A Hoodoo::Presenters::Embedding::References instance that contains UUIDs which are to be embedded in this rendered representation as references. Optional.

secured_with

An ActiveRecord::Base subclass instance where the model class includes a secure_with declaration. As per documentation for Hoodoo::ActiveRecord::Secure::ClassMethods#secure and Hoodoo::ActiveRecord::Secure::ClassMethods#secure_with, this leads (potentially) to the generation of the secured_with field and object value in the rendered resource data.

# File lib/hoodoo/presenters/base.rb, line 202
def self.render_in( context, data, options = {} )
  uuid         = options[ :uuid         ]
  created_at   = options[ :created_at   ]
  updated_at   = options[ :updated_at   ]
  created_by   = options[ :created_by   ]
  language     = options[ :language     ] || context.request.locale
  secured_with = options[ :secured_with ]
  embeds       = options[ :embeds       ]
  references   = options[ :references   ]

  target = self.render( data, uuid, created_at, language, created_by, updated_at )

  if defined?( ::ActiveRecord ) && secured_with.is_a?( ::ActiveRecord::Base )
    result_hash     = {}
    extra_scope_map = secured_with.class.secured_with()

    extra_scope_map.each do | model_field_name, key_or_options |
      resource_field = if key_or_options.is_a?( ::Hash )
        next if key_or_options[ :hide_from_resource ] == true
        key_or_options[ :resource_field_name ] || model_field_name
      else
        model_field_name
      end

      result_hash[ resource_field.to_s ] = secured_with.send( model_field_name )
    end unless extra_scope_map.nil?

    target[ 'secured_with' ] = result_hash unless result_hash.empty?
  end

  target[ '_embed'     ] = embeds.retrieve()     unless embeds.nil?
  target[ '_reference' ] = references.retrieve() unless references.nil?

  return target
end
schema( &block )

Define the JSON schema for validation.

&block

Block that makes calls to the DSL defined in Hoodoo::Presenters::BaseDSL in order to define the schema.

# File lib/hoodoo/presenters/base.rb, line 26
def self.schema( &block )
  @schema = Hoodoo::Presenters::Object.new
  @schema.instance_eval( &block )
  @schema_definition = block
end
validate( data, as_resource = false )

Is the given rendering of a resource valid? Returns an array of Error Primitive types (as hashes); this will be empty if the data given is valid.

data

Ruby Hash representation of JSON data that is to be validated against 'this' schema. Keys must be Strings, not Symbols.

as_resource

Check Resource common fields - id, kind, created_at and (for an internationalised resource) language. Otherwise, only basic data schema is examined. Optional; default is false.

# File lib/hoodoo/presenters/base.rb, line 250
def self.validate( data, as_resource = false )
  errors = @schema.validate( data )

  if as_resource
    common_fields = {
      'id'         => data[ :id         ],
      'created_at' => data[ :created_at ],
      'kind'       => data[ :kind       ]
    }

    created_by = data[ :created_by ]
    common_fields[ 'created_by' ] = created_by unless created_by.nil?

    updated_at = data[ :updated_at ]
    common_fields[ 'updated_at' ] = updated_at unless updated_at.nil?

    if self.is_internationalised?
      common_fields[ 'internationalised' ] = data[ 'internationalised' ]
      Hoodoo::Presenters::CommonResourceFields.get_schema.properties[ 'language' ].required = true
    end

    errors.merge!( Hoodoo::Presenters::CommonResourceFields.validate( data, false ) )

    Hoodoo::Presenters::CommonResourceFields.get_schema.properties[ 'language' ].required = false
  end

  return errors
end
walk( &block )

Walk the schema graph and invoke the given block on each field within it, passing the field instances to the block for each call.

All fields including the top-level “root” property (which has an empty string for a name) will be passed to the block in order of definition, recursing into nested objects, arrays and so-on as each is encountered.

# File lib/hoodoo/presenters/base.rb, line 286
def self.walk( &block )
  @schema.walk( &block )
end