Class: Hoodoo::Presenters::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/hoodoo/presenters/base.rb

Overview

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.

Class Method Summary collapse

Class Method Details

.get_schemaObject

Return the schema graph. See also #get_schema_definition.



299
300
301
# File 'lib/hoodoo/presenters/base.rb', line 299

def self.get_schema
  @schema
end

.get_schema_definitionObject

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



306
307
308
# File 'lib/hoodoo/presenters/base.rb', line 306

def self.get_schema_definition
  @schema_definition
end

.is_internationalised?Boolean

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

Returns:



293
294
295
# 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) ⇒ Object

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!



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# 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 = {}) ⇒ Object

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.



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# 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) ⇒ Object

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.



26
27
28
29
30
# 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) ⇒ Object

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.



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# 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) ⇒ Object

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.



286
287
288
# File 'lib/hoodoo/presenters/base.rb', line 286

def self.walk( &block )
  @schema.walk( &block )
end