jsi 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/CHANGELOG.md +7 -0
- data/README.md +49 -17
- data/lib/jsi.rb +4 -1
- data/lib/jsi/base.rb +147 -26
- data/lib/jsi/base/to_rb.rb +2 -3
- data/lib/jsi/json-schema-fragments.rb +6 -5
- data/lib/jsi/json/node.rb +117 -46
- data/lib/jsi/schema.rb +94 -62
- data/lib/jsi/typelike_modules.rb +65 -15
- data/lib/jsi/util.rb +30 -16
- data/lib/jsi/version.rb +1 -1
- data/test/base_array_test.rb +2 -2
- data/test/base_hash_test.rb +7 -7
- data/test/base_test.rb +19 -19
- data/test/jsi_json_arraynode_test.rb +133 -117
- data/test/jsi_json_hashnode_test.rb +116 -102
- data/test/jsi_json_node_test.rb +13 -13
- data/test/schema_instance_json_coder_test.rb +6 -7
- data/test/schema_test.rb +196 -0
- data/test/struct_json_coder_test.rb +2 -2
- data/test/test_helper.rb +36 -2
- data/test/util_test.rb +4 -4
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61dd1e2dc8c145ccf5acdcd6a0d6eaeef0d61dd85e876347af89f7168e838319
|
4
|
+
data.tar.gz: de6f01e5e859f2b3039b7d2c6f734b1e30b97b8c946f2444783581931d5939e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c15c232306814b19a3b84f94c9bd5bacfa261c7124e007ad8961e6e7a28495c23e675963aca024d2abb29613ba9092313cf11b0344feb3e20f38dec6dcf511b1
|
7
|
+
data.tar.gz: c00160e62da570c862b694d516bb157019b1799dcff24f594f494fa695c45fcfaa4a5e34e93b8bf35978c28e9bf13e84ed86b3938eff3b7443c6e4cae81118af
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--main README.md --markup=markdown {lib}/**/*.rb
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
JSI represents JSON-schemas as ruby classes, and schema instances as instances of those classes.
|
7
7
|
|
8
|
-
A JSI class aims to be a fairly unobtrusive wrapper around its instance. It adds accessors for known property names, validation methods, and a few other nice things. Mostly though, you use a JSI as you would use its underlying data.
|
8
|
+
A JSI class aims to be a fairly unobtrusive wrapper around its instance. It adds accessors for known property names, validation methods, and a few 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.
|
9
9
|
|
10
10
|
## Example
|
11
11
|
|
@@ -25,25 +25,29 @@ properties:
|
|
25
25
|
number: {type: "string"}
|
26
26
|
```
|
27
27
|
|
28
|
-
And here's
|
28
|
+
And here's the class for that schema from JSI:
|
29
29
|
|
30
30
|
```ruby
|
31
31
|
Contact = JSI.class_for_schema(YAML.load_file('contact.schema.yml'))
|
32
|
+
# you can copy/paste this line instead, to follow along in irb:
|
33
|
+
Contact = JSI.class_for_schema({"description" => "A Contact", "type" => "object", "properties" => {"name" => {"type" => "string"}, "phone" => {"type" => "array", "items" => {"type" => "object", "properties" => {"location" => {"type" => "string"}, "number" => {"type" => "string"}}}}}})
|
32
34
|
```
|
33
35
|
|
34
36
|
This definition gives you not just the Contact class, but classes for the whole nested structure. So, if we construct an instance like:
|
35
37
|
|
36
38
|
```ruby
|
37
|
-
bill = Contact.new(name
|
39
|
+
bill = Contact.new('name' => 'bill', 'phone' => [{'location' => 'home', 'number' => '555'}], 'nickname' => 'big b')
|
38
40
|
# => #{<Contact fragment="#">
|
39
41
|
# #{<Contact fragment="#">
|
40
|
-
# "phone" => #[<JSI::SchemaClasses["
|
41
|
-
# #{<JSI::SchemaClasses["
|
42
|
+
# "phone" => #[<JSI::SchemaClasses["1f97#/properties/phone"] fragment="#/phone">
|
43
|
+
# #{<JSI::SchemaClasses["1f97#/properties/phone/items"] fragment="#/phone/0"> "location" => "home", "number" => "555"}
|
42
44
|
# ],
|
43
45
|
# "nickname" => "big b"
|
44
46
|
# }
|
45
47
|
```
|
46
48
|
|
49
|
+
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.
|
50
|
+
|
47
51
|
The nested classes can be seen as `JSI::SchemaClasses[schema_id]` where schema_id is a generated value.
|
48
52
|
|
49
53
|
We get accessors for the Contact:
|
@@ -70,18 +74,20 @@ bill.validate
|
|
70
74
|
... and validations on the nested schema instances (#phone here), showing in this example validation failure:
|
71
75
|
|
72
76
|
```ruby
|
73
|
-
bad = Contact.new(phone
|
77
|
+
bad = Contact.new('phone' => [{'number' => [5, 5, 5]}])
|
74
78
|
# => #{<Contact fragment="#">
|
75
|
-
# "phone" => #[<JSI::SchemaClasses["
|
76
|
-
# #{<JSI::SchemaClasses["
|
77
|
-
# "number" => #[<JSI::SchemaClasses["
|
79
|
+
# "phone" => #[<JSI::SchemaClasses["1f97#/properties/phone"] fragment="#/phone">
|
80
|
+
# #{<JSI::SchemaClasses["1f97#/properties/phone/items"] fragment="#/phone/0">
|
81
|
+
# "number" => #[<JSI::SchemaClasses["1f97#/properties/phone/items/properties/number"] fragment="#/phone/0/number"> 5, 5, 5]
|
78
82
|
# }
|
79
83
|
# ]
|
80
84
|
# }
|
81
85
|
bad.phone.fully_validate
|
82
|
-
# => ["The property '#/0/number' of type array did not match the following type: string in schema
|
86
|
+
# => ["The property '#/0/number' of type array did not match the following type: string in schema 1f97"]
|
83
87
|
```
|
84
88
|
|
89
|
+
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.
|
90
|
+
|
85
91
|
Since the underlying instance is a ruby hash (json object), we can use it like a hash with #[] or, say, #transform_values:
|
86
92
|
|
87
93
|
```ruby
|
@@ -93,18 +99,18 @@ bill['nickname']
|
|
93
99
|
|
94
100
|
There's plenty more JSI has to offer, but this should give you a pretty good idea of basic usage.
|
95
101
|
|
96
|
-
## Terminology
|
102
|
+
## Terminology and Concepts
|
97
103
|
|
98
104
|
- JSI::Base is the base class from which other classes representing JSON-Schemas inherit.
|
99
105
|
- a JSI class refers to a class representing a schema, a subclass of JSI::Base.
|
100
106
|
- "instance" is a term that is significantly overloaded in this space, so documentation will attempt to be clear what kind of instance is meant:
|
101
107
|
- a schema instance refers broadly to a data structure that is described by a json-schema.
|
102
|
-
- a JSI instance is a ruby object instantiating a JSI class. it has a method #instance which contains the underlying data.
|
103
|
-
- a schema refers to a json-schema. a JSI::Schema represents such a json-schema. a JSI class
|
108
|
+
- a JSI instance (or just "a JSI") is a ruby object instantiating a JSI class. it has a method #instance which contains the underlying data.
|
109
|
+
- a schema refers to a json-schema. a JSI::Schema represents such a json-schema. a JSI class allows instantiation of such a schema.
|
104
110
|
|
105
111
|
## JSI classes
|
106
112
|
|
107
|
-
A JSI class (that is, subclass of JSI::Base) is a starting point but obviously you want your own methods, so you reopen the class as you would any other. referring back to the Example section above,
|
113
|
+
A JSI class (that is, subclass of JSI::Base) is a starting point but obviously you want your own methods, so you reopen the class as you would any other. referring back to the Example section above, we reopen the Contact class:
|
108
114
|
|
109
115
|
```ruby
|
110
116
|
class Contact
|
@@ -123,11 +129,37 @@ bill.name
|
|
123
129
|
# => "bill esq."
|
124
130
|
bill.name = 'rob esq.'
|
125
131
|
# => "rob esq."
|
126
|
-
bill
|
132
|
+
bill['name']
|
127
133
|
# => "rob"
|
128
134
|
```
|
129
135
|
|
130
|
-
Note the use of `super` - you can call to accessors defined by JSI and make your accessors act as wrappers (these accessor methods are defined on an included
|
136
|
+
Note the use of `super` - you can call to accessors defined by JSI and make your accessors act as wrappers (these accessor methods are defined on an included module instead of the JSI class for this reason). You can also use [] and []=, of course, with the same effect.
|
137
|
+
|
138
|
+
If you want to add methods to a subschema, get the class_for_schema for that schema and open up that class. You can leave the class anonymous, as in this example:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
phone_schema = Contact.schema['properties']['phone']['items']
|
142
|
+
JSI.class_for_schema(phone_schema).class_eval do
|
143
|
+
def number_with_dashes
|
144
|
+
number.split(//).join('-')
|
145
|
+
end
|
146
|
+
end
|
147
|
+
bill.phone.first.number_with_dashes
|
148
|
+
# => "5-5-5"
|
149
|
+
```
|
150
|
+
|
151
|
+
If you want to name the class, this works:
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
Phone = JSI.class_for_schema(Contact.schema['properties']['phone']['items'])
|
155
|
+
class Phone
|
156
|
+
def number_with_dashes
|
157
|
+
number.split(//).join('-')
|
158
|
+
end
|
159
|
+
end
|
160
|
+
```
|
161
|
+
|
162
|
+
Either syntax is slightly cumbersome and a better syntax is in the works.
|
131
163
|
|
132
164
|
## ActiveRecord serialization
|
133
165
|
|
@@ -149,7 +181,7 @@ Now `user.contacts` will return an array of Contact instances, from the json typ
|
|
149
181
|
|
150
182
|
## Keying Hashes (JSON Objects)
|
151
183
|
|
152
|
-
Unlike Ruby, JSON only supports string keys.
|
184
|
+
Unlike Ruby, JSON only supports string keys. It is recommended to use strings as hash keys for all JSI instances, but JSI does not enforce this, nor does it do any key conversion. It should be possible to use ActiveSupport::HashWithIndifferentAccess as the instance of a JSI in order to gain the benefits that offers over a plain hash. This is not tested behavior, but JSI should behave correctly with any instance that responds to #to_hash.
|
153
185
|
|
154
186
|
## Contributing
|
155
187
|
|
data/lib/jsi.rb
CHANGED
@@ -20,8 +20,11 @@ module JSI
|
|
20
20
|
autoload :SchemaClasses, 'jsi/base'
|
21
21
|
autoload :ObjectJSONCoder, 'jsi/schema_instance_json_coder'
|
22
22
|
autoload :StructJSONCoder, 'jsi/struct_json_coder'
|
23
|
-
autoload :SchemaInstanceJSONCoder,'jsi/schema_instance_json_coder'
|
23
|
+
autoload :SchemaInstanceJSONCoder, 'jsi/schema_instance_json_coder'
|
24
24
|
|
25
|
+
# @return [Class subclassing JSI::Base] a JSI class which represents the
|
26
|
+
# given schema. instances of the class represent JSON Schema instances
|
27
|
+
# for the given schema.
|
25
28
|
def self.class_for_schema(*a, &b)
|
26
29
|
SchemaClasses.class_for_schema(*a, &b)
|
27
30
|
end
|
data/lib/jsi/base.rb
CHANGED
@@ -2,16 +2,26 @@ require 'json'
|
|
2
2
|
require 'jsi/typelike_modules'
|
3
3
|
|
4
4
|
module JSI
|
5
|
-
# base class for representing
|
5
|
+
# the base class for representing and instantiating a JSON Schema.
|
6
|
+
#
|
7
|
+
# a class inheriting from JSI::Base represents a JSON Schema. an instance of
|
8
|
+
# that class represents a JSON schema instance.
|
9
|
+
#
|
10
|
+
# as such, JSI::Base itself is not intended to be instantiated - subclasses
|
11
|
+
# are dynamically created for schemas using {JSI.class_for_schema}, and these
|
12
|
+
# are what are used to instantiate and represent JSON schema instances.
|
6
13
|
class Base
|
7
14
|
include Memoize
|
8
15
|
include Enumerable
|
9
16
|
|
10
17
|
class << self
|
18
|
+
# @return [String] absolute schema_id of the schema this class represents.
|
19
|
+
# see {Schema#schema_id}.
|
11
20
|
def schema_id
|
12
21
|
schema.schema_id
|
13
22
|
end
|
14
23
|
|
24
|
+
# @return [String] a string representing the class, with schema_id
|
15
25
|
def inspect
|
16
26
|
if !respond_to?(:schema)
|
17
27
|
super
|
@@ -21,6 +31,9 @@ module JSI
|
|
21
31
|
%Q(#{name} (#{schema_id}))
|
22
32
|
end
|
23
33
|
end
|
34
|
+
|
35
|
+
# @return [String] a string representing the class - a class name if one
|
36
|
+
# was explicitly defined, otherwise a reference to JSI::SchemaClasses
|
24
37
|
def to_s
|
25
38
|
if !respond_to?(:schema)
|
26
39
|
super
|
@@ -31,6 +44,8 @@ module JSI
|
|
31
44
|
end
|
32
45
|
end
|
33
46
|
|
47
|
+
# @return [String] a name for a constant for this class, generated from the
|
48
|
+
# schema_id. only used if the class is not assigned to another constant.
|
34
49
|
def schema_classes_const_name
|
35
50
|
name = schema.schema_id.gsub(/[^\w]/, '_')
|
36
51
|
name = 'X' + name unless name[/\A[a-zA-Z_]/]
|
@@ -38,6 +53,7 @@ module JSI
|
|
38
53
|
name
|
39
54
|
end
|
40
55
|
|
56
|
+
# @return [String] a constant name of this class
|
41
57
|
def name
|
42
58
|
unless super
|
43
59
|
SchemaClasses.const_set(schema_classes_const_name, self)
|
@@ -46,6 +62,13 @@ module JSI
|
|
46
62
|
end
|
47
63
|
end
|
48
64
|
|
65
|
+
# initializes this JSI from the given instance. the instance will be
|
66
|
+
# wrapped as a {JSI::JSON::Node JSI::JSON::Node} (unless what you pass is
|
67
|
+
# a Node already).
|
68
|
+
#
|
69
|
+
# @param instance [Object] the JSON Schema instance being represented
|
70
|
+
# @param origin [JSI::Base] for internal use, specifies a parent
|
71
|
+
# from which this JSI originated
|
49
72
|
def initialize(instance, origin: nil)
|
50
73
|
unless respond_to?(:schema)
|
51
74
|
raise(TypeError, "cannot instantiate #{self.class.inspect} which has no method #schema. please use JSI.class_for_schema")
|
@@ -61,6 +84,7 @@ module JSI
|
|
61
84
|
end
|
62
85
|
end
|
63
86
|
|
87
|
+
# the instance of the json-schema. this is a JSI::JSON::Node.
|
64
88
|
attr_reader :instance
|
65
89
|
|
66
90
|
# each is overridden by BaseHash or BaseArray when appropriate. the base
|
@@ -69,6 +93,11 @@ module JSI
|
|
69
93
|
raise NoMethodError, "Enumerable methods and #each not implemented for instance that is not like a hash or array: #{instance.pretty_inspect.chomp}"
|
70
94
|
end
|
71
95
|
|
96
|
+
# an array of JSI instances above this one in the document. empty if this
|
97
|
+
# JSI is at the root or was instantiated from a source that does not have
|
98
|
+
# a document (e.g. a plain hash or array).
|
99
|
+
#
|
100
|
+
# @return [Array<JSI::Base>]
|
72
101
|
def parents
|
73
102
|
parent = @origin
|
74
103
|
(@origin.instance.path.size...self.instance.path.size).map do |i|
|
@@ -77,10 +106,18 @@ module JSI
|
|
77
106
|
end
|
78
107
|
end.reverse
|
79
108
|
end
|
109
|
+
|
110
|
+
# the immediate parent of this JSI. nil if no parent(s) are known.
|
111
|
+
#
|
112
|
+
# @return [JSI::Base, nil]
|
80
113
|
def parent
|
81
114
|
parents.first
|
82
115
|
end
|
83
116
|
|
117
|
+
# if this JSI is a $ref then the $ref is followed. otherwise this JSI
|
118
|
+
# is returned.
|
119
|
+
#
|
120
|
+
# @return [JSI::Base, self]
|
84
121
|
def deref
|
85
122
|
derefed = instance.deref
|
86
123
|
if derefed.object_id == instance.object_id
|
@@ -90,6 +127,13 @@ module JSI
|
|
90
127
|
end
|
91
128
|
end
|
92
129
|
|
130
|
+
# yields the content of the underlying instance. the block must result in
|
131
|
+
# a modified copy of that (not destructively modifying the yielded content)
|
132
|
+
# which will be used to instantiate a new instance of this JSI class with
|
133
|
+
# the modified content.
|
134
|
+
# @yield [Object] the content of the instance. the block should result
|
135
|
+
# in a (nondestructively) modified copy of this.
|
136
|
+
# @return [JSI::Base subclass the same as self] the modified copy of self
|
93
137
|
def modified_copy(&block)
|
94
138
|
modified_instance = instance.modified_copy(&block)
|
95
139
|
self.class.new(modified_instance, origin: @origin)
|
@@ -99,18 +143,32 @@ module JSI
|
|
99
143
|
instance.fragment
|
100
144
|
end
|
101
145
|
|
146
|
+
# @return [Array<String>] array of schema validation error messages for this instance
|
102
147
|
def fully_validate
|
103
148
|
schema.fully_validate(instance)
|
104
149
|
end
|
150
|
+
|
151
|
+
# @return [true, false] whether the instance validates against its schema
|
105
152
|
def validate
|
106
153
|
schema.validate(instance)
|
107
154
|
end
|
155
|
+
|
156
|
+
# @return [true] if this method does not raise, it returns true to
|
157
|
+
# indicate a valid instance.
|
158
|
+
# @raise [::JSON::Schema::ValidationError] raises if the instance has
|
159
|
+
# validation errors
|
108
160
|
def validate!
|
109
161
|
schema.validate!(instance)
|
110
162
|
end
|
163
|
+
|
164
|
+
# @return [String] a string representing this JSI, indicating its class
|
165
|
+
# and inspecting its instance
|
111
166
|
def inspect
|
112
167
|
"\#<#{self.class.to_s} #{instance.inspect}>"
|
113
168
|
end
|
169
|
+
|
170
|
+
# pretty-prints a representation this JSI to the given printer
|
171
|
+
# @return [void]
|
114
172
|
def pretty_print(q)
|
115
173
|
q.instance_exec(self) do |obj|
|
116
174
|
text "\#<#{obj.class.to_s}"
|
@@ -125,34 +183,43 @@ module JSI
|
|
125
183
|
end
|
126
184
|
end
|
127
185
|
|
186
|
+
# @return [String] the instance's object_group_text
|
128
187
|
def object_group_text
|
129
188
|
instance.object_group_text
|
130
189
|
end
|
131
190
|
|
191
|
+
# @return [Object] a jsonifiable representation of the instance
|
132
192
|
def as_json(*opt)
|
133
193
|
Typelike.as_json(instance, *opt)
|
134
194
|
end
|
135
195
|
|
196
|
+
# @return [Object] an opaque fingerprint of this JSI for FingerprintHash
|
136
197
|
def fingerprint
|
137
198
|
{class: self.class, instance: instance}
|
138
199
|
end
|
139
200
|
include FingerprintHash
|
140
201
|
|
141
202
|
private
|
203
|
+
|
204
|
+
# assigns @instance to the given thing, raising if the thing is not appropriate for a JSI instance
|
205
|
+
# @param thing [Object] a JSON schema instance for this class's schema
|
142
206
|
def instance=(thing)
|
143
207
|
if instance_variable_defined?(:@instance)
|
144
208
|
raise(JSI::Bug, "overwriting instance is not supported")
|
145
209
|
end
|
146
210
|
if thing.is_a?(Base)
|
147
211
|
warn "assigning instance to a Base instance is incorrect. received: #{thing.pretty_inspect.chomp}"
|
148
|
-
@instance =
|
212
|
+
@instance = thing.instance
|
149
213
|
elsif thing.is_a?(JSI::JSON::Node)
|
150
|
-
@instance =
|
214
|
+
@instance = thing
|
151
215
|
else
|
152
|
-
@instance = JSI::JSON::Node.
|
216
|
+
@instance = JSI::JSON::Node.new_doc(thing)
|
153
217
|
end
|
154
218
|
end
|
155
219
|
|
220
|
+
# assigns a subscript, taking care of memoization and unwrapping a JSI if given.
|
221
|
+
# @param subscript [Object] the bit between the [ and ]
|
222
|
+
# @param value [JSI::Base, Object] the value to be assigned
|
156
223
|
def subscript_assign(subscript, value)
|
157
224
|
clear_memo(:[], subscript)
|
158
225
|
if value.is_a?(Base)
|
@@ -166,12 +233,19 @@ module JSI
|
|
166
233
|
# this module is just a namespace for schema classes.
|
167
234
|
module SchemaClasses
|
168
235
|
extend Memoize
|
236
|
+
|
237
|
+
# JSI::SchemaClasses[schema_id] returns a class for the schema with the
|
238
|
+
# given id, the same class as returned from JSI.class_for_schema.
|
239
|
+
#
|
240
|
+
# @param schema_id [String] absolute schema id as returned by {JSI::Schema#schema_id}
|
241
|
+
# @return [Class subclassing JSI::Base] the class for that schema
|
169
242
|
def self.[](schema_id)
|
170
243
|
@classes_by_id[schema_id]
|
171
244
|
end
|
172
245
|
@classes_by_id = {}
|
173
246
|
end
|
174
247
|
|
248
|
+
# see {JSI.class_for_schema}
|
175
249
|
def SchemaClasses.class_for_schema(schema_object)
|
176
250
|
if schema_object.is_a?(JSI::Schema)
|
177
251
|
schema__ = schema_object
|
@@ -184,7 +258,7 @@ module JSI
|
|
184
258
|
begin
|
185
259
|
Class.new(Base).instance_exec(schema_) do |schema|
|
186
260
|
begin
|
187
|
-
include(JSI.module_for_schema(schema))
|
261
|
+
include(JSI::SchemaClasses.module_for_schema(schema))
|
188
262
|
|
189
263
|
SchemaClasses.instance_exec(self) { |klass| @classes_by_id[klass.schema_id] = klass }
|
190
264
|
|
@@ -196,7 +270,20 @@ module JSI
|
|
196
270
|
end
|
197
271
|
end
|
198
272
|
|
199
|
-
|
273
|
+
# a module for the given schema, with accessor methods for any object
|
274
|
+
# property names the schema identifies. also has class and instance
|
275
|
+
# methods called #schema to access the {JSI::Schema} this module
|
276
|
+
# represents.
|
277
|
+
#
|
278
|
+
# accessor methods are defined on these modules so that methods can be
|
279
|
+
# defined on {JSI.class_for_schema} classes without method redefinition
|
280
|
+
# warnings. additionally, these overriding instance methods can call
|
281
|
+
# `super` to invoke the normal accessor behavior.
|
282
|
+
#
|
283
|
+
# no property names that are the same as existing method names on the JSI
|
284
|
+
# class will be defined. users should use #[] and #[]= to access properties
|
285
|
+
# whose names conflict with existing methods.
|
286
|
+
def SchemaClasses.module_for_schema(schema_object)
|
200
287
|
if schema_object.is_a?(JSI::Schema)
|
201
288
|
schema__ = schema_object
|
202
289
|
else
|
@@ -219,26 +306,24 @@ module JSI
|
|
219
306
|
%Q(#<Module for Schema: #{schema_id}>)
|
220
307
|
end
|
221
308
|
|
222
|
-
|
223
|
-
|
224
|
-
instance_methods
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
raise(NoMethodError, "instance does not respond to []; cannot call reader `#{property_name}' for: #{pretty_inspect.chomp}")
|
234
|
-
end
|
309
|
+
instance_method_modules = [m, Base, BaseArray, BaseHash]
|
310
|
+
instance_methods = instance_method_modules.map do |mod|
|
311
|
+
mod.instance_methods + mod.private_instance_methods
|
312
|
+
end.inject(Set.new, &:|)
|
313
|
+
accessors_to_define = schema.described_object_property_names.map(&:to_s) - instance_methods.map(&:to_s)
|
314
|
+
accessors_to_define.each do |property_name|
|
315
|
+
define_method(property_name) do
|
316
|
+
if respond_to?(:[])
|
317
|
+
self[property_name]
|
318
|
+
else
|
319
|
+
raise(NoMethodError, "instance does not respond to []; cannot call reader `#{property_name}' for: #{pretty_inspect.chomp}")
|
235
320
|
end
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
321
|
+
end
|
322
|
+
define_method("#{property_name}=") do |value|
|
323
|
+
if respond_to?(:[]=)
|
324
|
+
self[property_name] = value
|
325
|
+
else
|
326
|
+
raise(NoMethodError, "instance does not respond to []=; cannot call writer `#{property_name}=' for: #{pretty_inspect.chomp}")
|
242
327
|
end
|
243
328
|
end
|
244
329
|
end
|
@@ -247,14 +332,22 @@ module JSI
|
|
247
332
|
end
|
248
333
|
end
|
249
334
|
|
335
|
+
# module extending a {JSI::Base} object when its schema instance is Hash-like (responds to #to_hash)
|
250
336
|
module BaseHash
|
251
|
-
#
|
337
|
+
# yields each key and value of this JSI.
|
338
|
+
# each yielded key is the same as a key of the instance, and each yielded
|
339
|
+
# value is the result of self[key] (see #[]).
|
340
|
+
# returns an Enumerator if no block is given.
|
341
|
+
# @yield [Object, Object] each key and value of this JSI hash
|
342
|
+
# @return [self, Enumerator]
|
252
343
|
def each
|
253
344
|
return to_enum(__method__) { instance.size } unless block_given?
|
254
345
|
instance.each_key { |k| yield(k, self[k]) }
|
255
346
|
self
|
256
347
|
end
|
257
348
|
|
349
|
+
# @return [Hash] a hash in which each key is a key of the instance hash and
|
350
|
+
# each value is the result of self[key] (see #[]).
|
258
351
|
def to_hash
|
259
352
|
inject({}) { |h, (k, v)| h[k] = v; h }
|
260
353
|
end
|
@@ -266,6 +359,10 @@ module JSI
|
|
266
359
|
define_method(method_name) { |*a, &b| instance.public_send(method_name, *a, &b) }
|
267
360
|
end
|
268
361
|
|
362
|
+
# @return [JSI::Base, Object] the instance's subscript value at the given
|
363
|
+
# key property_name_. if there is a subschema defined for that property
|
364
|
+
# on this JSI's schema, returns the instance's subscript as a JSI
|
365
|
+
# instiation of that subschema.
|
269
366
|
def [](property_name_)
|
270
367
|
memoize(:[], property_name_) do |property_name|
|
271
368
|
begin
|
@@ -280,18 +377,33 @@ module JSI
|
|
280
377
|
end
|
281
378
|
end
|
282
379
|
end
|
380
|
+
|
381
|
+
# assigns the given property name of the instance to the given value.
|
382
|
+
# if the value is a JSI, its instance is assigned.
|
383
|
+
# @param property_name [Object] this should generally be a String, but JSI
|
384
|
+
# does not enforce any constraint on it.
|
385
|
+
# @param value [Object] the value to be assigned to the given subscript
|
386
|
+
# property_name
|
283
387
|
def []=(property_name, value)
|
284
388
|
subscript_assign(property_name, value)
|
285
389
|
end
|
286
390
|
end
|
287
391
|
|
392
|
+
# module extending a {JSI::Base} object when its schema instance is Array-like (responds to #to_ary)
|
288
393
|
module BaseArray
|
394
|
+
# yields each element. each yielded element is the result of self[index]
|
395
|
+
# for each index of the instance (see #[]).
|
396
|
+
# returns an Enumerator if no block is given.
|
397
|
+
# @yield [Object] each element of this JSI array
|
398
|
+
# @return [self, Enumerator]
|
289
399
|
def each
|
290
400
|
return to_enum(__method__) { instance.size } unless block_given?
|
291
401
|
instance.each_index { |i| yield(self[i]) }
|
292
402
|
self
|
293
403
|
end
|
294
404
|
|
405
|
+
# @return [Array] an array, the same size as the instance, in which the
|
406
|
+
# element at each index is the result of self[index] (see #[])
|
295
407
|
def to_ary
|
296
408
|
to_a
|
297
409
|
end
|
@@ -304,6 +416,10 @@ module JSI
|
|
304
416
|
define_method(method_name) { |*a, &b| instance.public_send(method_name, *a, &b) }
|
305
417
|
end
|
306
418
|
|
419
|
+
# @return [Object] returns the instance's subscript value at the given index
|
420
|
+
# i_. if there is a subschema defined for that index on this JSI's schema,
|
421
|
+
# returns the instance's subscript as a JSI instiation of that subschema.
|
422
|
+
# @param i_ the array index to subscript
|
307
423
|
def [](i_)
|
308
424
|
memoize(:[], i_) do |i|
|
309
425
|
begin
|
@@ -318,6 +434,11 @@ module JSI
|
|
318
434
|
end
|
319
435
|
end
|
320
436
|
end
|
437
|
+
|
438
|
+
# assigns the given index of the instance to the given value.
|
439
|
+
# if the value is a JSI, its instance is assigned.
|
440
|
+
# @param i [Object] the array index to assign
|
441
|
+
# @param value [Object] the value to be assigned to the given subscript i
|
321
442
|
def []=(i, value)
|
322
443
|
subscript_assign(i, value)
|
323
444
|
end
|