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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec49b425e6253b4ff1bf14b1d2218fe1bd5c905194d169faab948820a2e17302
4
- data.tar.gz: 56d79d38177b3272ba094e21def5083896a12c8157346909b739927fbe52e542
3
+ metadata.gz: 61dd1e2dc8c145ccf5acdcd6a0d6eaeef0d61dd85e876347af89f7168e838319
4
+ data.tar.gz: de6f01e5e859f2b3039b7d2c6f734b1e30b97b8c946f2444783581931d5939e9
5
5
  SHA512:
6
- metadata.gz: cd3d72c29b166c0bb7b1a5970742e5538a79fda1a16359fadb35dd3b6ea65911481f40ab8e4f79ec9da56247f385ee6168ad2aeeaec0f4a6735da7370aba8005
7
- data.tar.gz: c476fb40d83d9b98568bc067375ddf2f95953fe886284ab889fed11df51b12bd3c8c079bb6dc7e0dec52fa38175e61eb1d784b83d3b707cc984100670f8a592a
6
+ metadata.gz: c15c232306814b19a3b84f94c9bd5bacfa261c7124e007ad8961e6e7a28495c23e675963aca024d2abb29613ba9092313cf11b0344feb3e20f38dec6dcf511b1
7
+ data.tar.gz: c00160e62da570c862b694d516bb157019b1799dcff24f594f494fa695c45fcfaa4a5e34e93b8bf35978c28e9bf13e84ed86b3938eff3b7443c6e4cae81118af
@@ -0,0 +1 @@
1
+ --main README.md --markup=markdown {lib}/**/*.rb
@@ -1,3 +1,10 @@
1
+ # v0.0.2
2
+
3
+ - JSI::JSON::Node and other utilities duck-type better with #to_hash and #to_ary
4
+ - much improved documentation
5
+ - much improved test coverage
6
+ - code improvements too numerous to list
7
+
1
8
  # v0.0.1
2
9
 
3
10
  - extracted JSI from Scorpio
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 an instance of that schema with JSI:
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: 'bill', phone: [{location: 'home', number: '555'}], nickname: 'big b')
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["594126e3-ea3c-5d9a-b5f4-7423a8701f97#/properties/phone"] fragment="#/phone">
41
- # #{<JSI::SchemaClasses["594126e3-ea3c-5d9a-b5f4-7423a8701f97#/properties/phone/items"] fragment="#/phone/0"> "location" => "home", "number" => "555"}
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: [{number: [5, 5, 5]}])
77
+ bad = Contact.new('phone' => [{'number' => [5, 5, 5]}])
74
78
  # => #{<Contact fragment="#">
75
- # "phone" => #[<JSI::SchemaClasses["594126e3-ea3c-5d9a-b5f4-7423a8701f97#/properties/phone"] fragment="#/phone">
76
- # #{<JSI::SchemaClasses["594126e3-ea3c-5d9a-b5f4-7423a8701f97#/properties/phone/items"] fragment="#/phone/0">
77
- # "number" => #[<JSI::SchemaClasses["594126e3-ea3c-5d9a-b5f4-7423a8701f97#/properties/phone/items/properties/number"] fragment="#/phone/0/number"> 5, 5, 5]
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 594126e3-ea3c-5d9a-b5f4-7423a8701f97"]
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 allow instantiation of such a schema.
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, here's an example:
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.instance['name']
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 class instead of the JSI class for this reason). You can also use [] and []=, of course, with the same effect.
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. JSI converts symbols to strings for its internal hash keys (much like ActiveSupport::HashWithIndifferentAccess). JSI accepts symbols to refer to its string hash keys for instantiation, but does not currently transform symbols to strings everywhere else, e.g. `bill[:name]` is `nil` whereas `bill['name']` is `"bill"`.
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
@@ -2,16 +2,26 @@ require 'json'
2
2
  require 'jsi/typelike_modules'
3
3
 
4
4
  module JSI
5
- # base class for representing an instance of an instance described by a schema
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 = JSI.deep_stringify_symbol_keys(thing.instance)
212
+ @instance = thing.instance
149
213
  elsif thing.is_a?(JSI::JSON::Node)
150
- @instance = JSI.deep_stringify_symbol_keys(thing)
214
+ @instance = thing
151
215
  else
152
- @instance = JSI::JSON::Node.new_by_type(JSI.deep_stringify_symbol_keys(thing), [])
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
- def self.module_for_schema(schema_object)
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
- if schema.describes_hash?
223
- instance_method_modules = [m, Base, BaseArray, BaseHash]
224
- instance_methods = instance_method_modules.map do |mod|
225
- mod.instance_methods + mod.private_instance_methods
226
- end.inject(Set.new, &:|)
227
- accessors_to_define = schema.described_hash_property_names.map(&:to_s) - instance_methods.map(&:to_s)
228
- accessors_to_define.each do |property_name|
229
- define_method(property_name) do
230
- if respond_to?(:[])
231
- self[property_name]
232
- else
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
- define_method("#{property_name}=") do |value|
237
- if respond_to?(:[]=)
238
- self[property_name] = value
239
- else
240
- raise(NoMethodError, "instance does not respond to []=; cannot call writer `#{property_name}=' for: #{pretty_inspect.chomp}")
241
- end
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
- # Hash methods
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