Class: Hoodoo::Presenters::Array

Inherits:
Field
  • Object
show all
Includes:
BaseDSL
Defined in:
lib/hoodoo/presenters/types/array.rb

Overview

A JSON Array schema member.

Instance Attribute Summary collapse

Attributes inherited from Field

#default, #name, #required

Instance Method Summary collapse

Methods included from BaseDSL

#array, #boolean, #date, #datetime, #decimal, #enum, #float, #hash, #integer, #internationalised, #is_internationalised?, #object, #resource, #string, #tags, #text, #type, #uuid

Methods inherited from Field

#full_path, #has_default?

Constructor Details

#initialize(name, options = {}) ⇒ Array

Initialize an Array instance with the appropriate name and options.

name

The JSON key.

options

A Hash of options, e.g. :required => true, :type => :enum, :field_from => [ 1, 2, 3, 4 ]. If a :type field is present, the Array contains atomic types of the given kind. Otherwise, either pass a block with inner schema DSL calls describing complex array entry schema, or nothing for no array content validation. If a block and :type option are passed, the block is used and option ignored.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/hoodoo/presenters/types/array.rb', line 26

def initialize( name, options = {} )
  super( name, options )

  if options.has_key?( :type )

    # Defining a property via "#property" adds it to the @properties
    # array, but handling of simple Types in array validation and
    # rendering is too different from complex types to use the same
    # code flow; we need the property to be independently used, so
    # extract it into its own instance variable and delete the item
    # from @properties.
    #
    value_klass     = type_option_to_class( options[ :type ] )
    random_name     = Hoodoo::UUID.generate()
    @value_property = property( random_name,
                                value_klass,
                                extract_field_prefix_options_from( options ) )

    @properties.delete( random_name )

    # This is approaching a blunt hack. Without it, validation errors
    # will result in e.g. "fields[1].cd2f0a15ec8e4bd6ab1964b25b044e69"
    # in error messages. By using nil, the validation code's JSON path
    # array to string code doesn't include the item, giving the
    # desired result. In addition, the base class Field#render code
    # has an important check for non-nil but empty and bails out, but
    # allows the nil name case to render simple types as expected. A
    # delicate / fragile balance of nil-vs-empty arises.
    #
    @value_property.name = nil

  end
end

Instance Attribute Details

#propertiesObject

The properties of this object, an array of Field instances.



12
13
14
# File 'lib/hoodoo/presenters/types/array.rb', line 12

def properties
  @properties
end

Instance Method Details

#render(data, target) ⇒ Object

Render an array into the target hash based on the internal state that describes this instance’s current path (position in the heirarchy of nested schema entities).

data

The Array to render.

target

The Hash that we render into. A “path” of keys leading to nested Hashes is built via super(), with the final key entry yielding the rendered array.



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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/hoodoo/presenters/types/array.rb', line 107

def render( data, target )

  # Data provided is explicitly nil or not an array? Don't need to render
  # anything beyond 'nil' at the field (the not-array case covers nil and
  # covers invalid input, which is treated as nil).

  return super( nil, target ) if ! data.is_a?( ::Array )

  # Otherwise, start looking at rendering array contents (even if the
  # input array is empty). This relies on pass-by-reference; we'll update
  # this specific instance of 'array' later. Call 'super' to render the
  # 'array' instance in place in 'target' straight away...

  array = []
  path  = super( array, target )

  # ...then look at rendering the input entries of 'data' into 'array'.

  if @properties.nil? == false && @properties.empty? == false
    data.each do | item |

      # We have properties defined so array values (in "item") must be
      # Hashes. If non-Hash, treat as if nil; explicit-nil-means-nil.

      unless item.is_a?( ::Hash )
        # Must modify existing instance of 'array', so use 'push()'
        array.push( nil )
        next
      end

      subtarget = {}

      @properties.each do | name, property |
        name    = name.to_s
        has_key = item.has_key?( name )

        next unless has_key || property.has_default?()

        property.render( has_key ? item[ name ] : property.default, subtarget )
      end

      rendered = subtarget.empty? ? {} : read_at_path( subtarget, path )

      # Must modify existing instance of 'array', so use 'push()'
      array.push( rendered )
    end

  elsif @value_property.nil? == false
    data.each do | item |
      subtarget = {}
      @value_property.render( item, subtarget )
      rendered = subtarget.empty? ? nil : read_at_path( subtarget, path ).values.first

      # Must modify existing instance of 'array', so use 'push()'
      array.push( rendered )
    end

  else
    # Must modify existing instance of 'array', so use 'push()'
    array.push( *data )

  end
end

#validate(data, path = '') ⇒ Object

Check if data is a valid Array and return a Hoodoo::Errors instance.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/hoodoo/presenters/types/array.rb', line 62

def validate( data, path = '' )
  errors = super( data, path )
  return errors if errors.has_errors? || ( ! @required && data.nil? )

  if data.is_a?( ::Array )

    # A block which defined properties for this instance takes
    # precedence; then check for a ":type" option via "@@value_property"
    # stored in the constructor; then give up and do no validation.
    #
    if @properties.nil? == false && @properties.empty? == false
      data.each_with_index do | item, index |
        @properties.each do | name, property |
          rdata = ( item.is_a?( ::Hash ) && item.has_key?( name ) ) ? item[ name ] : nil
          indexed_path = "#{ full_path( path ) }[#{ index }]"
          errors.merge!( property.validate( rdata, indexed_path ) )
        end
      end
    elsif @value_property.nil? == false
      data.each_with_index do | item, index |
        indexed_path = "#{ full_path( path ) }[#{ index }]"
        errors.merge!( @value_property.validate( item, indexed_path ) )
      end
    end

  else
    errors.add_error(
      'generic.invalid_array',
      :message   => "Field `#{ full_path( path ) }` is an invalid array",
      :reference => { :field_name => full_path( path ) }
    )
  end

  errors
end

#walk(&block) ⇒ Object

Invoke a given block, passing this item; call recursively for any defined sub-fields too. See Hoodoo::Presenters::Base#walk for why.

&block

Mandatory block, which is passed ‘self’ when called.



176
177
178
179
180
181
182
# File 'lib/hoodoo/presenters/types/array.rb', line 176

def walk( &block )
  block.call( self )

  @properties.each do | name, property |
    property.walk( &block )
  end unless @properties.nil?
end