jsi 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/LICENSE.md +613 -0
- data/README.md +62 -37
- data/jsi.gemspec +8 -12
- data/lib/jsi.rb +11 -0
- data/lib/jsi/base.rb +196 -258
- data/lib/jsi/base/to_rb.rb +2 -0
- data/lib/jsi/jsi_coder.rb +20 -15
- data/lib/jsi/json-schema-fragments.rb +2 -0
- data/lib/jsi/json.rb +2 -0
- data/lib/jsi/json/node.rb +45 -88
- data/lib/jsi/json/pointer.rb +102 -5
- data/lib/jsi/metaschema.rb +7 -0
- data/lib/jsi/metaschema_node.rb +217 -0
- data/lib/jsi/pathed_node.rb +5 -0
- data/lib/jsi/schema.rb +146 -169
- data/lib/jsi/schema_classes.rb +112 -47
- data/lib/jsi/simple_wrap.rb +8 -3
- data/lib/jsi/typelike_modules.rb +31 -39
- data/lib/jsi/util.rb +27 -47
- data/lib/jsi/version.rb +1 -1
- data/lib/schemas/json-schema.org/draft-04/schema.rb +7 -0
- data/lib/schemas/json-schema.org/draft-06/schema.rb +7 -0
- data/resources/icons/AGPL-3.0.png +0 -0
- data/test/base_array_test.rb +174 -60
- data/test/base_hash_test.rb +179 -46
- data/test/base_test.rb +163 -94
- data/test/jsi_coder_test.rb +14 -14
- data/test/jsi_json_arraynode_test.rb +10 -10
- data/test/jsi_json_hashnode_test.rb +14 -14
- data/test/jsi_json_node_test.rb +83 -136
- data/test/jsi_typelike_as_json_test.rb +1 -1
- data/test/metaschema_node_test.rb +19 -0
- data/test/schema_module_test.rb +21 -0
- data/test/schema_test.rb +40 -50
- data/test/test_helper.rb +35 -3
- data/test/util_test.rb +8 -8
- metadata +24 -16
- data/LICENSE.txt +0 -21
data/README.md
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
[![Build Status](https://travis-ci.org/notEthan/jsi.svg?branch=master)](https://travis-ci.org/notEthan/jsi)
|
4
4
|
[![Coverage Status](https://coveralls.io/repos/github/notEthan/jsi/badge.svg)](https://coveralls.io/github/notEthan/jsi)
|
5
5
|
|
6
|
-
JSI offers an Object-Oriented representation for JSON data using JSON Schemas. Given your JSON Schemas, JSI constructs Ruby classes which are used to instantiate your JSON data. These
|
6
|
+
JSI offers an Object-Oriented representation for JSON data using JSON Schemas. Given your JSON Schemas, JSI constructs Ruby modules and classes which are used to instantiate your JSON data. These modules let you use JSON with all the niceties of OOP such as property accessors and application-defined instance methods.
|
7
7
|
|
8
8
|
To learn more about JSON Schema see [https://json-schema.org/]().
|
9
9
|
|
10
|
-
A JSI class aims to be a fairly unobtrusive wrapper around its instance - "instance" here meaning the JSON data
|
10
|
+
A JSI class aims to be a fairly unobtrusive wrapper around its instance - "instance" here meaning the JSON data, usually a Hash or Array, which instantiate the JSON Schema. JSI schema modules and classes add accessors for property names described by its schema, schema validation, and other nice things. Mostly though, you use a JSI as you would use its underlying data, calling the same methods (e.g. `#[]`, `#map`, `#repeated_permutation`) and passing it to anything that duck-types expecting `#to_ary` or `#to_hash`.
|
11
11
|
|
12
12
|
## Example
|
13
13
|
|
@@ -21,20 +21,27 @@ properties:
|
|
21
21
|
phone:
|
22
22
|
type: "array"
|
23
23
|
items:
|
24
|
+
description: "A phone number"
|
24
25
|
type: "object"
|
25
26
|
properties:
|
26
27
|
location: {type: "string"}
|
27
28
|
number: {type: "string"}
|
28
29
|
```
|
29
30
|
|
30
|
-
|
31
|
+
Using that schema, we instantiate a JSI::Schema to represent it:
|
31
32
|
|
32
33
|
```ruby
|
33
34
|
# this would usually use a YAML.load/JSON.parse/whatever; it's inlined for copypastability.
|
34
|
-
|
35
|
+
contact_schema = JSI::Schema.new({"description" => "A Contact", "type" => "object", "properties" => {"name" => {"type" => "string"}, "phone" => {"type" => "array", "items" => {"type" => "object", "properties" => {"location" => {"type" => "string"}, "number" => {"type" => "string"}}}}}})
|
35
36
|
```
|
36
37
|
|
37
|
-
|
38
|
+
We name the module that JSI will use when instantiating a contact. Named modules are better to work with, and JSI will indicate the names of schema modules in its `#inspect` output.
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
Contact = contact_schema.jsi_schema_module
|
42
|
+
```
|
43
|
+
|
44
|
+
To instantiate the schema, we need some JSON data (expressed here as YAML)
|
38
45
|
|
39
46
|
```yaml
|
40
47
|
name: bill
|
@@ -48,11 +55,14 @@ So, if we construct an instance like:
|
|
48
55
|
|
49
56
|
```ruby
|
50
57
|
# this would usually use a YAML.load/JSON.parse/whatever; it's inlined for copypastability.
|
51
|
-
bill = Contact.
|
52
|
-
# => #{<Contact
|
58
|
+
bill = Contact.new_jsi({"name" => "bill", "phone" => [{"location" => "home", "number" => "555"}], "nickname" => "big b"})
|
59
|
+
# => #{<JSI (Contact)>
|
53
60
|
# "name" => "bill",
|
54
|
-
# "phone" => #[<JSI
|
55
|
-
# #{<JSI
|
61
|
+
# "phone" => #[<JSI>
|
62
|
+
# #{<JSI>
|
63
|
+
# "location" => "home",
|
64
|
+
# "number" => "555"
|
65
|
+
# }
|
56
66
|
# ],
|
57
67
|
# "nickname" => "big b"
|
58
68
|
# }
|
@@ -60,8 +70,6 @@ bill = Contact.new({"name" => "bill", "phone" => [{"location" => "home", "number
|
|
60
70
|
|
61
71
|
Note that the keys are strings. JSI, being designed with JSON in mind, is geared toward string keys. Symbol keys will not match to schema properties, and so act the same as any other key not recognized from the schema.
|
62
72
|
|
63
|
-
The nested classes can be seen in the #inspect output as `JSI::SchemaClasses[schema_id]` where schema_id is a generated value.
|
64
|
-
|
65
73
|
We get accessors for the Contact:
|
66
74
|
|
67
75
|
```ruby
|
@@ -86,23 +94,25 @@ bill.validate
|
|
86
94
|
... and validations on the nested schema instances (#phone here), showing in this example validation failure:
|
87
95
|
|
88
96
|
```ruby
|
89
|
-
bad = Contact.
|
90
|
-
# => #{<Contact
|
91
|
-
# "phone" => #[<JSI
|
92
|
-
# #{<JSI
|
93
|
-
# "number" => #[<JSI
|
97
|
+
bad = Contact.new_jsi({'phone' => [{'number' => [5, 5, 5]}]})
|
98
|
+
# => #{<JSI (Contact)>
|
99
|
+
# "phone" => #[<JSI>
|
100
|
+
# #{<JSI>
|
101
|
+
# "number" => #[<JSI> 5, 5, 5]
|
94
102
|
# }
|
95
103
|
# ]
|
96
104
|
# }
|
97
105
|
bad.phone.fully_validate
|
98
|
-
# => ["The property '#/0/number' of type array did not match the following type: string in schema
|
106
|
+
# => ["The property '#/0/number' of type array did not match the following type: string in schema 594126e3"]
|
99
107
|
```
|
100
108
|
|
101
109
|
These validations are done by the [`json-schema` gem](https://github.com/ruby-json-schema/json-schema) - JSI does not do validations on its own.
|
102
110
|
|
103
|
-
Since the underlying instance is a ruby hash (json object), we can use it like a hash with
|
111
|
+
Since the underlying instance is a ruby hash (json object), we can use it like a hash with `#[]` or, say, `#transform_values`:
|
104
112
|
|
105
113
|
```ruby
|
114
|
+
# note that #size here is actually referring to multiple different methods; for name and nickname
|
115
|
+
# it is String#size but for phone it is Array#size.
|
106
116
|
bill.transform_values(&:size)
|
107
117
|
# => {"name" => 4, "phone" => 1, "nickname" => 5}
|
108
118
|
bill['nickname']
|
@@ -115,19 +125,20 @@ There's plenty more JSI has to offer, but this should give you a pretty good ide
|
|
115
125
|
|
116
126
|
- `JSI::Base` is the base class for each JSI class representing a JSON Schema.
|
117
127
|
- a "JSI class" is a subclass of `JSI::Base` representing a JSON schema.
|
128
|
+
- a "JSI schema module" is a module representing a schema, included on a JSI class.
|
118
129
|
- "instance" is a term that is significantly overloaded in this space, so documentation will attempt to be clear what kind of instance is meant:
|
119
130
|
- a schema instance refers broadly to a data structure that is described by a JSON schema.
|
120
|
-
- a JSI instance (or just "a JSI") is a ruby object instantiating a JSI class. it has a method
|
121
|
-
- a schema refers to a JSON schema.
|
131
|
+
- a JSI instance (or just "a JSI") is a ruby object instantiating a JSI class. it has a method `#jsi_instance` which contains the underlying data.
|
132
|
+
- a schema refers to a JSON schema. `JSI::Schema` is a module which extends schemas. A schema is usually a `JSI::Base` instance, and that schema JSI's schema is a metaschema (see the sections on Metaschemas below).
|
122
133
|
|
123
|
-
## JSI
|
134
|
+
## JSI and Object Oriented Programming
|
124
135
|
|
125
|
-
|
136
|
+
Instantiating your schema is a starting point. But, since the major point of object-oriented programming is applying methods to your objects, of course you want to be able to define your own methods. To do this we reopen the JSI module we defined. Referring back to the Example section above, we reopen the `Contact` module:
|
126
137
|
|
127
138
|
```ruby
|
128
|
-
|
129
|
-
def
|
130
|
-
|
139
|
+
module Contact
|
140
|
+
def phone_numbers
|
141
|
+
phone.map(&:number)
|
131
142
|
end
|
132
143
|
def name
|
133
144
|
super + ' esq.'
|
@@ -143,15 +154,17 @@ bill.name = 'rob esq.'
|
|
143
154
|
# => "rob esq."
|
144
155
|
bill['name']
|
145
156
|
# => "rob"
|
157
|
+
bill.phone_numbers
|
158
|
+
# => ["555"]
|
146
159
|
```
|
147
160
|
|
148
|
-
Note the use of `super` - you can call to accessors defined by JSI and make your accessors act as wrappers
|
161
|
+
Note the use of `super` - you can call to accessors defined by JSI and make your accessors act as wrappers. You can alternatively use `[]` and `[]=` with the same effect.
|
149
162
|
|
150
|
-
|
163
|
+
You can also add methods to a subschema using the same method `#jsi_schema_module` which we used to define the `Contact` module above.
|
151
164
|
|
152
165
|
```ruby
|
153
|
-
phone_schema = Contact.schema
|
154
|
-
|
166
|
+
phone_schema = Contact.schema.properties['phone'].items
|
167
|
+
phone_schema.jsi_schema_module.module_eval do
|
155
168
|
def number_with_dashes
|
156
169
|
number.split(//).join('-')
|
157
170
|
end
|
@@ -160,11 +173,11 @@ bill.phone.first.number_with_dashes
|
|
160
173
|
# => "5-5-5"
|
161
174
|
```
|
162
175
|
|
163
|
-
If you want to name the
|
176
|
+
If you want to name the module, this works:
|
164
177
|
|
165
178
|
```ruby
|
166
|
-
|
167
|
-
|
179
|
+
ContactPhone = Contact.schema.properties['phone'].items.jsi_schema_module
|
180
|
+
module ContactPhone
|
168
181
|
def number_with_dashes
|
169
182
|
number.split(//).join('-')
|
170
183
|
end
|
@@ -173,15 +186,23 @@ end
|
|
173
186
|
|
174
187
|
Either syntax is slightly cumbersome and a better syntax is in the works.
|
175
188
|
|
189
|
+
## Metaschemas
|
190
|
+
|
191
|
+
A metaschema is a schema which describes schemas. Likewise, a schema is an instance of a metaschema.
|
192
|
+
|
193
|
+
In JSI, a schema is generally a JSI::Base instance whose schema is a metaschema.
|
194
|
+
|
195
|
+
A self-descriptive metaschema - most commonly one of the JSON schema draft metaschemas - is an object whose schema is itself. This is instantiated in JSI as a JSI::MetaschemaNode (not a JSI::Base).
|
196
|
+
|
176
197
|
## ActiveRecord serialization
|
177
198
|
|
178
199
|
A really excellent place to use JSI is when dealing with serialized columns in ActiveRecord.
|
179
200
|
|
180
|
-
Let's say you're sticking to
|
201
|
+
Let's say you're sticking to JSON types in the database - you have to do so if you're using JSON columns, or JSON serialization, and if you have dealt with arbitrary yaml- or marshal-serialized objects in ruby, you have probably found that approach has its shortcomings when the implementation of your classes changes.
|
181
202
|
|
182
|
-
But if your database contains
|
203
|
+
But if your database contains JSON, then your deserialized objects in ruby are likewise Hash / Array / basic types. You have to use subscripts instead of accessors, and you don't have any way to add methods to your data types.
|
183
204
|
|
184
|
-
JSI gives you the best of both with JSICoder. This coder dumps objects which are simple
|
205
|
+
JSI gives you the best of both with JSICoder. This coder dumps objects which are simple JSON types, and loads instances of a specified JSI schema. Here's an example:
|
185
206
|
|
186
207
|
```ruby
|
187
208
|
class User < ActiveRecord::Base
|
@@ -189,7 +210,7 @@ class User < ActiveRecord::Base
|
|
189
210
|
end
|
190
211
|
```
|
191
212
|
|
192
|
-
Now `user.contacts` will return an array of Contact instances, from the
|
213
|
+
Now `user.contacts` will return an array of Contact instances, from the JSON type in the database, with Contact's accessors, validations, and user-defined instance methods.
|
193
214
|
|
194
215
|
See the gem [`arms`](https://github.com/notEthan/arms) if you wish to serialize the dumped JSON-compatible objects further as text.
|
195
216
|
|
@@ -203,4 +224,8 @@ Issues and pull requests are welcome on GitHub at https://github.com/notEthan/js
|
|
203
224
|
|
204
225
|
## License
|
205
226
|
|
206
|
-
|
227
|
+
[<img align="right" src="https://github.com/notEthan/jsi/raw/master/resources/icons/AGPL-3.0.png">](https://www.gnu.org/licenses/agpl-3.0.html)
|
228
|
+
|
229
|
+
JSI is Open Source Software licensed under the terms of the [GNU Affero General Public License version 3](https://www.gnu.org/licenses/agpl-3.0.html).
|
230
|
+
|
231
|
+
Unlike the MIT or BSD licenses more commonly used with Ruby gems, this license requires that if you modify JSI and propagate your changes, e.g. by including it in a web application, your modified version must be publicly available. The common path of forking on Github should satisfy this requirement.
|
data/jsi.gemspec
CHANGED
@@ -6,26 +6,22 @@ Gem::Specification.new do |spec|
|
|
6
6
|
spec.name = "jsi"
|
7
7
|
spec.version = JSI::VERSION
|
8
8
|
spec.authors = ["Ethan"]
|
9
|
-
spec.email = ["ethan@unth.net"]
|
9
|
+
spec.email = ["ethan.jsi@unth.net"]
|
10
10
|
|
11
11
|
spec.summary = "JSI: JSON Schema Instantiation"
|
12
12
|
spec.description = "JSI offers an Object-Oriented representation for JSON data using JSON Schemas"
|
13
13
|
spec.homepage = "https://github.com/notEthan/jsi"
|
14
|
-
spec.license = "
|
15
|
-
ignore_files
|
16
|
-
ignore_files_re
|
17
|
-
|
18
|
-
|
19
|
-
spec.test_files = `git ls-files -z test`.split("\x0")
|
20
|
-
end
|
21
|
-
spec.bindir = "exe"
|
22
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
14
|
+
spec.license = "AGPL-3.0"
|
15
|
+
ignore_files = %w(.gitignore .travis.yml Gemfile test)
|
16
|
+
ignore_files_re = %r{\A(#{ignore_files.map { |f| Regexp.escape(f) }.join('|')})(/|\z)}
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(ignore_files_re) }
|
18
|
+
spec.test_files = `git ls-files -z test`.split("\x0")
|
23
19
|
spec.require_paths = ["lib"]
|
24
20
|
|
25
21
|
# we are monkey patching json-schema with a fix that has not been merged in a timely fashion.
|
26
22
|
spec.add_dependency "json-schema", "~> 2.8"
|
27
|
-
spec.add_development_dependency "rake"
|
28
|
-
spec.add_development_dependency "minitest"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "minitest"
|
29
25
|
spec.add_development_dependency "minitest-around"
|
30
26
|
spec.add_development_dependency "minitest-reporters"
|
31
27
|
spec.add_development_dependency "scorpio"
|
data/lib/jsi.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "jsi/version"
|
2
4
|
require "pp"
|
3
5
|
require "set"
|
6
|
+
require "pathname"
|
4
7
|
require "jsi/json-schema-fragments"
|
5
8
|
require "jsi/util"
|
6
9
|
|
@@ -10,6 +13,9 @@ module JSI
|
|
10
13
|
class Bug < NotImplementedError
|
11
14
|
end
|
12
15
|
|
16
|
+
ROOT_PATH = Pathname.new(__FILE__).dirname.parent.expand_path
|
17
|
+
RESOURCES_PATH = ROOT_PATH.join('resources')
|
18
|
+
|
13
19
|
autoload :JSON, 'jsi/json'
|
14
20
|
autoload :PathedNode, 'jsi/pathed_node'
|
15
21
|
autoload :Typelike, 'jsi/typelike_modules'
|
@@ -19,9 +25,14 @@ module JSI
|
|
19
25
|
autoload :Base, 'jsi/base'
|
20
26
|
autoload :BaseArray, 'jsi/base'
|
21
27
|
autoload :BaseHash, 'jsi/base'
|
28
|
+
autoload :Metaschema, 'jsi/metaschema'
|
29
|
+
autoload :MetaschemaNode, 'jsi/metaschema_node'
|
22
30
|
autoload :SchemaClasses, 'jsi/schema_classes'
|
23
31
|
autoload :JSICoder, 'jsi/jsi_coder'
|
24
32
|
|
33
|
+
autoload :JSONSchemaOrgDraft04, 'schemas/json-schema.org/draft-04/schema'
|
34
|
+
autoload :JSONSchemaOrgDraft06, 'schemas/json-schema.org/draft-06/schema'
|
35
|
+
|
25
36
|
autoload :SimpleWrap, 'jsi/simple_wrap'
|
26
37
|
|
27
38
|
# @return [Class subclassing JSI::Base] a JSI class which represents the
|
data/lib/jsi/base.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
require 'jsi/typelike_modules'
|
3
5
|
|
@@ -16,9 +18,20 @@ module JSI
|
|
16
18
|
include PathedNode
|
17
19
|
|
18
20
|
class << self
|
21
|
+
# JSI::Base.new_jsi behaves the same as .new, and is defined for compatibility so you may call #new_jsi
|
22
|
+
# on any of a JSI::Schema, a JSI::SchemaModule, or a JSI schema class.
|
23
|
+
# @return [JSI::Base] a JSI whose instance is the given instance
|
24
|
+
def new_jsi(instance, *a, &b)
|
25
|
+
new(instance, *a, &b)
|
26
|
+
end
|
27
|
+
|
19
28
|
# is the constant JSI::SchemaClasses::{self.schema_classes_const_name} defined?
|
20
29
|
# (if so, we will prefer to use something more human-readable than that ugly mess.)
|
21
|
-
|
30
|
+
def in_schema_classes
|
31
|
+
# #name sets @in_schema_classes
|
32
|
+
name
|
33
|
+
@in_schema_classes
|
34
|
+
end
|
22
35
|
|
23
36
|
# @return [String] absolute schema_id of the schema this class represents.
|
24
37
|
# see {Schema#schema_id}.
|
@@ -26,46 +39,39 @@ module JSI
|
|
26
39
|
schema.schema_id
|
27
40
|
end
|
28
41
|
|
29
|
-
# @return [String] a string representing the class, with schema_id
|
42
|
+
# @return [String] a string representing the class, with schema_id or schema ptr fragment
|
30
43
|
def inspect
|
31
|
-
name # see #name for side effects
|
32
44
|
if !respond_to?(:schema)
|
33
45
|
super
|
34
|
-
elsif in_schema_classes
|
35
|
-
%Q(#{SchemaClasses.inspect}[#{schema_id.inspect}])
|
36
|
-
elsif !name
|
37
|
-
%Q(#<Class for Schema: #{schema_id}>)
|
38
46
|
else
|
39
|
-
|
47
|
+
idfrag = schema_id || schema.node_ptr.fragment
|
48
|
+
if name && !in_schema_classes
|
49
|
+
"#{name} (#{idfrag})"
|
50
|
+
else
|
51
|
+
"(JSI Schema Class: #{idfrag})"
|
52
|
+
end
|
40
53
|
end
|
41
54
|
end
|
42
55
|
|
43
|
-
|
44
|
-
# was explicitly defined, otherwise a reference to JSI::SchemaClasses
|
45
|
-
def to_s
|
46
|
-
if !respond_to?(:schema)
|
47
|
-
super
|
48
|
-
elsif !name || name =~ /\AJSI::SchemaClasses::/
|
49
|
-
%Q(#{SchemaClasses.inspect}[#{schema_id.inspect}])
|
50
|
-
else
|
51
|
-
name
|
52
|
-
end
|
53
|
-
end
|
56
|
+
alias_method :to_s, :inspect
|
54
57
|
|
55
58
|
# @return [String] a name for a constant for this class, generated from the
|
56
59
|
# schema_id. only used if the class is not assigned to another constant.
|
57
60
|
def schema_classes_const_name
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
name
|
61
|
+
if schema_id
|
62
|
+
'X' + schema_id.gsub(/[^\w]/, '_')
|
63
|
+
end
|
62
64
|
end
|
63
65
|
|
64
66
|
# @return [String] a constant name of this class
|
65
67
|
def name
|
66
|
-
unless
|
67
|
-
SchemaClasses.
|
68
|
-
|
68
|
+
unless instance_variable_defined?(:@in_schema_classes)
|
69
|
+
if super || !schema_id || SchemaClasses.const_defined?(schema_classes_const_name)
|
70
|
+
@in_schema_classes = false
|
71
|
+
else
|
72
|
+
SchemaClasses.const_set(schema_classes_const_name, self)
|
73
|
+
@in_schema_classes = true
|
74
|
+
end
|
69
75
|
end
|
70
76
|
super
|
71
77
|
end
|
@@ -87,17 +93,16 @@ module JSI
|
|
87
93
|
# @param jsi_ptr [JSI::JSON::Pointer] for internal use. a JSON pointer specifying
|
88
94
|
# the path of this instance in the `jsi_document` param. `jsi_ptr` must be passed
|
89
95
|
# iff `jsi_document` is passed, i.e. when `instance` is `NOINSTANCE`
|
90
|
-
# @param
|
91
|
-
|
92
|
-
def initialize(instance, jsi_document: nil, jsi_ptr: nil, ancestor_jsi: nil)
|
96
|
+
# @param jsi_root_node [JSI::Base] for internal use, specifies the JSI at the root of the document
|
97
|
+
def initialize(instance, jsi_document: nil, jsi_ptr: nil, jsi_root_node: nil)
|
93
98
|
unless respond_to?(:schema)
|
94
99
|
raise(TypeError, "cannot instantiate #{self.class.inspect} which has no method #schema. please use JSI.class_for_schema")
|
95
100
|
end
|
96
101
|
|
97
|
-
if instance.is_a?(JSI::
|
98
|
-
raise(TypeError, "assigning
|
99
|
-
elsif instance.is_a?(JSI::
|
100
|
-
raise(TypeError, "assigning
|
102
|
+
if instance.is_a?(JSI::Schema)
|
103
|
+
raise(TypeError, "assigning a schema to a #{self.class.inspect} instance is incorrect. received: #{instance.pretty_inspect.chomp}")
|
104
|
+
elsif instance.is_a?(JSI::Base)
|
105
|
+
raise(TypeError, "assigning another JSI::Base instance to a #{self.class.inspect} instance is incorrect. received: #{instance.pretty_inspect.chomp}")
|
101
106
|
end
|
102
107
|
|
103
108
|
if instance == NOINSTANCE
|
@@ -106,33 +111,33 @@ module JSI
|
|
106
111
|
raise(TypeError, "jsi_ptr must be a JSI::JSON::Pointer; got: #{jsi_ptr.inspect}")
|
107
112
|
end
|
108
113
|
@jsi_ptr = jsi_ptr
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
@jsi_document = instance.document_root_node
|
113
|
-
# this can result in the unusual situation where ancestor_jsi is nil, though jsi_ptr is not root.
|
114
|
-
# #document_root_node will then return a JSI::JSON::Pointer instead of a root JSI.
|
115
|
-
@jsi_ptr = instance.node_ptr
|
114
|
+
if @jsi_ptr.root?
|
115
|
+
raise(Bug, "jsi_root_node cannot be specified for root JSI") if jsi_root_node
|
116
|
+
@jsi_root_node = self
|
116
117
|
else
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
end
|
125
|
-
if !ancestor_jsi.jsi_ptr.contains?(@jsi_ptr)
|
126
|
-
raise(Bug, "ancestor_jsi ptr #{ancestor_jsi.jsi_ptr.inspect} is not ancestor of #{@jsi_ptr.inspect}")
|
118
|
+
if !jsi_root_node.is_a?(JSI::Base)
|
119
|
+
raise(TypeError, "jsi_root_node must be a JSI::Base; got: #{jsi_root_node.inspect}")
|
120
|
+
end
|
121
|
+
if !jsi_root_node.jsi_ptr.root?
|
122
|
+
raise(Bug, "jsi_root_node ptr #{jsi_root_node.jsi_ptr.inspect} is not root")
|
123
|
+
end
|
124
|
+
@jsi_root_node = jsi_root_node
|
127
125
|
end
|
126
|
+
else
|
127
|
+
raise(Bug, 'incorrect usage') if jsi_document || jsi_ptr || jsi_root_node
|
128
|
+
@jsi_document = instance
|
129
|
+
@jsi_ptr = JSI::JSON::Pointer.new([])
|
130
|
+
@jsi_root_node = self
|
128
131
|
end
|
129
|
-
@ancestor_jsi = ancestor_jsi
|
130
132
|
|
131
133
|
if self.jsi_instance.respond_to?(:to_hash)
|
132
134
|
extend BaseHash
|
133
135
|
elsif self.jsi_instance.respond_to?(:to_ary)
|
134
136
|
extend BaseArray
|
135
137
|
end
|
138
|
+
if self.schema.describes_schema?
|
139
|
+
extend JSI::Schema
|
140
|
+
end
|
136
141
|
end
|
137
142
|
|
138
143
|
# document containing the instance of this JSI
|
@@ -141,11 +146,12 @@ module JSI
|
|
141
146
|
# JSI::JSON::Pointer pointing to this JSI's instance within the jsi_document
|
142
147
|
attr_reader :jsi_ptr
|
143
148
|
|
144
|
-
#
|
145
|
-
attr_reader :
|
149
|
+
# the JSI at the root of this JSI's document
|
150
|
+
attr_reader :jsi_root_node
|
146
151
|
|
147
152
|
alias_method :node_document, :jsi_document
|
148
153
|
alias_method :node_ptr, :jsi_ptr
|
154
|
+
alias_method :document_root_node, :jsi_root_node
|
149
155
|
|
150
156
|
# the instance of the json-schema
|
151
157
|
alias_method :jsi_instance, :node_content
|
@@ -154,27 +160,18 @@ module JSI
|
|
154
160
|
# each is overridden by BaseHash or BaseArray when appropriate. the base
|
155
161
|
# #each is not actually implemented, along with all the methods of Enumerable.
|
156
162
|
def each
|
157
|
-
raise NoMethodError, "Enumerable methods and #each not implemented for instance that is not like a hash or array: #{
|
163
|
+
raise NoMethodError, "Enumerable methods and #each not implemented for instance that is not like a hash or array: #{jsi_instance.pretty_inspect.chomp}"
|
158
164
|
end
|
159
165
|
|
160
|
-
# an array of JSI instances above this one in the document.
|
161
|
-
# JSI does not have a known ancestor.
|
166
|
+
# an array of JSI instances above this one in the document.
|
162
167
|
#
|
163
168
|
# @return [Array<JSI::Base>]
|
164
169
|
def parent_jsis
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
parent = parent[self.jsi_ptr.reference_tokens[i]]
|
171
|
-
if current.is_a?(JSI::Base)
|
172
|
-
current
|
173
|
-
else
|
174
|
-
# sometimes after a deref, we may end up with parents whose schema we do not know.
|
175
|
-
# TODO this is kinda crap; hopefully we can remove it along with deref instantiating
|
176
|
-
# a deref ptr as the same JSI class it is
|
177
|
-
SimpleWrap.new(NOINSTANCE, jsi_document: jsi_document, jsi_ptr: jsi_ptr.take(i), ancestor_jsi: @ancestor_jsi)
|
170
|
+
parent = jsi_root_node
|
171
|
+
|
172
|
+
jsi_ptr.reference_tokens.map do |token|
|
173
|
+
parent.tap do
|
174
|
+
parent = parent[token]
|
178
175
|
end
|
179
176
|
end.reverse
|
180
177
|
end
|
@@ -186,40 +183,84 @@ module JSI
|
|
186
183
|
parent_jsis.first
|
187
184
|
end
|
188
185
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
186
|
+
alias_method :parent_node, :parent_jsi
|
187
|
+
|
188
|
+
# @deprecated
|
189
|
+
alias_method :parents, :parent_jsis
|
190
|
+
# @deprecated
|
191
|
+
alias_method :parent, :parent_jsi
|
192
|
+
|
193
|
+
# @param token [String, Integer, Object] the token to subscript
|
194
|
+
# @return [JSI::Base, Object] the instance's subscript value at the given token.
|
195
|
+
# if there is a subschema defined for that token on this JSI's schema,
|
196
|
+
# returns that value as a JSI instantiation of that subschema.
|
197
|
+
def [](token)
|
198
|
+
if respond_to?(:to_hash)
|
199
|
+
token_in_range = node_content_hash_pubsend(:key?, token)
|
200
|
+
value = node_content_hash_pubsend(:[], token)
|
201
|
+
elsif respond_to?(:to_ary)
|
202
|
+
token_in_range = node_content_ary_pubsend(:each_index).include?(token)
|
203
|
+
value = node_content_ary_pubsend(:[], token)
|
198
204
|
else
|
199
|
-
|
205
|
+
raise(NoMethodError, "cannot subcript (using token: #{token.inspect}) from instance: #{jsi_instance.pretty_inspect.chomp}")
|
200
206
|
end
|
201
|
-
end
|
202
207
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
208
|
+
jsi_memoize(:[], token, value, token_in_range) do |token, value, token_in_range|
|
209
|
+
if respond_to?(:to_ary)
|
210
|
+
token_schema = schema.subschema_for_index(token)
|
211
|
+
else
|
212
|
+
token_schema = schema.subschema_for_property(token)
|
213
|
+
end
|
214
|
+
token_schema = token_schema && token_schema.match_to_instance(value)
|
215
|
+
|
216
|
+
if token_in_range
|
217
|
+
complex_value = token_schema && (value.respond_to?(:to_hash) || value.respond_to?(:to_ary))
|
218
|
+
schema_value = token_schema && token_schema.describes_schema?
|
219
|
+
|
220
|
+
if complex_value || schema_value
|
221
|
+
class_for_schema(token_schema).new(Base::NOINSTANCE, jsi_document: @jsi_document, jsi_ptr: @jsi_ptr[token], jsi_root_node: @jsi_root_node)
|
222
|
+
else
|
223
|
+
value
|
224
|
+
end
|
225
|
+
else
|
226
|
+
defaults = Set.new
|
227
|
+
if token_schema
|
228
|
+
if token_schema.respond_to?(:to_hash) && token_schema.key?('default')
|
229
|
+
defaults << token_schema['default']
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
if defaults.size == 1
|
234
|
+
# use the default value
|
235
|
+
# we are using #dup so that we get a modified copy of self, in which we set dup[token]=default.
|
236
|
+
dup.tap { |o| o[token] = defaults.first }[token]
|
237
|
+
else
|
238
|
+
# I kind of want to just return nil here. the preferred mechanism for
|
239
|
+
# a JSI's default value should be its schema. but returning nil ignores
|
240
|
+
# any value returned by Hash#default/#default_proc. there's no compelling
|
241
|
+
# reason not to support both, so I'll return that.
|
242
|
+
value
|
243
|
+
end
|
211
244
|
end
|
212
|
-
elsif instance.is_a?(PathedNode)
|
213
|
-
instance.parent_node
|
214
|
-
else
|
215
|
-
JSI::JSON::Node.new_by_type(@jsi_document, @jsi_ptr.parent)
|
216
245
|
end
|
217
246
|
end
|
218
247
|
|
219
|
-
#
|
220
|
-
|
221
|
-
#
|
222
|
-
|
248
|
+
# assigns the subscript of the instance identified by the given token to the given value.
|
249
|
+
# if the value is a JSI, its instance is assigned instead of the JSI value itself.
|
250
|
+
#
|
251
|
+
# @param token [String, Integer, Object] token identifying the subscript to assign
|
252
|
+
# @param value [JSI::Base, Object] the value to be assigned
|
253
|
+
def []=(token, value)
|
254
|
+
unless respond_to?(:to_hash) || respond_to?(:to_ary)
|
255
|
+
raise(NoMethodError, "cannot assign subcript (using token: #{token.inspect}) to instance: #{jsi_instance.pretty_inspect.chomp}")
|
256
|
+
end
|
257
|
+
jsi_clear_memo(:[])
|
258
|
+
if value.is_a?(Base)
|
259
|
+
self[token] = value.jsi_instance
|
260
|
+
else
|
261
|
+
jsi_instance[token] = value
|
262
|
+
end
|
263
|
+
end
|
223
264
|
|
224
265
|
# if this JSI is a $ref then the $ref is followed. otherwise this JSI
|
225
266
|
# is returned.
|
@@ -230,19 +271,7 @@ module JSI
|
|
230
271
|
# @return [JSI::Base, self]
|
231
272
|
def deref(&block)
|
232
273
|
node_ptr_deref do |deref_ptr|
|
233
|
-
|
234
|
-
if jsi_from_root.is_a?(JSI::Base)
|
235
|
-
return jsi_from_root.tap(&(block || Util::NOOP))
|
236
|
-
else
|
237
|
-
# TODO I want to get rid of this ... just return jsi_from_root whatever it is
|
238
|
-
# NOTE when I get rid of this, simplify #parent_jsis too
|
239
|
-
if @ancestor_jsi && @ancestor_jsi.jsi_ptr.contains?(deref_ptr)
|
240
|
-
derefed = self.class.new(Base::NOINSTANCE, jsi_document: @jsi_document, jsi_ptr: deref_ptr, ancestor_jsi: @ancestor_jsi)
|
241
|
-
else
|
242
|
-
derefed = self.class.new(Base::NOINSTANCE, jsi_document: @jsi_document, jsi_ptr: deref_ptr)
|
243
|
-
end
|
244
|
-
return derefed.tap(&(block || Util::NOOP))
|
245
|
-
end
|
274
|
+
deref_ptr.evaluate(jsi_root_node).tap(&(block || Util::NOOP))
|
246
275
|
end
|
247
276
|
return self
|
248
277
|
end
|
@@ -255,33 +284,25 @@ module JSI
|
|
255
284
|
# in a (nondestructively) modified copy of this.
|
256
285
|
# @return [JSI::Base subclass the same as self] the modified copy of self
|
257
286
|
def modified_copy(&block)
|
258
|
-
if
|
259
|
-
raise(Bug, 'bad @ancestor_jsi') if @ancestor_jsi.object_id == self.object_id
|
260
|
-
|
261
|
-
modified_ancestor = @ancestor_jsi.modified_copy do |anc|
|
262
|
-
mod_anc = @jsi_ptr.ptr_relative_to(@ancestor_jsi.jsi_ptr).modified_document_copy(anc, &block)
|
263
|
-
mod_anc
|
264
|
-
end
|
265
|
-
self.class.new(Base::NOINSTANCE, jsi_document: modified_ancestor.jsi_document, jsi_ptr: @jsi_ptr, ancestor_jsi: modified_ancestor)
|
266
|
-
else
|
287
|
+
if node_ptr.root?
|
267
288
|
modified_document = @jsi_ptr.modified_document_copy(@jsi_document, &block)
|
268
289
|
self.class.new(Base::NOINSTANCE, jsi_document: modified_document, jsi_ptr: @jsi_ptr)
|
290
|
+
else
|
291
|
+
modified_jsi_root_node = @jsi_root_node.modified_copy do |root|
|
292
|
+
@jsi_ptr.modified_document_copy(root, &block)
|
293
|
+
end
|
294
|
+
self.class.new(Base::NOINSTANCE, jsi_document: modified_jsi_root_node.node_document, jsi_ptr: @jsi_ptr, jsi_root_node: modified_jsi_root_node)
|
269
295
|
end
|
270
296
|
end
|
271
297
|
|
272
|
-
# @return [
|
273
|
-
def
|
274
|
-
|
275
|
-
end
|
276
|
-
|
277
|
-
# @return [Array<String>] array of schema validation error messages for this instance
|
278
|
-
def fully_validate
|
279
|
-
schema.fully_validate_instance(instance)
|
298
|
+
# @return [Array] array of schema validation errors for this instance
|
299
|
+
def fully_validate(errors_as_objects: false)
|
300
|
+
schema.fully_validate_instance(jsi_instance, errors_as_objects: errors_as_objects)
|
280
301
|
end
|
281
302
|
|
282
303
|
# @return [true, false] whether the instance validates against its schema
|
283
304
|
def validate
|
284
|
-
schema.validate_instance(
|
305
|
+
schema.validate_instance(jsi_instance)
|
285
306
|
end
|
286
307
|
|
287
308
|
# @return [true] if this method does not raise, it returns true to
|
@@ -289,7 +310,7 @@ module JSI
|
|
289
310
|
# @raise [::JSON::Schema::ValidationError] raises if the instance has
|
290
311
|
# validation errors
|
291
312
|
def validate!
|
292
|
-
schema.validate_instance!(
|
313
|
+
schema.validate_instance!(jsi_instance)
|
293
314
|
end
|
294
315
|
|
295
316
|
def dup
|
@@ -299,56 +320,77 @@ module JSI
|
|
299
320
|
# @return [String] a string representing this JSI, indicating its class
|
300
321
|
# and inspecting its instance
|
301
322
|
def inspect
|
302
|
-
"\#<#{
|
323
|
+
"\#<#{object_group_text.join(' ')} #{jsi_instance.inspect}>"
|
303
324
|
end
|
304
325
|
|
305
326
|
# pretty-prints a representation this JSI to the given printer
|
306
327
|
# @return [void]
|
307
328
|
def pretty_print(q)
|
308
|
-
q.
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
}
|
329
|
+
q.text '#<'
|
330
|
+
q.text object_group_text.join(' ')
|
331
|
+
q.group_sub {
|
332
|
+
q.nest(2) {
|
333
|
+
q.breakable ' '
|
334
|
+
q.pp jsi_instance
|
315
335
|
}
|
316
|
-
|
317
|
-
|
318
|
-
|
336
|
+
}
|
337
|
+
q.breakable ''
|
338
|
+
q.text '>'
|
319
339
|
end
|
320
340
|
|
321
341
|
# @return [Array<String>]
|
322
342
|
def object_group_text
|
323
|
-
|
343
|
+
class_name = self.class.name unless self.class.in_schema_classes
|
344
|
+
class_txt = begin
|
345
|
+
if class_name
|
346
|
+
# ignore ID
|
347
|
+
schema_name = schema.jsi_schema_module.name
|
348
|
+
if !schema_name
|
349
|
+
class_name
|
350
|
+
else
|
351
|
+
"#{class_name} (#{schema_name})"
|
352
|
+
end
|
353
|
+
else
|
354
|
+
schema_name = schema.jsi_schema_module.name || schema.schema_id
|
355
|
+
if !schema_name
|
356
|
+
"JSI"
|
357
|
+
else
|
358
|
+
"JSI (#{schema_name})"
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
if (is_a?(PathedArrayNode) || is_a?(PathedHashNode)) && ![Array, Hash].include?(node_content.class)
|
364
|
+
if node_content.respond_to?(:object_group_text)
|
365
|
+
node_content_txt = node_content.object_group_text
|
366
|
+
else
|
367
|
+
node_content_txt = [node_content.class.to_s]
|
368
|
+
end
|
369
|
+
else
|
370
|
+
node_content_txt = []
|
371
|
+
end
|
372
|
+
|
373
|
+
[
|
374
|
+
class_txt,
|
375
|
+
is_a?(Metaschema) ? "Metaschema" : is_a?(Schema) ? "Schema" : nil,
|
376
|
+
*node_content_txt,
|
377
|
+
].compact
|
324
378
|
end
|
325
379
|
|
326
380
|
# @return [Object] a jsonifiable representation of the instance
|
327
381
|
def as_json(*opt)
|
328
|
-
Typelike.as_json(
|
382
|
+
Typelike.as_json(jsi_instance, *opt)
|
329
383
|
end
|
330
384
|
|
331
385
|
# @return [Object] an opaque fingerprint of this JSI for FingerprintHash. JSIs are equal
|
332
386
|
# if their instances are equal, and if the JSIs are of the same JSI class or subclass.
|
333
|
-
def
|
387
|
+
def jsi_fingerprint
|
334
388
|
{class: jsi_class, jsi_document: jsi_document, jsi_ptr: jsi_ptr}
|
335
389
|
end
|
336
390
|
include FingerprintHash
|
337
391
|
|
338
392
|
private
|
339
393
|
|
340
|
-
# assigns a subscript, unwrapping a JSI if given.
|
341
|
-
# @param subscript [Object] the bit between the [ and ]
|
342
|
-
# @param value [JSI::Base, Object] the value to be assigned
|
343
|
-
def subscript_assign(subscript, value)
|
344
|
-
clear_memo(:[])
|
345
|
-
if value.is_a?(Base)
|
346
|
-
instance[subscript] = value.instance
|
347
|
-
else
|
348
|
-
instance[subscript] = value
|
349
|
-
end
|
350
|
-
end
|
351
|
-
|
352
394
|
# this is an instance method in order to allow subclasses of JSI classes to
|
353
395
|
# override it to point to other subclasses corresponding to other schemas.
|
354
396
|
def class_for_schema(schema)
|
@@ -359,114 +401,10 @@ module JSI
|
|
359
401
|
# module extending a {JSI::Base} object when its instance is Hash-like (responds to #to_hash)
|
360
402
|
module BaseHash
|
361
403
|
include PathedHashNode
|
362
|
-
|
363
|
-
alias_method :jsi_instance_hash_pubsend, :node_content_hash_pubsend
|
364
|
-
|
365
|
-
# @param property_name [String, Object] the property name to subscript
|
366
|
-
# @return [JSI::Base, Object] the instance's subscript value at the given
|
367
|
-
# key property_name_. if there is a subschema defined for that property
|
368
|
-
# on this JSI's schema, returns the instance's subscript as a JSI
|
369
|
-
# instiation of that subschema.
|
370
|
-
def [](property_name)
|
371
|
-
instance_property_key_ = jsi_instance_hash_pubsend(:key?, property_name)
|
372
|
-
if !instance_property_key_
|
373
|
-
deref do |deref_jsi|
|
374
|
-
return deref_jsi[property_name]
|
375
|
-
end
|
376
|
-
end
|
377
|
-
instance_property_value_ = jsi_instance_sub(property_name)
|
378
|
-
memoize(:[], property_name, instance_property_value_, instance_property_key_) do |property_name_, instance_property_value, instance_property_key|
|
379
|
-
begin
|
380
|
-
property_schema = schema.subschema_for_property(property_name_)
|
381
|
-
property_schema = property_schema && property_schema.match_to_instance(instance_property_value)
|
382
|
-
|
383
|
-
if !instance_property_key && property_schema && property_schema.schema_object.key?('default')
|
384
|
-
# use the default value
|
385
|
-
default = property_schema.schema_object['default']
|
386
|
-
if default.respond_to?(:to_hash) || default.respond_to?(:to_ary)
|
387
|
-
# we are using #dup so that we get a modified copy of self, in which we set dup[property_name_]=default.
|
388
|
-
# this avoids duplication of code with #modified_copy and below in #[] to handle pathing and such.
|
389
|
-
dup.tap { |o| o[property_name_] = default }[property_name_]
|
390
|
-
else
|
391
|
-
default
|
392
|
-
end
|
393
|
-
elsif property_schema && (instance_property_value.respond_to?(:to_hash) || instance_property_value.respond_to?(:to_ary))
|
394
|
-
class_for_schema(property_schema).new(Base::NOINSTANCE, jsi_document: @jsi_document, jsi_ptr: @jsi_ptr[property_name_], ancestor_jsi: @ancestor_jsi || self)
|
395
|
-
else
|
396
|
-
instance_property_value
|
397
|
-
end
|
398
|
-
end
|
399
|
-
end
|
400
|
-
end
|
401
|
-
|
402
|
-
# assigns the given property name of the instance to the given value.
|
403
|
-
# if the value is a JSI, its instance is assigned.
|
404
|
-
# @param property_name [Object] this should generally be a String, but JSI
|
405
|
-
# does not enforce any constraint on it.
|
406
|
-
# @param value [Object] the value to be assigned to the given subscript
|
407
|
-
# property_name
|
408
|
-
def []=(property_name, value)
|
409
|
-
subscript_assign(property_name, value)
|
410
|
-
end
|
411
|
-
|
412
|
-
private
|
413
|
-
|
414
|
-
# @param token [String, Object]
|
415
|
-
# @return [Object]
|
416
|
-
def jsi_instance_sub(token)
|
417
|
-
jsi_instance_hash_pubsend(:[], token)
|
418
|
-
end
|
419
404
|
end
|
420
405
|
|
421
406
|
# module extending a {JSI::Base} object when its instance is Array-like (responds to #to_ary)
|
422
407
|
module BaseArray
|
423
408
|
include PathedArrayNode
|
424
|
-
|
425
|
-
alias_method :jsi_instance_ary_pubsend, :node_content_ary_pubsend
|
426
|
-
|
427
|
-
# @param i [Integer] the array index to subscript
|
428
|
-
# @return [JSI::Base, Object] the instance's subscript value at the given index
|
429
|
-
# i. if there is a subschema defined for that index on this JSI's schema,
|
430
|
-
# returns the instance's subscript as a JSI instiation of that subschema.
|
431
|
-
def [](i)
|
432
|
-
memoize(:[], i, jsi_instance_sub(i), jsi_instance_ary_pubsend(:each_index).to_a.include?(i)) do |i_, instance_idx_value, i_in_range|
|
433
|
-
begin
|
434
|
-
index_schema = schema.subschema_for_index(i_)
|
435
|
-
index_schema = index_schema && index_schema.match_to_instance(instance_idx_value)
|
436
|
-
|
437
|
-
if !i_in_range && index_schema && index_schema.schema_object.key?('default')
|
438
|
-
# use the default value
|
439
|
-
default = index_schema.schema_object['default']
|
440
|
-
if default.respond_to?(:to_hash) || default.respond_to?(:to_ary)
|
441
|
-
# we are using #dup so that we get a modified copy of self, in which we set dup[i]=default.
|
442
|
-
# this avoids duplication of code with #modified_copy and below in #[] to handle pathing and such.
|
443
|
-
dup.tap { |o| o[i_] = default }[i_]
|
444
|
-
else
|
445
|
-
default
|
446
|
-
end
|
447
|
-
elsif index_schema && (instance_idx_value.respond_to?(:to_hash) || instance_idx_value.respond_to?(:to_ary))
|
448
|
-
class_for_schema(index_schema).new(Base::NOINSTANCE, jsi_document: @jsi_document, jsi_ptr: @jsi_ptr[i_], ancestor_jsi: @ancestor_jsi || self)
|
449
|
-
else
|
450
|
-
instance_idx_value
|
451
|
-
end
|
452
|
-
end
|
453
|
-
end
|
454
|
-
end
|
455
|
-
|
456
|
-
# assigns the given index of the instance to the given value.
|
457
|
-
# if the value is a JSI, its instance is assigned.
|
458
|
-
# @param i [Object] the array index to assign
|
459
|
-
# @param value [Object] the value to be assigned to the given subscript i
|
460
|
-
def []=(i, value)
|
461
|
-
subscript_assign(i, value)
|
462
|
-
end
|
463
|
-
|
464
|
-
private
|
465
|
-
|
466
|
-
# @param token [Integer]
|
467
|
-
# @return [Object]
|
468
|
-
def jsi_instance_sub(token)
|
469
|
-
jsi_instance_ary_pubsend(:[], token)
|
470
|
-
end
|
471
409
|
end
|
472
410
|
end
|