golden_fleece 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +242 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/golden_fleece.gemspec +31 -0
- data/lib/golden_fleece.rb +14 -0
- data/lib/golden_fleece/context.rb +37 -0
- data/lib/golden_fleece/context/export.rb +19 -0
- data/lib/golden_fleece/context/formats.rb +15 -0
- data/lib/golden_fleece/context/getters.rb +23 -0
- data/lib/golden_fleece/context/normalizers.rb +15 -0
- data/lib/golden_fleece/context/schemas.rb +10 -0
- data/lib/golden_fleece/definitions.rb +14 -0
- data/lib/golden_fleece/format.rb +18 -0
- data/lib/golden_fleece/model.rb +25 -0
- data/lib/golden_fleece/model/active_model/normalization.rb +17 -0
- data/lib/golden_fleece/model/active_model/validation.rb +22 -0
- data/lib/golden_fleece/model/context.rb +24 -0
- data/lib/golden_fleece/model/export.rb +13 -0
- data/lib/golden_fleece/model/normalization.rb +23 -0
- data/lib/golden_fleece/normalizer.rb +18 -0
- data/lib/golden_fleece/schema.rb +117 -0
- data/lib/golden_fleece/type.rb +22 -0
- data/lib/golden_fleece/utility.rb +30 -0
- data/lib/golden_fleece/validations/active_model/fleece_schema_conformance_validator.rb +18 -0
- data/lib/golden_fleece/validations/validator_context.rb +74 -0
- data/lib/golden_fleece/value.rb +69 -0
- data/lib/golden_fleece/version.rb +3 -0
- metadata +178 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 69b0eddc5da9185615c28fa06cb491850643d5ce
|
4
|
+
data.tar.gz: a6f855832e94fe8d72d71f9f837dea0004fc370b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 02edc695368a00db6beadb66b7ca9ee9ac06384581bf09119cdef5e1e904d384b418ccb58d649b28c587e2d02c4c4b08cb57ee0ef1c6a5fbc6b93d302f72135a
|
7
|
+
data.tar.gz: 8c67be8b70be4d7b37502fa8b52984708e4c95f8972b2c7801d342d15155d03d200766eed9f83091b53cf430ad790fcf1fe81e8e5a00524dcc2b095c2b5b89f6
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at ersin.akinci@instacart.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Ersin Akinci
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
# Golden Fleece 🐑
|
2
|
+
|
3
|
+
Easy schemas for JSON columns in your Ruby data models. Currently supports ActiveRecord/ActiveModel >= 3.0 (i.e., Rails 3, 4 and 5).
|
4
|
+
|
5
|
+
Golden Fleece lets you define a schema for your Ruby data models, which can be used to do fun things:
|
6
|
+
|
7
|
+
- Validate JSON data types and formats
|
8
|
+
- Normalize JSON data
|
9
|
+
- Provide default values within nested JSON
|
10
|
+
|
11
|
+
It's like [JSON Schema](http://json-schema.org/) but more opinionated and, in our opinion, more straightforward to use.
|
12
|
+
|
13
|
+
🍊 Battle-tested at Instacart.
|
14
|
+
|
15
|
+
## Quick start
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'golden_fleece'
|
21
|
+
```
|
22
|
+
|
23
|
+
Then `include GoldenFleece::Model` and define schemas in your data models:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class Person < ActiveRecord::Base
|
27
|
+
include GoldenFleece::Model
|
28
|
+
|
29
|
+
fleece do
|
30
|
+
define_schemas :profile, {
|
31
|
+
first_name: { type: :string },
|
32
|
+
last_name: { types: [:string, :null] },
|
33
|
+
zip_code: { type: :string, default: '90210' }
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
person.profile['first_name'] = 'Jane'
|
39
|
+
person.profile['last_name'] = nil
|
40
|
+
person.valid? # true
|
41
|
+
person.export_fleece # { profile: { first_name: 'Jane', last_name: nil, zip_code: '90210' } }
|
42
|
+
|
43
|
+
person.profile.delete 'first_name'
|
44
|
+
person.valid? # false
|
45
|
+
```
|
46
|
+
|
47
|
+
## Usage
|
48
|
+
|
49
|
+
### Schemas
|
50
|
+
|
51
|
+
Golden Fleece's core concept is the schema. A schema is a structure that defines what your JSON columns should look like and are defined within the `fleece` block on a model using `define_schemas`:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class Person < ActiveRecord::Base
|
55
|
+
include GoldenFleece::Model
|
56
|
+
|
57
|
+
fleece do
|
58
|
+
define_schemas :profile, {
|
59
|
+
first_name: { type: :string },
|
60
|
+
last_name: { types: [:string, :null] },
|
61
|
+
zip_code: { type: :string, default: '90210' }
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
The above example defines a schema on the `Person` model's `profile` column and introduces certain restraints on the `first_name`, `last_name` and `zip_code` fields within the `profile` column's JSON object. Note that Golden Fleece assumes that all columns with a schema are valid JSON objects.
|
68
|
+
|
69
|
+
Note that any keys added to a JSON object that aren't listed in the schema are invalid:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
define_schemas :profile, {
|
73
|
+
first_name: ...,
|
74
|
+
last_name: ...,
|
75
|
+
zip_code: ...,
|
76
|
+
}
|
77
|
+
|
78
|
+
person.profile['address'] = '123 Nottingham Way'
|
79
|
+
person.valid? # false
|
80
|
+
```
|
81
|
+
|
82
|
+
### Types
|
83
|
+
|
84
|
+
Type checks are introduced with the `type` or `types` option (both are interchangeable):
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
define_schemas :profile, {
|
88
|
+
zip_code: { type: :string }
|
89
|
+
}
|
90
|
+
|
91
|
+
person.profile['zip_code'] = 90210
|
92
|
+
person.valid? # false
|
93
|
+
```
|
94
|
+
|
95
|
+
Note that passing `:null` to `type`/`types` allows the field to be nullable.
|
96
|
+
|
97
|
+
### Defaults
|
98
|
+
|
99
|
+
Defaults defined on schemas will fill in `nil` values in your JSON columns when validating and exporting. Defaults are safe and will _never_ backfill your model's columns:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
define_schemas :profile, {
|
103
|
+
zip_code: { type: :string, default: '90210' }
|
104
|
+
}
|
105
|
+
|
106
|
+
person.profile['zip_code'] = nil
|
107
|
+
person.valid? # true
|
108
|
+
person.export_fleece # { profile: { zip_code: '90210' } }
|
109
|
+
person.profile['zip_code'] # nil
|
110
|
+
```
|
111
|
+
|
112
|
+
In addition to static values, you can use Proc's to dynamically generate defaults at runtime:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
define_schemas :profile, {
|
116
|
+
zip_code: { type: :string, default: -> record { record.closest_location.zip_code } }
|
117
|
+
}
|
118
|
+
|
119
|
+
person.export_fleece # { profile: { zip_code: '94131' } }
|
120
|
+
```
|
121
|
+
|
122
|
+
### Getters
|
123
|
+
|
124
|
+
Top-level keys in your JSON columns can automatically be mapped as getters on your data model's instances using `define_getters`. Getters are safe and will _never_ override any preexisting instance methods:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
define_schemas :profile, {
|
128
|
+
zip_code: ...,
|
129
|
+
class: ...
|
130
|
+
}
|
131
|
+
define_getters :profile
|
132
|
+
|
133
|
+
person.zip_code # '90210'
|
134
|
+
person.profile['zip_code'] # nil
|
135
|
+
|
136
|
+
person.profile['class'] = 'Freshman'
|
137
|
+
person.class # Person
|
138
|
+
```
|
139
|
+
|
140
|
+
Note that getters will return the exported value of your JSON key rather than the raw value.
|
141
|
+
|
142
|
+
### Normalizers
|
143
|
+
|
144
|
+
Normalizers are Procs that normalize your data before validating, exporting or saving:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
define_normalizers({
|
148
|
+
cast_string: -> record, value { value.to_s }
|
149
|
+
})
|
150
|
+
|
151
|
+
define_schemas :profile, {
|
152
|
+
zip_code: { type: :string, normalizer: :cast_string }
|
153
|
+
}
|
154
|
+
|
155
|
+
person.profile['zip_code'] = 90210
|
156
|
+
person.profile['zip_code'] # 90210
|
157
|
+
person.zip_code # '90210'
|
158
|
+
person.valid? # true
|
159
|
+
|
160
|
+
person.save
|
161
|
+
person.profile['zip_code'] # '90210'
|
162
|
+
person.zip_code # '90210'
|
163
|
+
```
|
164
|
+
|
165
|
+
Note that multiple normalizers can be chained with `normalizers`:
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
define_normalizers({
|
169
|
+
cast_string: ...,
|
170
|
+
sha1: -> record, value { sha1(value) }
|
171
|
+
})
|
172
|
+
|
173
|
+
define_schemas :profile, {
|
174
|
+
zip_code: { type: :string, normalizers: [:cast_string, :sha1] }
|
175
|
+
}
|
176
|
+
|
177
|
+
person.profile['zip_code'] = 90210
|
178
|
+
person.zip_code # '2b02dbc1030b278245b2b9cb11667eebf7275a52'
|
179
|
+
```
|
180
|
+
|
181
|
+
### Formats
|
182
|
+
|
183
|
+
Formats are Procs that can be used to enforce complex validations:
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
define_formats({
|
187
|
+
zip_code: -> record, value { raise ArgumentError.new("must be a valid ZIP code") unless value =~ /^[0-9]{5}(?:-[0-9]{4})?$/ }
|
188
|
+
})
|
189
|
+
|
190
|
+
define_schemas :profile, {
|
191
|
+
zip_code: { type: :string, format: :zip_code }
|
192
|
+
}
|
193
|
+
|
194
|
+
person.profile['zip_code'] = '90210' # person.valid? == true
|
195
|
+
person.profile['zip_code'] = '90210-1234' # person.valid? == true
|
196
|
+
person.profile['zip_code'] = '90210-12' # person.valid? == false
|
197
|
+
person.errors.messages # "Invalid format at '/zip_code' on column 'profile': must be a valid ZIP code"
|
198
|
+
```
|
199
|
+
|
200
|
+
Note that unlike types and normalizers, you can only use one format at a time for each schema.
|
201
|
+
|
202
|
+
### Nested JSON
|
203
|
+
|
204
|
+
Schemas can be nested with `subschemas`:
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
define_schemas :profile, {
|
208
|
+
address: { type: :object, subschemas: {
|
209
|
+
number: { type: :number },
|
210
|
+
street: { type: :string },
|
211
|
+
zip_code: { type: :string, default: '90210' }
|
212
|
+
}
|
213
|
+
}
|
214
|
+
}
|
215
|
+
|
216
|
+
person.profile['address'] # nil
|
217
|
+
person.address # { number: nil, street: nil, zip_code: '90210' }
|
218
|
+
person.valid? # false
|
219
|
+
```
|
220
|
+
|
221
|
+
### Exporting
|
222
|
+
|
223
|
+
TODO
|
224
|
+
|
225
|
+
## Development
|
226
|
+
|
227
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
228
|
+
|
229
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
230
|
+
|
231
|
+
### Using Golden Fleece with other ORM's
|
232
|
+
|
233
|
+
TODO
|
234
|
+
|
235
|
+
## Contributing
|
236
|
+
|
237
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/earksiinni/golden_fleece. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
238
|
+
|
239
|
+
|
240
|
+
## License
|
241
|
+
|
242
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "golden_fleece"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'golden_fleece/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "golden_fleece"
|
8
|
+
spec.version = GoldenFleece::VERSION
|
9
|
+
spec.authors = ["Ersin Akinci"]
|
10
|
+
spec.email = ["ersin.akinci@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Easy schemas for your JSON columns.}
|
13
|
+
spec.description = %q{Golden Fleece lets you validate, normalize, set up defaults for and provide getters for JSON data in your Ruby data models through easy to use schemas. More opinionated and easier to use than JSON Schema.}
|
14
|
+
spec.homepage = "https://github.com/earksiinni/golden_fleece"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.14"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
27
|
+
spec.add_development_dependency "activemodel", "~> 3.0"
|
28
|
+
spec.add_development_dependency "activesupport", "~> 3.0"
|
29
|
+
spec.add_development_dependency "pry", "~> 0.9"
|
30
|
+
spec.add_dependency "hana", "~> 1.3"
|
31
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "golden_fleece/context"
|
2
|
+
require "golden_fleece/definitions"
|
3
|
+
require "golden_fleece/format"
|
4
|
+
require "golden_fleece/model"
|
5
|
+
require "golden_fleece/normalizer"
|
6
|
+
require "golden_fleece/schema"
|
7
|
+
require "golden_fleece/type"
|
8
|
+
require "golden_fleece/utility"
|
9
|
+
require "golden_fleece/value"
|
10
|
+
require "golden_fleece/version"
|
11
|
+
|
12
|
+
module GoldenFleece
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "golden_fleece/context/export"
|
2
|
+
require "golden_fleece/context/formats"
|
3
|
+
require "golden_fleece/context/getters"
|
4
|
+
require "golden_fleece/context/normalizers"
|
5
|
+
require "golden_fleece/context/schemas"
|
6
|
+
require 'golden_fleece/schema'
|
7
|
+
|
8
|
+
module GoldenFleece
|
9
|
+
class Context
|
10
|
+
include ::GoldenFleece::Context::Export
|
11
|
+
include ::GoldenFleece::Context::Formats
|
12
|
+
include ::GoldenFleece::Context::Getters
|
13
|
+
include ::GoldenFleece::Context::Normalizers
|
14
|
+
include ::GoldenFleece::Context::Schemas
|
15
|
+
|
16
|
+
attr_accessor :rules
|
17
|
+
attr_reader :model_class, :normalizers, :formats, :attributes, :schemas, :setup_callbacks, :has_run_setup
|
18
|
+
|
19
|
+
def initialize(model_class)
|
20
|
+
@model_class = model_class
|
21
|
+
@normalizers = {}
|
22
|
+
@formats = {}
|
23
|
+
@attributes = []
|
24
|
+
@schemas = Schema.new(self, '/', {})
|
25
|
+
@setup_callbacks = []
|
26
|
+
@has_run_setup = false
|
27
|
+
end
|
28
|
+
|
29
|
+
def run_setup_callbacks
|
30
|
+
@setup_callbacks.each do |cb|
|
31
|
+
cb.call self
|
32
|
+
end
|
33
|
+
|
34
|
+
@has_run_setup = true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module GoldenFleece
|
2
|
+
class Context
|
3
|
+
module Export
|
4
|
+
def export(record, export_attributes)
|
5
|
+
export_attributes = Array.wrap export_attributes
|
6
|
+
|
7
|
+
schemas.reduce({}) { |memo, (attribute, schema)|
|
8
|
+
if export_attributes.include? attribute
|
9
|
+
memo[attribute] = schema.reduce({}) { |memo, (schema_name, schema)|
|
10
|
+
memo[schema_name] = schema.value.compute(record)
|
11
|
+
memo
|
12
|
+
}
|
13
|
+
end
|
14
|
+
memo
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module GoldenFleece
|
2
|
+
class Context
|
3
|
+
module Getters
|
4
|
+
def define_getters(*attributes)
|
5
|
+
# For each attribute...
|
6
|
+
attributes.each do |attribute|
|
7
|
+
# ...and each top-level schema of each attribute...
|
8
|
+
schemas[attribute.to_sym].each do |schema_name, schema|
|
9
|
+
# ...if there isn't already an instance method named after the schema...
|
10
|
+
if !model_class.new.respond_to?(schema_name)
|
11
|
+
# ...define a getter for that schema's value!
|
12
|
+
model_class.class_eval do
|
13
|
+
define_method schema_name do
|
14
|
+
self.class.fleece_context.schemas[attribute.to_sym][schema_name].value.compute(self)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'golden_fleece/normalizer'
|
2
|
+
|
3
|
+
module GoldenFleece
|
4
|
+
class Context
|
5
|
+
module Normalizers
|
6
|
+
def define_normalizers(lambdas = {})
|
7
|
+
lambdas.each do |name, fn|
|
8
|
+
name = name.to_sym
|
9
|
+
|
10
|
+
normalizers[name] = Normalizer.new(name, fn)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'golden_fleece/type'
|
2
|
+
|
3
|
+
module GoldenFleece
|
4
|
+
module Definitions
|
5
|
+
TYPES = {
|
6
|
+
array: Type.new(:array, Array),
|
7
|
+
boolean: Type.new(:boolean, FalseClass, TrueClass),
|
8
|
+
null: Type.new(:null, NilClass),
|
9
|
+
number: Type.new(:number, Numeric),
|
10
|
+
object: Type.new(:object, Hash),
|
11
|
+
string: Type.new(:string, String)
|
12
|
+
}.freeze
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "golden_fleece/model/context"
|
2
|
+
require "golden_fleece/model/export"
|
3
|
+
require "golden_fleece/model/normalization"
|
4
|
+
require "golden_fleece/model/active_model/normalization"
|
5
|
+
require "golden_fleece/model/active_model/validation"
|
6
|
+
|
7
|
+
module GoldenFleece
|
8
|
+
module Model
|
9
|
+
def self.included(base)
|
10
|
+
# Include ORM-specific modules depending on what ORM we're using
|
11
|
+
orm = if defined? ::ActiveModel
|
12
|
+
"ActiveModel"
|
13
|
+
end
|
14
|
+
orm_module = "GoldenFleece::Model::#{orm}".constantize
|
15
|
+
|
16
|
+
base.class_eval do
|
17
|
+
include GoldenFleece::Model::Context
|
18
|
+
include GoldenFleece::Model::Export
|
19
|
+
include GoldenFleece::Model::Normalization
|
20
|
+
include orm_module::Normalization
|
21
|
+
include orm_module::Validation
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'golden_fleece/validations/active_model/fleece_schema_conformance_validator'
|
3
|
+
|
4
|
+
module GoldenFleece
|
5
|
+
module Model
|
6
|
+
module ActiveModel
|
7
|
+
module Validation
|
8
|
+
def self.included(base)
|
9
|
+
base.class_eval do
|
10
|
+
validate_attributes = -> fleece_context {
|
11
|
+
fleece_context.model_class.class_eval do
|
12
|
+
validates *fleece_context.attributes, 'GoldenFleece::Validations::ActiveModel::FleeceSchemaConformance' => true
|
13
|
+
end
|
14
|
+
}
|
15
|
+
|
16
|
+
fleece_context.setup_callbacks << validate_attributes
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'golden_fleece/context'
|
2
|
+
|
3
|
+
module GoldenFleece
|
4
|
+
module Model
|
5
|
+
module Context
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
|
9
|
+
base.instance_eval do
|
10
|
+
@fleece_context = GoldenFleece::Context.new(self)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
attr_reader :fleece_context
|
16
|
+
|
17
|
+
def fleece(&block)
|
18
|
+
fleece_context.instance_eval(&block)
|
19
|
+
fleece_context.run_setup_callbacks unless fleece_context.has_run_setup
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module GoldenFleece
|
2
|
+
module Model
|
3
|
+
module Normalization
|
4
|
+
include Utility
|
5
|
+
|
6
|
+
def normalize_fleece
|
7
|
+
self.class.fleece_context.schemas.each do |attribute, schema|
|
8
|
+
persisted_json = read_attribute attribute
|
9
|
+
|
10
|
+
schema.each do |schema_name, schema|
|
11
|
+
schema_name = schema_name.to_s
|
12
|
+
computed_json = { schema_name => schema.value.compute(self) }
|
13
|
+
deep_stringify_keys computed_json if computed_json.is_a? Hash
|
14
|
+
|
15
|
+
if !persisted_json[schema_name].nil? && persisted_json[schema_name] != computed_json[schema_name]
|
16
|
+
write_attribute attribute, computed_json
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'golden_fleece/definitions'
|
2
|
+
require 'golden_fleece/schema'
|
3
|
+
require 'golden_fleece/utility'
|
4
|
+
|
5
|
+
module GoldenFleece
|
6
|
+
class Schema
|
7
|
+
include Utility
|
8
|
+
|
9
|
+
attr_reader :attribute, :name, :path, :json_path, :types, :normalizers, :format, :value, :default
|
10
|
+
|
11
|
+
def initialize(context, path, definitions)
|
12
|
+
@context = context
|
13
|
+
@path = path
|
14
|
+
@name = path.split("/").last
|
15
|
+
@attribute = path.split("/")[1]
|
16
|
+
@json_path = path.split("/")[2..-1]
|
17
|
+
@json_path = @json_path.join("/") if @json_path
|
18
|
+
@subschemas = {}
|
19
|
+
|
20
|
+
# .count == 1 means we're at the root
|
21
|
+
# .count == 2 means we're at the attribute
|
22
|
+
# .count >= 3 means we're cookin'
|
23
|
+
if path.split("/").count <= 2
|
24
|
+
@types = [Definitions::TYPES[:object]]
|
25
|
+
map_subschemas(definitions)
|
26
|
+
else
|
27
|
+
map_value
|
28
|
+
map_types(definitions[:type], definitions[:types])
|
29
|
+
map_normalizers(definitions[:normalizer], definitions[:normalizers])
|
30
|
+
map_format(definitions[:format])
|
31
|
+
map_default(definitions[:default])
|
32
|
+
map_subschemas(definitions[:subschemas])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def [](subschema_name)
|
37
|
+
subschemas[subschema_name]
|
38
|
+
end
|
39
|
+
|
40
|
+
def []=(subschema_name, subschema_definition)
|
41
|
+
subschemas[subschema_name] = Schema.new(context, build_json_path(path, subschema_name), subschema_definition)
|
42
|
+
end
|
43
|
+
|
44
|
+
def each(&block)
|
45
|
+
subschemas.each(&block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def reduce(memo, &block)
|
49
|
+
subschemas.reduce(memo, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def parent?
|
53
|
+
subschemas.count > 0
|
54
|
+
end
|
55
|
+
|
56
|
+
def keys
|
57
|
+
subschemas.keys
|
58
|
+
end
|
59
|
+
|
60
|
+
def values
|
61
|
+
subschemas.values
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
attr_reader :context, :subschemas
|
67
|
+
|
68
|
+
def map_value
|
69
|
+
@value = Value.new self
|
70
|
+
end
|
71
|
+
|
72
|
+
def map_types(*args)
|
73
|
+
@types = args.flatten.compact.map { |type|
|
74
|
+
type = type.to_sym
|
75
|
+
|
76
|
+
raise ArgumentError.new("Invalid type '#{type}' specified for #{error_suffix(attribute, json_path)}}") unless Definitions::TYPES.include? type
|
77
|
+
|
78
|
+
Definitions::TYPES[type]
|
79
|
+
}.uniq
|
80
|
+
end
|
81
|
+
|
82
|
+
def map_normalizers(*args)
|
83
|
+
@normalizers = args.flatten.compact.map { |normalizer|
|
84
|
+
normalizer = normalizer.to_sym
|
85
|
+
|
86
|
+
raise ArgumentError.new("Invalid normalizer(s) '#{normalizer}' specified for #{error_suffix(attribute, json_path)}") unless context.normalizers.include?(normalizer)
|
87
|
+
|
88
|
+
context.normalizers[normalizer]
|
89
|
+
}.uniq
|
90
|
+
end
|
91
|
+
|
92
|
+
def map_format(fmt)
|
93
|
+
unless fmt.nil?
|
94
|
+
fmt = fmt.to_sym
|
95
|
+
|
96
|
+
raise ArgumentError.new("Invalid format '#{fmt}' specified for #{error_suffix(attribute, json_path)}") unless context.formats.include?(fmt)
|
97
|
+
|
98
|
+
@format = context.formats[fmt]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def map_default(default)
|
103
|
+
@default = default
|
104
|
+
end
|
105
|
+
|
106
|
+
def map_subschemas(subschema_definitions)
|
107
|
+
@subschemas = subschema_definitions.reduce({}) { |memo, (subschema_name, subschema_definition)|
|
108
|
+
raise ArgumentError.new("'subschemas' option can only be set for 'object' type schemas, attempted to provide subschemas for #{error_suffix(attribute, json_path)}") unless types.include? Definitions::TYPES[:object]
|
109
|
+
raise ArgumentError.new("The 'subschemas' option must be passed a hash, please check #{error_suffix(attribute, json_path)}") unless subschema_definition.is_a?(Hash)
|
110
|
+
|
111
|
+
subschema_path = build_json_path(path, subschema_name)
|
112
|
+
memo[subschema_name] = Schema.new(context, subschema_path, subschema_definition)
|
113
|
+
memo
|
114
|
+
} if subschema_definitions.present?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module GoldenFleece
|
2
|
+
class Type
|
3
|
+
attr_reader :name, :classes
|
4
|
+
|
5
|
+
def initialize(name, *classes)
|
6
|
+
@name = name.to_sym
|
7
|
+
@classes = classes
|
8
|
+
end
|
9
|
+
|
10
|
+
def matches?(value)
|
11
|
+
classes.any? { |klass| value.is_a? klass }
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
":#{name}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def inspect
|
19
|
+
to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module GoldenFleece
|
2
|
+
module Utility
|
3
|
+
FALSE_VALUES = [false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"].to_set
|
4
|
+
|
5
|
+
def build_json_path(parent_path, key_name)
|
6
|
+
"#{parent_path}#{'/' unless parent_path =~ /\/$/}#{key_name}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def error_suffix(attribute, path)
|
10
|
+
"'#{path}' on column '#{attribute}'"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Copied from ActiveModel::Type::Boolean
|
14
|
+
# https://github.com/rails/rails/blob/master/activemodel/lib/active_model/type/boolean.rb
|
15
|
+
def cast_boolean(value)
|
16
|
+
if value == ""
|
17
|
+
nil
|
18
|
+
else
|
19
|
+
!FALSE_VALUES.include?(value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def deep_stringify_keys(hash)
|
24
|
+
hash.reduce({}) { |memo, (key, value)|
|
25
|
+
memo[key.to_s] = value.is_a?(Hash) ? deep_stringify_keys(value) : value
|
26
|
+
memo
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'golden_fleece/validations/validator_context'
|
3
|
+
|
4
|
+
module GoldenFleece
|
5
|
+
module Validations
|
6
|
+
module ActiveModel
|
7
|
+
class FleeceSchemaConformanceValidator < ::ActiveModel::EachValidator
|
8
|
+
def validate_each(record, attribute, persisted_json)
|
9
|
+
context = record.class.fleece_context
|
10
|
+
errors = ValidatorContext.new(record, attribute, persisted_json, context.schemas[attribute], '/').validate
|
11
|
+
|
12
|
+
errors.each { |e| record.errors.add attribute, e }
|
13
|
+
errors.empty?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'golden_fleece/utility'
|
2
|
+
|
3
|
+
# An ORM-independent recursive validator that goes down into every level of
|
4
|
+
# your nested JSON and applies various validation logic. ORM-specific validation
|
5
|
+
# classes should use this class for their core processing. Returns an array of
|
6
|
+
# error messages.
|
7
|
+
|
8
|
+
module GoldenFleece
|
9
|
+
class ValidatorContext
|
10
|
+
include Utility
|
11
|
+
|
12
|
+
def initialize(record, attribute, persisted_json, schemas, parent_path)
|
13
|
+
@persisted_json = persisted_json
|
14
|
+
@schemas = schemas
|
15
|
+
@parent_path = parent_path
|
16
|
+
persisted_keys = if persisted_json && persisted_json.keys
|
17
|
+
persisted_json.keys.map(&:to_sym)
|
18
|
+
else
|
19
|
+
[]
|
20
|
+
end
|
21
|
+
schemas_keys = schemas ? schemas.keys : []
|
22
|
+
@validatable_keys = (persisted_keys + schemas_keys).uniq
|
23
|
+
@record = record
|
24
|
+
@attribute = attribute
|
25
|
+
@errors = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def validate
|
29
|
+
validatable_keys.each do |key|
|
30
|
+
path = build_json_path(parent_path, key)
|
31
|
+
|
32
|
+
validate_key key, path
|
33
|
+
|
34
|
+
# If all keys on our current level are valid, proceed
|
35
|
+
if errors.empty?
|
36
|
+
schema = schemas[key]
|
37
|
+
value = schema.value.compute(record)
|
38
|
+
|
39
|
+
validate_type(value, schema.types, path)
|
40
|
+
validate_format(value, schema.format, path)
|
41
|
+
|
42
|
+
# If the key's value is a nested JSON object, recurse down
|
43
|
+
errors << ValidatorContext.new(record, attribute, (persisted_json ? persisted_json[key.to_s] : nil), schemas[key], path).validate if value.is_a?(Hash)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
errors.flatten
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
attr_reader :persisted_json, :schemas, :parent_path, :validatable_keys, :record, :attribute, :errors
|
53
|
+
|
54
|
+
def validate_key(key, path)
|
55
|
+
errors << "Invalid key #{error_suffix(attribute, path)}" unless schemas.keys.include? key
|
56
|
+
end
|
57
|
+
|
58
|
+
def validate_type(value, valid_types, path)
|
59
|
+
unless valid_types.any? { |valid_type| valid_type.matches? value }
|
60
|
+
errors << "Invalid type at #{error_suffix(attribute, path)}, must be one of #{valid_types}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def validate_format(value, valid_format, path)
|
65
|
+
if valid_format
|
66
|
+
begin
|
67
|
+
valid_format.validate record, value
|
68
|
+
rescue Exception => e
|
69
|
+
errors << "Invalid format at #{error_suffix(attribute, path)}: #{e.message}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'golden_fleece/definitions'
|
2
|
+
require 'hana'
|
3
|
+
|
4
|
+
module GoldenFleece
|
5
|
+
class Value
|
6
|
+
include Utility
|
7
|
+
|
8
|
+
def initialize(schema)
|
9
|
+
@schema = schema
|
10
|
+
self.value_initialized = false
|
11
|
+
end
|
12
|
+
|
13
|
+
def compute(record)
|
14
|
+
@record = record
|
15
|
+
|
16
|
+
if dirty?
|
17
|
+
@value = Hana::Pointer.new(schema.json_path).eval(record.read_attribute(schema.attribute))
|
18
|
+
|
19
|
+
cast_booleans
|
20
|
+
apply_normalizers
|
21
|
+
apply_default
|
22
|
+
|
23
|
+
self.value_initialized = true
|
24
|
+
end
|
25
|
+
|
26
|
+
value
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_accessor :value_initialized
|
32
|
+
attr_reader :schema, :record, :value
|
33
|
+
|
34
|
+
def dirty?
|
35
|
+
record.send("#{schema.attribute}_changed?") || !value_initialized
|
36
|
+
end
|
37
|
+
|
38
|
+
# Cast boolean values the way that Rails normally does on boolean columns
|
39
|
+
def cast_booleans
|
40
|
+
if schema.types.include? Definitions::TYPES[:boolean]
|
41
|
+
@value = cast_boolean(value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def apply_normalizers
|
46
|
+
@value = schema.normalizers.reduce(value) { |memo, normalizer| normalizer.normalize record, memo }
|
47
|
+
end
|
48
|
+
|
49
|
+
# If there's a persisted value, use that
|
50
|
+
# If not, use the default value; if the default is a lambda, call it
|
51
|
+
def apply_default
|
52
|
+
@value = if value.nil?
|
53
|
+
if schema.parent?
|
54
|
+
d = schema.reduce({}) { |memo, (subschema_name, subschema)|
|
55
|
+
memo[subschema_name] = subschema.value.compute(record)
|
56
|
+
memo
|
57
|
+
}
|
58
|
+
d.values.compact.empty? ? nil : d
|
59
|
+
elsif schema.default.respond_to?(:call)
|
60
|
+
schema.default.call(record)
|
61
|
+
else
|
62
|
+
schema.default
|
63
|
+
end
|
64
|
+
else
|
65
|
+
value
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
metadata
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: golden_fleece
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ersin Akinci
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-05-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.14'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activemodel
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: activesupport
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.9'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.9'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: hana
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.3'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.3'
|
111
|
+
description: Golden Fleece lets you validate, normalize, set up defaults for and provide
|
112
|
+
getters for JSON data in your Ruby data models through easy to use schemas. More
|
113
|
+
opinionated and easier to use than JSON Schema.
|
114
|
+
email:
|
115
|
+
- ersin.akinci@gmail.com
|
116
|
+
executables: []
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- ".gitignore"
|
121
|
+
- ".rspec"
|
122
|
+
- ".travis.yml"
|
123
|
+
- CODE_OF_CONDUCT.md
|
124
|
+
- Gemfile
|
125
|
+
- LICENSE.txt
|
126
|
+
- README.md
|
127
|
+
- Rakefile
|
128
|
+
- bin/console
|
129
|
+
- bin/setup
|
130
|
+
- golden_fleece.gemspec
|
131
|
+
- lib/golden_fleece.rb
|
132
|
+
- lib/golden_fleece/context.rb
|
133
|
+
- lib/golden_fleece/context/export.rb
|
134
|
+
- lib/golden_fleece/context/formats.rb
|
135
|
+
- lib/golden_fleece/context/getters.rb
|
136
|
+
- lib/golden_fleece/context/normalizers.rb
|
137
|
+
- lib/golden_fleece/context/schemas.rb
|
138
|
+
- lib/golden_fleece/definitions.rb
|
139
|
+
- lib/golden_fleece/format.rb
|
140
|
+
- lib/golden_fleece/model.rb
|
141
|
+
- lib/golden_fleece/model/active_model/normalization.rb
|
142
|
+
- lib/golden_fleece/model/active_model/validation.rb
|
143
|
+
- lib/golden_fleece/model/context.rb
|
144
|
+
- lib/golden_fleece/model/export.rb
|
145
|
+
- lib/golden_fleece/model/normalization.rb
|
146
|
+
- lib/golden_fleece/normalizer.rb
|
147
|
+
- lib/golden_fleece/schema.rb
|
148
|
+
- lib/golden_fleece/type.rb
|
149
|
+
- lib/golden_fleece/utility.rb
|
150
|
+
- lib/golden_fleece/validations/active_model/fleece_schema_conformance_validator.rb
|
151
|
+
- lib/golden_fleece/validations/validator_context.rb
|
152
|
+
- lib/golden_fleece/value.rb
|
153
|
+
- lib/golden_fleece/version.rb
|
154
|
+
homepage: https://github.com/earksiinni/golden_fleece
|
155
|
+
licenses:
|
156
|
+
- MIT
|
157
|
+
metadata: {}
|
158
|
+
post_install_message:
|
159
|
+
rdoc_options: []
|
160
|
+
require_paths:
|
161
|
+
- lib
|
162
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
|
+
requirements:
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: '0'
|
172
|
+
requirements: []
|
173
|
+
rubyforge_project:
|
174
|
+
rubygems_version: 2.6.11
|
175
|
+
signing_key:
|
176
|
+
specification_version: 4
|
177
|
+
summary: Easy schemas for your JSON columns.
|
178
|
+
test_files: []
|