jsi 0.2.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.md +36 -0
  4. data/LICENSE.md +613 -0
  5. data/README.md +153 -52
  6. data/lib/jsi/base.rb +485 -338
  7. data/lib/jsi/jsi_coder.rb +24 -18
  8. data/lib/jsi/metaschema.rb +7 -0
  9. data/lib/jsi/metaschema_node/bootstrap_schema.rb +100 -0
  10. data/lib/jsi/metaschema_node.rb +245 -0
  11. data/lib/jsi/pathed_node.rb +49 -46
  12. data/lib/jsi/ptr.rb +292 -0
  13. data/lib/jsi/schema/application/child_application/contains.rb +16 -0
  14. data/lib/jsi/schema/application/child_application/draft04.rb +22 -0
  15. data/lib/jsi/schema/application/child_application/draft06.rb +29 -0
  16. data/lib/jsi/schema/application/child_application/draft07.rb +29 -0
  17. data/lib/jsi/schema/application/child_application/items.rb +18 -0
  18. data/lib/jsi/schema/application/child_application/properties.rb +25 -0
  19. data/lib/jsi/schema/application/child_application.rb +40 -0
  20. data/lib/jsi/schema/application/draft04.rb +8 -0
  21. data/lib/jsi/schema/application/draft06.rb +8 -0
  22. data/lib/jsi/schema/application/draft07.rb +8 -0
  23. data/lib/jsi/schema/application/inplace_application/dependencies.rb +28 -0
  24. data/lib/jsi/schema/application/inplace_application/draft04.rb +26 -0
  25. data/lib/jsi/schema/application/inplace_application/draft06.rb +27 -0
  26. data/lib/jsi/schema/application/inplace_application/draft07.rb +33 -0
  27. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +20 -0
  28. data/lib/jsi/schema/application/inplace_application/ref.rb +18 -0
  29. data/lib/jsi/schema/application/inplace_application/someof.rb +29 -0
  30. data/lib/jsi/schema/application/inplace_application.rb +46 -0
  31. data/lib/jsi/schema/application.rb +12 -0
  32. data/lib/jsi/schema/draft04.rb +14 -0
  33. data/lib/jsi/schema/draft06.rb +14 -0
  34. data/lib/jsi/schema/draft07.rb +14 -0
  35. data/lib/jsi/schema/issue.rb +36 -0
  36. data/lib/jsi/schema/ref.rb +159 -0
  37. data/lib/jsi/schema/schema_ancestor_node.rb +119 -0
  38. data/lib/jsi/schema/validation/array.rb +69 -0
  39. data/lib/jsi/schema/validation/const.rb +20 -0
  40. data/lib/jsi/schema/validation/contains.rb +25 -0
  41. data/lib/jsi/schema/validation/core.rb +39 -0
  42. data/lib/jsi/schema/validation/dependencies.rb +49 -0
  43. data/lib/jsi/schema/validation/draft04/minmax.rb +91 -0
  44. data/lib/jsi/schema/validation/draft04.rb +112 -0
  45. data/lib/jsi/schema/validation/draft06.rb +122 -0
  46. data/lib/jsi/schema/validation/draft07.rb +159 -0
  47. data/lib/jsi/schema/validation/enum.rb +25 -0
  48. data/lib/jsi/schema/validation/ifthenelse.rb +46 -0
  49. data/lib/jsi/schema/validation/items.rb +54 -0
  50. data/lib/jsi/schema/validation/not.rb +20 -0
  51. data/lib/jsi/schema/validation/numeric.rb +121 -0
  52. data/lib/jsi/schema/validation/object.rb +45 -0
  53. data/lib/jsi/schema/validation/pattern.rb +34 -0
  54. data/lib/jsi/schema/validation/properties.rb +101 -0
  55. data/lib/jsi/schema/validation/property_names.rb +32 -0
  56. data/lib/jsi/schema/validation/ref.rb +40 -0
  57. data/lib/jsi/schema/validation/required.rb +27 -0
  58. data/lib/jsi/schema/validation/someof.rb +90 -0
  59. data/lib/jsi/schema/validation/string.rb +47 -0
  60. data/lib/jsi/schema/validation/type.rb +49 -0
  61. data/lib/jsi/schema/validation.rb +51 -0
  62. data/lib/jsi/schema.rb +528 -233
  63. data/lib/jsi/schema_classes.rb +238 -51
  64. data/lib/jsi/schema_registry.rb +141 -0
  65. data/lib/jsi/schema_set.rb +141 -0
  66. data/lib/jsi/simple_wrap.rb +8 -3
  67. data/lib/jsi/typelike_modules.rb +75 -68
  68. data/lib/jsi/util/attr_struct.rb +106 -0
  69. data/lib/jsi/util.rb +167 -64
  70. data/lib/jsi/validation/error.rb +34 -0
  71. data/lib/jsi/validation/result.rb +210 -0
  72. data/lib/jsi/validation.rb +15 -0
  73. data/lib/jsi/version.rb +3 -1
  74. data/lib/jsi.rb +72 -9
  75. data/lib/schemas/json-schema.org/draft-04/schema.rb +12 -0
  76. data/lib/schemas/json-schema.org/draft-06/schema.rb +12 -0
  77. data/lib/schemas/json-schema.org/draft-07/schema.rb +12 -0
  78. data/readme.rb +138 -0
  79. data/{resources}/schemas/json-schema.org/draft-04/schema.json +149 -0
  80. data/{resources}/schemas/json-schema.org/draft-06/schema.json +154 -0
  81. data/{resources}/schemas/json-schema.org/draft-07/schema.json +168 -0
  82. metadata +80 -107
  83. data/.simplecov +0 -1
  84. data/LICENSE.txt +0 -21
  85. data/Rakefile.rb +0 -9
  86. data/jsi.gemspec +0 -31
  87. data/lib/jsi/base/to_rb.rb +0 -126
  88. data/lib/jsi/json/node.rb +0 -243
  89. data/lib/jsi/json/pointer.rb +0 -330
  90. data/lib/jsi/json-schema-fragments.rb +0 -59
  91. data/lib/jsi/json.rb +0 -8
  92. data/test/base_array_test.rb +0 -209
  93. data/test/base_hash_test.rb +0 -204
  94. data/test/base_test.rb +0 -422
  95. data/test/jsi_coder_test.rb +0 -85
  96. data/test/jsi_json_arraynode_test.rb +0 -150
  97. data/test/jsi_json_hashnode_test.rb +0 -132
  98. data/test/jsi_json_node_test.rb +0 -310
  99. data/test/jsi_json_pointer_test.rb +0 -106
  100. data/test/jsi_test.rb +0 -11
  101. data/test/jsi_typelike_as_json_test.rb +0 -53
  102. data/test/schema_test.rb +0 -196
  103. data/test/spreedly_openapi_test.rb +0 -8
  104. data/test/test_helper.rb +0 -63
  105. data/test/util_test.rb +0 -62
data/README.md CHANGED
@@ -1,19 +1,24 @@
1
- # JSI: JSON-Schema Instantiation
1
+ # JSI: JSON Schema Instantiation
2
2
 
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 classes let you use JSON with all the niceties of OOP such as property accessors and application-defined instance methods.
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
- To learn more about JSON Schema see [https://json-schema.org/]().
8
+ To learn more about JSON Schema see [https://json-schema.org/](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 which instantiate the JSON Schema. The instance is usually a Hash or an Array but may be basic types, or in fact any object. A JSI class adds 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.
10
+ JSI marries object-oriented programming with JSON Schemas by associating a module with each schema, and extending every instance described by a schema with that module. When an application adds methods to a schema module, those methods can be used on its instances.
11
+
12
+ A JSI instance aims to offer a fairly unobtrusive wrapper around its JSON data, which is usually a Hash (JSON Object) or Array described by one or more JSON Schemas. JSI instances have accessors for property names described by schemas, 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`.
13
+
14
+ Note: The canonical location of this README is on [RubyDoc](http://rubydoc.info/gems/jsi/). When viewed on [Github](https://github.com/notEthan/jsi/), it may be inconsistent with the latest released gem, and Yardoc links will not work.
11
15
 
12
16
  ## Example
13
17
 
14
18
  Words are boring, let's code. Here's a schema in yaml:
15
19
 
16
20
  ```yaml
21
+ $schema: "http://json-schema.org/draft-07/schema"
17
22
  description: "A Contact"
18
23
  type: "object"
19
24
  properties:
@@ -21,37 +26,54 @@ properties:
21
26
  phone:
22
27
  type: "array"
23
28
  items:
29
+ description: "A phone number"
24
30
  type: "object"
25
31
  properties:
26
32
  location: {type: "string"}
27
33
  number: {type: "string"}
28
34
  ```
29
35
 
30
- And here's the class for that schema from JSI:
36
+ We pass that to {JSI.new_schema} which will instantiate a JSI Schema which represents it:
31
37
 
32
38
  ```ruby
33
- Contact = JSI.class_for_schema(YAML.load_file('contact.schema.yml'))
34
- # you can copy/paste this line instead, to follow along in irb:
35
- 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"}}}}}})
39
+ # this would usually load YAML or JSON; the schema object is inlined for copypastability.
40
+ contact_schema = JSI.new_schema({"$schema" => "http://json-schema.org/draft-07/schema", "description" => "A Contact", "type" => "object", "properties" => {"name" => {"type" => "string"}, "phone" => {"type" => "array", "items" => {"type" => "object", "properties" => {"location" => {"type" => "string"}, "number" => {"type" => "string"}}}}}})
36
41
  ```
37
42
 
38
- This definition gives you not just the Contact class, but classes for the whole nested structure. So, if we construct an instance like:
43
+ 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
44
 
40
45
  ```ruby
41
- bill = Contact.new('name' => 'bill', 'phone' => [{'location' => 'home', 'number' => '555'}], 'nickname' => 'big b')
46
+ Contact = contact_schema.jsi_schema_module
47
+ ```
42
48
 
43
- # => #{<Contact Hash>
49
+ To instantiate the schema, we need some JSON data (expressed here as YAML)
50
+
51
+ ```yaml
52
+ name: bill
53
+ phone:
54
+ - location: home
55
+ number: "555"
56
+ nickname: big b
57
+ ```
58
+
59
+ So, if we construct an instance like:
60
+
61
+ ```ruby
62
+ # this would usually load YAML or JSON; the schema instance is inlined for copypastability.
63
+ bill = Contact.new_jsi({"name" => "bill", "phone" => [{"location" => "home", "number" => "555"}], "nickname" => "big b"})
64
+ # => #{<JSI (Contact)>
44
65
  # "name" => "bill",
45
- # "phone" => #[<JSI::SchemaClasses["23d8#/properties/phone"] Array>
46
- # #{<JSI::SchemaClasses["23d8#/properties/phone/items"] Hash> "location" => "home", "number" => "555"}
66
+ # "phone" => #[<JSI (Contact.properties["phone"])>
67
+ # #{<JSI (Contact.properties["phone"].items)>
68
+ # "location" => "home",
69
+ # "number" => "555"
70
+ # }
47
71
  # ],
48
72
  # "nickname" => "big b"
49
73
  # }
50
74
  ```
51
75
 
52
- 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.
53
-
54
- The nested classes can be seen in the #inspect output as `JSI::SchemaClasses[schema_id]` where schema_id is a generated value.
76
+ Note that the hash keys are strings. JSI, being designed with JSON in mind, is geared toward string keys.
55
77
 
56
78
  We get accessors for the Contact:
57
79
 
@@ -60,7 +82,7 @@ bill.name
60
82
  # => "bill"
61
83
  ```
62
84
 
63
- but also nested accessors - #phone is an instance of its array-type schema, and each phone item is an instance of another object-type schema with location and number accessors:
85
+ but also nested accessors - `#phone` is an instance of its array-type schema, and each phone item is an instance of another object-type schema with `#location` and `#number` accessors:
64
86
 
65
87
  ```ruby
66
88
  bill.phone.map(&:location)
@@ -70,32 +92,43 @@ bill.phone.map(&:location)
70
92
  We also get validations, as you'd expect given that's largely what json-schema exists to do:
71
93
 
72
94
  ```ruby
73
- bill.validate
95
+ bill.jsi_valid?
74
96
  # => true
75
97
  ```
76
98
 
77
- ... and validations on the nested schema instances (#phone here), showing in this example validation failure:
99
+ ... and validations on the nested schema instances (`#phone` here), showing in this example validation failure on /phone/0/number:
78
100
 
79
101
  ```ruby
80
- bad = Contact.new('phone' => [{'number' => [5, 5, 5]}])
81
- # => #{<Contact Hash>
82
- # "phone" => #[<JSI::SchemaClasses["23d8#/properties/phone"] Array>
83
- # #{<JSI::SchemaClasses["23d8#/properties/phone/items"] Hash>
84
- # "number" => #[<JSI::SchemaClasses["23d8#/properties/phone/items/properties/number"] Array> 5, 5, 5]
102
+ bad = Contact.new_jsi({'phone' => [{'number' => [5, 5, 5]}]})
103
+ # => #{<JSI (Contact)>
104
+ # "phone" => #[<JSI (Contact.properties["phone"])>
105
+ # #{<JSI (Contact.properties["phone"].items)>
106
+ # "number" => #[<JSI (Contact.properties["phone"].items.properties["number"])> 5, 5, 5]
85
107
  # }
86
108
  # ]
87
109
  # }
88
- bad.phone.fully_validate
89
- # => ["The property '#/0/number' of type array did not match the following type: string in schema 23d8"]
110
+ bad.phone.jsi_validate
111
+ # => #<JSI::Validation::FullResult
112
+ # @validation_errors=
113
+ # #<Set: {#<JSI::Validation::Error
114
+ # message: "instance type does not match `type` value",
115
+ # keyword: "type",
116
+ # schema: #{<JSI (JSI::JSONSchemaOrgDraft07) Schema> "type" => "string"},
117
+ # instance_ptr: JSI::Ptr["phone", 0, "number"],
118
+ # instance_document: {"phone"=>[{"number"=>[5, 5, 5]}]}
119
+ # >,
120
+ # ...
121
+ # >
90
122
  ```
91
123
 
92
- 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.
93
-
94
- Since the underlying instance is a ruby hash (json object), we can use it like a hash with #[] or, say, #transform_values:
124
+ Since the underlying instance is a ruby hash (json object), we can use it like a hash with `#[]` or, say, `#transform_values`:
95
125
 
96
126
  ```ruby
127
+ # note that #size here is actually referring to multiple different methods;
128
+ # for name and nickname it is String#size but for phone it is Array#size.
97
129
  bill.transform_values(&:size)
98
130
  # => {"name" => 4, "phone" => 1, "nickname" => 5}
131
+
99
132
  bill['nickname']
100
133
  # => "big b"
101
134
  ```
@@ -104,21 +137,33 @@ There's plenty more JSI has to offer, but this should give you a pretty good ide
104
137
 
105
138
  ## Terminology and Concepts
106
139
 
107
- - `JSI::Base` is the base class for each JSI class representing a JSON Schema.
108
- - a "JSI class" is a subclass of `JSI::Base` representing a JSON schema.
140
+ - `JSI::Base` is the base class for each JSI schema class representing instances of JSON Schemas.
141
+ - a "JSI Schema" is a JSON Schema, instantiated as (usually) a JSI::Base described by a metaschema (see the sections on Metaschemas below). a JSI Schema is an instance of the module `JSI::Schema`.
142
+ - a "JSI Schema Module" is a module which represents one schema, dynamically created by that Schema. Instances of that schema are extended with its JSI schema module. applications may reopen these modules to add functionality to JSI instances described by a given schema.
143
+ - a "JSI schema class" is a subclass of `JSI::Base` representing one or more JSON schemas. Instances of such a class are described by all of the represented schemas. A JSI schema class includes the JSI schema module of each represented schema.
109
144
  - "instance" is a term that is significantly overloaded in this space, so documentation will attempt to be clear what kind of instance is meant:
110
145
  - a schema instance refers broadly to a data structure that is described by a JSON schema.
111
- - 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.
112
- - a schema refers to a JSON schema. a `JSI::Schema` represents such a schema. a JSI class allows instantiation of a schema as a JSI instance.
146
+ - a JSI instance (or just "a JSI") is a ruby object instantiating a JSI schema class (subclass of `JSI::Base`). This wraps the content of the schema instance (see `JSI::Base#jsi_instance`), and ties it to the schemas which describe the instance (`JSI::Base#jsi_schemas`).
147
+ - "schema" refers to either a parsed JSON schema (generally a ruby Hash) or a JSI schema.
148
+
149
+ ## Supported specification versions
150
+
151
+ JSI supports these JSON Schema specification versions:
152
+
153
+ | Version | `$schema` URI | JSI Schema Module |
154
+ | --- | --- | --- |
155
+ | Draft 4 | `http://json-schema.org/draft-04/schema#` | {JSI::JSONSchemaOrgDraft04} |
156
+ | Draft 6 | `http://json-schema.org/draft-06/schema#` | {JSI::JSONSchemaOrgDraft06} |
157
+ | Draft 7 | `http://json-schema.org/draft-07/schema#` | {JSI::JSONSchemaOrgDraft07} |
113
158
 
114
- ## JSI classes
159
+ ## JSI and Object Oriented Programming
115
160
 
116
- 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:
161
+ 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:
117
162
 
118
163
  ```ruby
119
- class Contact
120
- def full_address
121
- address.values.join(", ")
164
+ module Contact
165
+ def phone_numbers
166
+ phone.map(&:number)
122
167
  end
123
168
  def name
124
169
  super + ' esq.'
@@ -134,15 +179,25 @@ bill.name = 'rob esq.'
134
179
  # => "rob esq."
135
180
  bill['name']
136
181
  # => "rob"
182
+ bill.phone_numbers
183
+ # => ["555"]
137
184
  ```
138
185
 
139
- 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.
186
+ 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.
187
+
188
+ Working with subschemas is just about as easy as with root schemas.
189
+
190
+ You can subscript or use property accessors on a JSI schema module to refer to the schema modules of its subschemas, e.g.:
191
+
192
+ ```ruby
193
+ Contact.properties['phone'].items
194
+ # => Contact.properties["phone"].items (JSI Schema Module)
195
+ ```
140
196
 
141
- 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:
197
+ Opening a subschema module with [`module_exec`](https://ruby-doc.org/core/Module.html#method-i-module_exec), you can add methods to instances of the subschema.
142
198
 
143
199
  ```ruby
144
- phone_schema = Contact.schema['properties']['phone']['items']
145
- JSI.class_for_schema(phone_schema).class_eval do
200
+ Contact.properties['phone'].items.module_exec do
146
201
  def number_with_dashes
147
202
  number.split(//).join('-')
148
203
  end
@@ -151,28 +206,70 @@ bill.phone.first.number_with_dashes
151
206
  # => "5-5-5"
152
207
  ```
153
208
 
154
- If you want to name the class, this works:
209
+ A recommended convention for naming subschemas is to define them in the namespace of the module of their
210
+ parent schema. The module can then be opened to add methods to the subschema's module.
155
211
 
156
212
  ```ruby
157
- Phone = JSI.class_for_schema(Contact.schema['properties']['phone']['items'])
158
- class Phone
213
+ module Contact
214
+ Phone = properties['phone'].items
215
+ module Phone
216
+ def number_with_dashes
217
+ number.split(//).join('-')
218
+ end
219
+ end
220
+ end
221
+ ```
222
+
223
+ However, that is only a convention, and a flat namespace works fine too.
224
+
225
+ ```ruby
226
+ ContactPhone = Contact.properties['phone'].items
227
+ module ContactPhone
159
228
  def number_with_dashes
160
229
  number.split(//).join('-')
161
230
  end
162
231
  end
163
232
  ```
164
233
 
165
- Either syntax is slightly cumbersome and a better syntax is in the works.
234
+ ### A note on Classes
235
+
236
+ The classes used to instantiate JSIs are dynamically generated subclasses of JSI::Base which include the JSI Schema Module of each schema describing the given instance. These are mostly intended to be ignored: applications aren't expected to instantiate these directly (rather, `#new_jsi` on a Schema or Schema Module is intended), and they are not intended for subclassing or method definition (applications should instead define methods on a schema's {JSI::Schema#jsi_schema_module}).
237
+
238
+ ## Registration
239
+
240
+ In order for references across documents (generally from a `$ref` schema keyword) to resolve, JSI provides a registry which associates URIs with schemas (or resources containing schemas). This registry is accessible on {JSI.schema_registry} and is a {JSI::SchemaRegistry}.
241
+
242
+ Schemas instantiated with `.new_schema`, and their subschemas, are automatically registered with `JSI.schema_registry` if they identify an absolute URI.
243
+
244
+ Schemas can automatically be lazily loaded by registering a block which instantiates them with {JSI::SchemaRegistry#autoload_uri} (see its documentation).
245
+
246
+ ## Validation
247
+
248
+ JSI implements all required features, and many optional features, for validation according to supported JSON Schema specifications. To validate instances, see methods {JSI::Base#jsi_validate}, {JSI::Base#jsi_valid?}, {JSI::Schema#instance_validate}, {JSI::Schema#instance_valid?}.
249
+
250
+ The following optional features are not completely supported:
251
+
252
+ - The `format` keyword does not perform any validation.
253
+ - Regular expressions are interpreted by Ruby's Regexp class, whereas JSON Schema recommends interpreting these as ECMA 262 regular expressions. Certain expressions behave differently, particularly `^` and `$`.
254
+ - Keywords `contentMediaType` and `contentEncoding` do not perform validation.
255
+
256
+ ## Metaschemas
257
+
258
+ A metaschema is a schema which describes schemas. Likewise, a schema is an instance of a metaschema.
259
+
260
+ In JSI, a schema is generally a JSI::Base instance whose schemas include a metaschema.
261
+
262
+ A self-descriptive metaschema - most commonly one of the JSON schema draft metaschemas - is an object whose schemas include itself. This is instantiated in JSI as a JSI::MetaschemaNode, a special subclass of JSI::Base.
166
263
 
167
264
  ## ActiveRecord serialization
168
265
 
169
266
  A really excellent place to use JSI is when dealing with serialized columns in ActiveRecord.
170
267
 
171
- 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.
268
+ 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.
172
269
 
173
- 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.
270
+ 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.
174
271
 
175
- 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 class. Here's an example:
272
+ 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, supposing a `users` table with a JSON column `contact_info`:
176
273
 
177
274
  ```ruby
178
275
  class User < ActiveRecord::Base
@@ -180,13 +277,13 @@ class User < ActiveRecord::Base
180
277
  end
181
278
  ```
182
279
 
183
- 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.
280
+ Now `user.contact_info` will be instantiated as a Contact JSI instance, from the JSON type in the database, with Contact's accessors, validations, and user-defined instance methods.
184
281
 
185
282
  See the gem [`arms`](https://github.com/notEthan/arms) if you wish to serialize the dumped JSON-compatible objects further as text.
186
283
 
187
284
  ## Keying Hashes (JSON Objects)
188
285
 
189
- 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.
286
+ 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. You may also use [ActiveSupport::HashWithIndifferentAccess](https://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html) as the instance of a JSI in order to gain the benefits that offers over a plain hash. Note that activesupport is not a dependency of jsi and would be required separately for this.
190
287
 
191
288
  ## Contributing
192
289
 
@@ -194,4 +291,8 @@ Issues and pull requests are welcome on GitHub at https://github.com/notEthan/js
194
291
 
195
292
  ## License
196
293
 
197
- JSI is open source software available under the terms of the [MIT License](https://opensource.org/licenses/MIT).
294
+ [<img align="right" src="https://github.com/notEthan/jsi/raw/v0.3.0/resources/icons/AGPL-3.0.png">](https://www.gnu.org/licenses/agpl-3.0.html)
295
+
296
+ JSI is licensed under the terms of the [GNU Affero General Public License version 3](https://www.gnu.org/licenses/agpl-3.0.html).
297
+
298
+ 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.