Class: JSI::Base

Inherits:
Object
  • Object
show all
Includes:
Enumerable, FingerprintHash, Memoize
Defined in:
lib/jsi/base.rb,
lib/jsi/base/to_rb.rb

Overview

the base class for representing and instantiating a JSON Schema.

a class inheriting from JSI::Base represents a JSON Schema. an instance of that class represents a JSON schema instance.

as such, JSI::Base itself is not intended to be instantiated - subclasses are dynamically created for schemas using class_for_schema, and these are what are used to instantiate and represent JSON schema instances.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from FingerprintHash

#==, #hash

Methods included from Memoize

#clear_memo, #memoize

Constructor Details

#initialize(instance, origin: nil) ⇒ Base

initializes this JSI from the given instance. the instance will be wrapped as a JSI::JSON::Node (unless what you pass is a Node already).



72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/jsi/base.rb', line 72

def initialize(instance, origin: nil)
  unless respond_to?(:schema)
    raise(TypeError, "cannot instantiate #{self.class.inspect} which has no method #schema. please use JSI.class_for_schema")
  end

  @origin = origin || self
  self.instance = instance

  if @instance.is_a?(JSI::JSON::HashNode)
    extend BaseHash
  elsif @instance.is_a?(JSI::JSON::ArrayNode)
    extend BaseArray
  end
end

Instance Attribute Details

#instanceObject

the instance of the json-schema. this is a JSI::JSON::Node.



88
89
90
# File 'lib/jsi/base.rb', line 88

def instance
  @instance
end

Class Method Details

.class_commentObject



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
59
60
61
62
# File 'lib/jsi/base/to_rb.rb', line 4

def class_comment
  lines = []

  description = schema &&
    schema['description'].respond_to?(:to_str) &&
    schema['description'].to_str
  if description
    description.split("\n", -1).each do |descline|
      lines << "# " + descline
    end
    lines << "#"
  end

  schema.described_object_property_names.each_with_index do |propname, i|
    lines << "#" unless i == 0
    lines << "# @!attribute [rw] #{propname}"

    property_schema = schema['properties'].respond_to?(:to_hash) &&
      schema['properties'][propname].respond_to?(:to_hash) &&
      schema['properties'][propname]

    required = property_schema && property_schema['required']
    required ||= schema['required'].respond_to?(:to_ary) && schema['required'].include?(propname)
    lines << "#   @required" if required

    type = property_schema &&
      property_schema['type'].respond_to?(:to_str) &&
      property_schema['type'].to_str
    simple = {'string' => 'String', 'number' => 'Numeric', 'boolean' => 'Boolean', 'null' => 'nil'}
    rettypes = []
    if simple.key?(type)
      rettypes << simple[type]
    elsif type == 'object' || type == 'array'
      rettypes = []
      schema_class = JSI.class_for_schema(property_schema)
      unless schema_class.name =~ /\AJSI::SchemaClasses::/
        rettypes << schema_class.name
      end
      rettypes << {'object' => '#to_hash', 'array' => '#to_ary'}[type]
    elsif type
      # not really valid, but there's some information in there. whatever it is.
      rettypes << type
    end
    # we'll add Object to all because the accessor methods have no enforcement that their value is
    # of the specified type, and may return anything really. TODO: consider if this is of any value?
    rettypes << 'Object'
    lines << "#   @return [#{rettypes.join(', ')}]"

    description = property_schema &&
      property_schema['description'].respond_to?(:to_str) &&
      property_schema['description'].to_str
    if description
      description.split("\n", -1).each do |descline|
        lines << "#     " + descline
      end
    end
  end
  lines.join("\n")
end

.inspectString



25
26
27
28
29
30
31
32
33
# File 'lib/jsi/base.rb', line 25

def inspect
  if !respond_to?(:schema)
    super
  elsif !name || name =~ /\AJSI::SchemaClasses::/
    %Q(#{SchemaClasses.inspect}[#{schema_id.inspect}])
  else
    %Q(#{name} (#{schema_id}))
  end
end

.nameString



57
58
59
60
61
62
# File 'lib/jsi/base.rb', line 57

def name
  unless super
    SchemaClasses.const_set(schema_classes_const_name, self)
  end
  super
end

.schema_classes_const_nameString



49
50
51
52
53
54
# File 'lib/jsi/base.rb', line 49

def schema_classes_const_name
  name = schema.schema_id.gsub(/[^\w]/, '_')
  name = 'X' + name unless name[/\A[a-zA-Z_]/]
  name = name[0].upcase + name[1..-1]
  name
end

.schema_idString



20
21
22
# File 'lib/jsi/base.rb', line 20

def schema_id
  schema.schema_id
end

.to_rbObject



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
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
# File 'lib/jsi/base/to_rb.rb', line 64

def to_rb
  lines = []
  description = schema &&
    schema['description'].respond_to?(:to_str) &&
    schema['description'].to_str
  if description
    description.split("\n", -1).each do |descline|
      lines << "# " + descline
    end
  end
  lines << "class #{name}"
  schema.described_object_property_names.each_with_index do |propname, i|
    lines << "" unless i == 0
    property_schema = schema['properties'].respond_to?(:to_hash) &&
      schema['properties'][propname].respond_to?(:to_hash) &&
      schema['properties'][propname]
    description = property_schema &&
      property_schema['description'].respond_to?(:to_str) &&
      property_schema['description'].to_str
    if description
      description.split("\n", -1).each do |descline|
        lines << "  # " + descline
      end
      lines << "  #" # blank comment line between description and @return
    end

    required = property_schema && property_schema['required']
    required ||= schema['required'].respond_to?(:to_ary) && schema['required'].include?(propname)
    lines << "  # @required" if required

    type = property_schema &&
      property_schema['type'].respond_to?(:to_str) &&
      property_schema['type'].to_str
    simple = {'string' => 'String', 'number' => 'Numeric', 'boolean' => 'Boolean', 'null' => 'nil'}
    rettypes = []
    if simple.key?(type)
      rettypes << simple[type]
    elsif type == 'object' || type == 'array'
      rettypes = []
      schema_class = JSI.class_for_schema(property_schema)
      unless schema_class.name =~ /\AJSI::SchemaClasses::/
        rettypes << schema_class.name
      end
      rettypes << {'object' => '#to_hash', 'array' => '#to_ary'}[type]
    elsif type
      # not really valid, but there's some information in there. whatever it is.
      rettypes << type
    end
    # we'll add Object to all because the accessor methods have no enforcement that their value is
    # of the specified type, and may return anything really. TODO: consider if this is of any value?
    rettypes << 'Object'
    lines << "  # @return [#{rettypes.join(', ')}]"

    lines << "  def #{propname}"
    lines << "    super"
    lines << "  end"
  end
  lines << "end"
  lines.join("\n")
end

.to_sString



37
38
39
40
41
42
43
44
45
# File 'lib/jsi/base.rb', line 37

def to_s
  if !respond_to?(:schema)
    super
  elsif !name || name =~ /\AJSI::SchemaClasses::/
    %Q(#{SchemaClasses.inspect}[#{schema_id.inspect}])
  else
    name
  end
end

Instance Method Details

#as_json(*opt) ⇒ Object



192
193
194
# File 'lib/jsi/base.rb', line 192

def as_json(*opt)
  Typelike.as_json(instance, *opt)
end

#derefJSI::Base, self

if this JSI is a $ref then the $ref is followed. otherwise this JSI is returned.



121
122
123
124
125
126
127
128
# File 'lib/jsi/base.rb', line 121

def deref
  derefed = instance.deref
  if derefed.object_id == instance.object_id
    self
  else
    self.class.new(derefed, origin: @origin)
  end
end

#eachObject

each is overridden by BaseHash or BaseArray when appropriate. the base

each is not actually implemented, along with all the methods of Enumerable.

Raises:

  • (NoMethodError)


92
93
94
# File 'lib/jsi/base.rb', line 92

def each
  raise NoMethodError, "Enumerable methods and #each not implemented for instance that is not like a hash or array: #{instance.pretty_inspect.chomp}"
end

#fingerprintObject



197
198
199
# File 'lib/jsi/base.rb', line 197

def fingerprint
  {class: self.class, instance: instance}
end

#fragmentObject



142
143
144
# File 'lib/jsi/base.rb', line 142

def fragment
  instance.fragment
end

#fully_validateArray<String>



147
148
149
# File 'lib/jsi/base.rb', line 147

def fully_validate
  schema.fully_validate(instance)
end

#inspectString



166
167
168
# File 'lib/jsi/base.rb', line 166

def inspect
  "\#<#{self.class.to_s} #{instance.inspect}>"
end

#modified_copy {|Object| ... } ⇒ JSI::Base subclass the same as self

yields the content of the underlying instance. the block must result in a modified copy of that (not destructively modifying the yielded content) which will be used to instantiate a new instance of this JSI class with the modified content.

Yields:

  • (Object)

    the content of the instance. the block should result in a (nondestructively) modified copy of this.



137
138
139
140
# File 'lib/jsi/base.rb', line 137

def modified_copy(&block)
  modified_instance = instance.modified_copy(&block)
  self.class.new(modified_instance, origin: @origin)
end

#object_group_textString



187
188
189
# File 'lib/jsi/base.rb', line 187

def object_group_text
  instance.object_group_text
end

#parentJSI::Base?

the immediate parent of this JSI. nil if no parent(s) are known.



113
114
115
# File 'lib/jsi/base.rb', line 113

def parent
  parents.first
end

#parentsArray<JSI::Base>

an array of JSI instances above this one in the document. empty if this JSI is at the root or was instantiated from a source that does not have a document (e.g. a plain hash or array).



101
102
103
104
105
106
107
108
# File 'lib/jsi/base.rb', line 101

def parents
  parent = @origin
  (@origin.instance.path.size...self.instance.path.size).map do |i|
    parent.tap do
      parent = parent[self.instance.path[i]]
    end
  end.reverse
end

#pretty_print(q) ⇒ void

This method returns an undefined value.

pretty-prints a representation this JSI to the given printer



172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/jsi/base.rb', line 172

def pretty_print(q)
  q.instance_exec(self) do |obj|
    text "\#<#{obj.class.to_s}"
    group_sub {
      nest(2) {
        breakable ' '
        pp obj.instance
      }
    }
    breakable ''
    text '>'
  end
end

#validatetrue, false



152
153
154
# File 'lib/jsi/base.rb', line 152

def validate
  schema.validate(instance)
end

#validate!true

Returns if this method does not raise, it returns true to indicate a valid instance.

Raises:

  • (::JSON::Schema::ValidationError)

    raises if the instance has validation errors



160
161
162
# File 'lib/jsi/base.rb', line 160

def validate!
  schema.validate!(instance)
end