activerecord_json_validator 1.3.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yaml +37 -0
- data/.rubocop.yml +5 -0
- data/LICENSE.md +1 -1
- data/README.md +74 -21
- data/activerecord_json_validator.gemspec +6 -6
- data/docker-compose.yml +11 -0
- data/lib/active_record/json_validator/validator.rb +18 -19
- data/lib/active_record/json_validator/version.rb +1 -1
- data/lib/activerecord_json_validator.rb +1 -1
- data/spec/json_validator_spec.rb +79 -92
- data/spec/spec_helper.rb +0 -1
- data/spec/support/macros/database/database_adapter.rb +1 -1
- data/spec/support/macros/database/mysql_adapter.rb +0 -9
- data/spec/support/macros/database/postgresql_adapter.rb +0 -9
- data/spec/support/macros/database_macros.rb +1 -1
- metadata +37 -39
- data/.travis.yml +0 -33
- data/gemfiles/Gemfile.activerecord-4.2.x +0 -6
- data/gemfiles/Gemfile.activerecord-5.0.x +0 -5
- data/gemfiles/Gemfile.activerecord-6.0.x +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90252b743670b3c94e3ac700b868c698cf9b94124f115fa7b39b27b63f66445a
|
4
|
+
data.tar.gz: 8de9a2eeb74864be691a63ecb9a27f286d669949221b78f70db62ecbb3f9b62c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: abdacc70526c1c9be8b5d3585283eaa594fe0c48116aab7eac9ca19a5553fcae69a409986e3793eb96c9ab80624d74f8556c69c0f3994a88987a5d83f9a84266
|
7
|
+
data.tar.gz: a170d66db92dcc1bc0e3eb9ebe75fb939ccecb85d952cc18b590aa8404562ddf0093edbe4993d1a16b11ba1efa5135e78e2b957f5ef114a41e8e0399cfcc08c5
|
@@ -0,0 +1,37 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
pull_request:
|
8
|
+
branches:
|
9
|
+
- '**'
|
10
|
+
|
11
|
+
jobs:
|
12
|
+
ci:
|
13
|
+
runs-on: ubuntu-latest
|
14
|
+
|
15
|
+
services:
|
16
|
+
db:
|
17
|
+
image: postgres:10.19
|
18
|
+
env:
|
19
|
+
POSTGRES_DB: activerecord_json_validator_test
|
20
|
+
POSTGRES_USER: postgres
|
21
|
+
POSTGRES_PASSWORD: postgres
|
22
|
+
ports: ['5432:5432']
|
23
|
+
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
24
|
+
|
25
|
+
env:
|
26
|
+
CANONICAL_HOST: localhost
|
27
|
+
DATABASE_URL: postgres://postgres:postgres@localhost/activerecord_json_validator_test
|
28
|
+
DB_ADAPTER: postgresql
|
29
|
+
|
30
|
+
steps:
|
31
|
+
- uses: actions/checkout@v2
|
32
|
+
- uses: ruby/setup-ruby@v1
|
33
|
+
with:
|
34
|
+
ruby-version: '2.7.5'
|
35
|
+
bundler-cache: true
|
36
|
+
- run: bundle exec rubocop
|
37
|
+
- run: bundle exec rake spec
|
data/.rubocop.yml
CHANGED
data/LICENSE.md
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
<p align="center">
|
2
2
|
<a href="https://github.com/mirego/activerecord_json_validator">
|
3
|
-
<img src="https://
|
3
|
+
<img src="https://user-images.githubusercontent.com/11348/126779905-3468eb15-d554-46d5-925b-235f68169d86.png" alt="" />
|
4
4
|
</a>
|
5
5
|
<br />
|
6
|
-
<code>ActiveRecord::JSONValidator</code> makes it easy to validate<br /> JSON attributes against a <a href="
|
6
|
+
<code>ActiveRecord::JSONValidator</code> makes it easy to validate<br /> JSON attributes against a <a href="https://json-schema.org/">JSON schema</a>.
|
7
7
|
<br /><br />
|
8
|
-
<a href="https://rubygems.org/gems/activerecord_json_validator"><img src="
|
9
|
-
<a href="https://
|
8
|
+
<a href="https://rubygems.org/gems/activerecord_json_validator"><img src="https://img.shields.io/gem/v/activerecord_json_validator.svg" /></a>
|
9
|
+
<a href="https://github.com/mirego/activerecord_json_validator/actions/workflows/ci.yaml"><img src="https://github.com/mirego/activerecord_json_validator/actions/workflows/ci.yaml/badge.svg" /></a>
|
10
10
|
</p>
|
11
11
|
|
12
12
|
---
|
@@ -16,17 +16,19 @@
|
|
16
16
|
Add this line to your application's Gemfile:
|
17
17
|
|
18
18
|
```ruby
|
19
|
-
gem 'activerecord_json_validator'
|
19
|
+
gem 'activerecord_json_validator', '~> 3.0.0'
|
20
20
|
```
|
21
21
|
|
22
22
|
## Usage
|
23
23
|
|
24
24
|
### JSON Schema
|
25
25
|
|
26
|
+
Schemas should be a JSON file
|
27
|
+
|
26
28
|
```json
|
27
29
|
{
|
28
30
|
"type": "object",
|
29
|
-
"$schema": "http://json-schema.org/draft-04/schema",
|
31
|
+
"$schema": "http://json-schema.org/draft-04/schema#",
|
30
32
|
"properties": {
|
31
33
|
"city": { "type": "string" },
|
32
34
|
"country": { "type": "string" }
|
@@ -45,7 +47,7 @@ end
|
|
45
47
|
|
46
48
|
class User < ActiveRecord::Base
|
47
49
|
# Constants
|
48
|
-
PROFILE_JSON_SCHEMA = Rails.root.join('config', 'schemas', 'profile.
|
50
|
+
PROFILE_JSON_SCHEMA = Rails.root.join('config', 'schemas', 'profile.json')
|
49
51
|
|
50
52
|
# Validations
|
51
53
|
validates :name, presence: true
|
@@ -65,17 +67,17 @@ user.profile_invalid_json # => '{invalid JSON":}'
|
|
65
67
|
|
66
68
|
#### Options
|
67
69
|
|
68
|
-
| Option | Description
|
69
|
-
|
70
|
-
| `:schema` | The JSON schema to validate the data against (see **Schema** section)
|
71
|
-
| `:
|
72
|
-
| `:
|
70
|
+
| Option | Description |
|
71
|
+
| ---------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
72
|
+
| `:schema` | The JSON schema to validate the data against (see **Schema** section) |
|
73
|
+
| `:value` | The actual value to use when validating (see **Value** section) |
|
74
|
+
| `:message` | The ActiveRecord message added to the record errors (see **Message** section) |
|
75
|
+
| `:options` | A `Hash` of [`json_schemer`](https://github.com/davishmcclurg/json_schemer#options)-supported options to pass to the validator |
|
73
76
|
|
74
77
|
##### Schema
|
75
78
|
|
76
|
-
`ActiveRecord::JSONValidator` uses the
|
77
|
-
data against a JSON schema.
|
78
|
-
`JSON::Validator.validate` would take as the `schema` argument.
|
79
|
+
`ActiveRecord::JSONValidator` uses the [json_schemer](https://github.com/davishmcclurg/json_schemer) gem to validate the JSON
|
80
|
+
data against a JSON schema.
|
79
81
|
|
80
82
|
Additionally, you can use a `Symbol` or a `Proc`. Both will be executed in the
|
81
83
|
context of the validated record (`Symbol` will be sent as a method and the
|
@@ -84,8 +86,8 @@ context of the validated record (`Symbol` will be sent as a method and the
|
|
84
86
|
```ruby
|
85
87
|
class User < ActiveRecord::Base
|
86
88
|
# Constants
|
87
|
-
PROFILE_REGULAR_JSON_SCHEMA = Rails.root.join('config', 'schemas', 'profile.json_schema')
|
88
|
-
PROFILE_ADMIN_JSON_SCHEMA = Rails.root.join('config', 'schemas', 'profile_admin.json_schema')
|
89
|
+
PROFILE_REGULAR_JSON_SCHEMA = Rails.root.join('config', 'schemas', 'profile.json_schema')
|
90
|
+
PROFILE_ADMIN_JSON_SCHEMA = Rails.root.join('config', 'schemas', 'profile_admin.json_schema')
|
89
91
|
|
90
92
|
# Validations
|
91
93
|
validates :profile, presence: true, json: { schema: lambda { dynamic_profile_schema } } # `schema: :dynamic_profile_schema` would also work
|
@@ -96,6 +98,48 @@ class User < ActiveRecord::Base
|
|
96
98
|
end
|
97
99
|
```
|
98
100
|
|
101
|
+
The schema is passed to the `JSONSchemer.schema` function, so it can be anything supported by it:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
class User < ActiveRecord::Base
|
105
|
+
# Constants
|
106
|
+
JSON_SCHEMA = Rails.root.join('config', 'schemas', 'profile.json_schema')
|
107
|
+
# JSON_SCHEMA = { 'type' => 'object', 'properties' => { 'foo' => { 'type' => 'integer', 'minimum' => 3 } } }
|
108
|
+
# JSON_SCHEMA = '{"type":"object","properties":{"foo":{"type":"integer","minimum":3}}}'
|
109
|
+
|
110
|
+
# Validations
|
111
|
+
validates :profile, presence: true, json: { schema: JSON_SCHEMA }
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
115
|
+
##### Value
|
116
|
+
|
117
|
+
By default, the validator will use the “getter” method to the fetch attribute
|
118
|
+
value and validate the schema against it.
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
# Will validate `self.foo`
|
122
|
+
validates :foo, json: { schema: SCHEMA }
|
123
|
+
```
|
124
|
+
|
125
|
+
But you can change this behavior if the getter method doesn’t return raw JSON data (a `Hash`):
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
# Will validate `self[:foo]`
|
129
|
+
validates :foo, json: { schema: SCHEMA, value: ->(record, _, _) { record[:foo] } }
|
130
|
+
```
|
131
|
+
|
132
|
+
You could also implement a “raw getter” if you want to avoid the `value` option:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
# Will validate `self[:foo]`
|
136
|
+
validates :raw_foo, json: { schema: SCHEMA }
|
137
|
+
|
138
|
+
def raw_foo
|
139
|
+
self[:foo]
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
99
143
|
##### Message
|
100
144
|
|
101
145
|
Like any other ActiveModel validation, you can specify either a `Symbol` or
|
@@ -120,14 +164,23 @@ user.errors.full_messages
|
|
120
164
|
# ]
|
121
165
|
```
|
122
166
|
|
167
|
+
## Development
|
168
|
+
|
169
|
+
The tests require a database. We've provided a simple `docker-compose.yml` that will make
|
170
|
+
it trivial to run the tests against PostgreSQL. Simply run `docker compose up -d`
|
171
|
+
followed by `rake spec`. When you're done, run `docker compose down` to stop the database.
|
172
|
+
|
173
|
+
In order to use another database, simply define the `DATABASE_URL` environment variable
|
174
|
+
appropriately.
|
175
|
+
|
123
176
|
## License
|
124
177
|
|
125
|
-
`ActiveRecord::JSONValidator` is © 2013-
|
178
|
+
`ActiveRecord::JSONValidator` is © 2013-2024 [Mirego](https://www.mirego.com) and may be freely distributed under the [New BSD license](https://opensource.org/licenses/BSD-3-Clause). See the [`LICENSE.md`](https://github.com/mirego/activerecord_json_validator/blob/master/LICENSE.md) file.
|
126
179
|
|
127
|
-
The tree logo is based on [this lovely icon](
|
180
|
+
The tree logo is based on [this lovely icon](https://thenounproject.com/term/tree/51004/) by [Sara Quintana](https://thenounproject.com/sara.quintana.75), from The Noun Project. Used under a [Creative Commons BY 3.0](https://creativecommons.org/licenses/by/3.0/) license.
|
128
181
|
|
129
182
|
## About Mirego
|
130
183
|
|
131
|
-
[Mirego](
|
184
|
+
[Mirego](https://www.mirego.com) is a team of passionate people who believe that work is a place where you can innovate and have fun. We're a team of [talented people](https://life.mirego.com) who imagine and build beautiful Web and mobile applications. We come together to share ideas and [change the world](https://www.mirego.org).
|
132
185
|
|
133
|
-
We also [love open-source software](
|
186
|
+
We also [love open-source software](https://open.mirego.com) and we try to give back to the community as much as we can.
|
@@ -18,15 +18,15 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_development_dependency 'bundler', '
|
21
|
+
spec.add_development_dependency 'bundler', '>= 1.12'
|
22
22
|
spec.add_development_dependency 'rake'
|
23
23
|
spec.add_development_dependency 'rspec', '~> 3.5'
|
24
24
|
spec.add_development_dependency 'pg'
|
25
|
-
spec.add_development_dependency '
|
26
|
-
spec.add_development_dependency 'activesupport', '>= 4.2.0', '< 7'
|
27
|
-
spec.add_development_dependency 'phare'
|
25
|
+
spec.add_development_dependency 'activesupport', '>= 4.2.0', '< 9'
|
28
26
|
spec.add_development_dependency 'rubocop', '~> 0.28'
|
27
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 1.44'
|
28
|
+
spec.add_development_dependency 'rubocop-standard', '~> 6.0'
|
29
29
|
|
30
|
-
spec.add_dependency '
|
31
|
-
spec.add_dependency 'activerecord', '>= 4.2.0', '<
|
30
|
+
spec.add_dependency 'json_schemer', '~> 2.2'
|
31
|
+
spec.add_dependency 'activerecord', '>= 4.2.0', '< 9'
|
32
32
|
end
|
data/docker-compose.yml
ADDED
@@ -5,6 +5,7 @@ class JsonValidator < ActiveModel::EachValidator
|
|
5
5
|
options.reverse_merge!(message: :invalid_json)
|
6
6
|
options.reverse_merge!(schema: nil)
|
7
7
|
options.reverse_merge!(options: {})
|
8
|
+
options.reverse_merge!(value: ->(_record, _attribute, value) { value })
|
8
9
|
@attributes = options[:attributes]
|
9
10
|
|
10
11
|
super
|
@@ -14,15 +15,20 @@ class JsonValidator < ActiveModel::EachValidator
|
|
14
15
|
|
15
16
|
# Validate the JSON value with a JSON schema path or String
|
16
17
|
def validate_each(record, attribute, value)
|
17
|
-
#
|
18
|
-
|
18
|
+
# Get the _actual_ attribute value, not the getter method value
|
19
|
+
value = options.fetch(:value).call(record, attribute, value)
|
20
|
+
|
21
|
+
# Validate value with JSON Schemer
|
22
|
+
errors = JSONSchemer.schema(schema(record), **options.fetch(:options)).validate(value).to_a
|
19
23
|
|
20
24
|
# Everything is good if we don’t have any errors and we got valid JSON value
|
21
25
|
return if errors.empty? && record.send(:"#{attribute}_invalid_json").blank?
|
22
26
|
|
23
27
|
# Add error message to the attribute
|
28
|
+
details = errors.map { |e| e.fetch('error') }
|
24
29
|
message(errors).each do |error|
|
25
|
-
|
30
|
+
error = error.fetch('error') if error.is_a?(Hash)
|
31
|
+
record.errors.add(attribute, error, errors: details, value: value)
|
26
32
|
end
|
27
33
|
end
|
28
34
|
|
@@ -31,25 +37,27 @@ protected
|
|
31
37
|
# Redefine the setter method for the attributes, since we want to
|
32
38
|
# catch JSON parsing errors.
|
33
39
|
def inject_setter_method(klass, attributes)
|
40
|
+
return if klass.nil?
|
41
|
+
|
34
42
|
attributes.each do |attribute|
|
35
|
-
klass.
|
43
|
+
klass.prepend(Module.new do
|
36
44
|
attr_reader :"#{attribute}_invalid_json"
|
37
45
|
|
38
46
|
define_method "#{attribute}=" do |args|
|
39
47
|
begin
|
40
|
-
@#{attribute}_invalid_json
|
48
|
+
instance_variable_set("@#{attribute}_invalid_json", nil)
|
41
49
|
args = ::ActiveSupport::JSON.decode(args) if args.is_a?(::String)
|
42
50
|
super(args)
|
43
51
|
rescue ActiveSupport::JSON.parse_error
|
44
|
-
@#{attribute}_invalid_json
|
52
|
+
instance_variable_set("@#{attribute}_invalid_json", args)
|
45
53
|
super({})
|
46
54
|
end
|
47
55
|
end
|
48
|
-
|
56
|
+
end)
|
49
57
|
end
|
50
58
|
end
|
51
59
|
|
52
|
-
# Return a valid schema
|
60
|
+
# Return a valid schema, recursively calling
|
53
61
|
# itself until it gets a non-Proc/non-Symbol value.
|
54
62
|
def schema(record, schema = nil)
|
55
63
|
schema ||= options.fetch(:schema)
|
@@ -61,18 +69,9 @@ protected
|
|
61
69
|
end
|
62
70
|
end
|
63
71
|
|
64
|
-
def validatable_value(value)
|
65
|
-
return value if value.is_a?(String)
|
66
|
-
|
67
|
-
::ActiveSupport::JSON.encode(value)
|
68
|
-
end
|
69
|
-
|
70
72
|
def message(errors)
|
71
73
|
message = options.fetch(:message)
|
72
|
-
|
73
|
-
|
74
|
-
when Proc then [message.call(errors)].flatten if message.is_a?(Proc)
|
75
|
-
else [message]
|
76
|
-
end
|
74
|
+
message = message.call(errors) if message.is_a?(Proc)
|
75
|
+
[message].flatten
|
77
76
|
end
|
78
77
|
end
|
data/spec/json_validator_spec.rb
CHANGED
@@ -3,106 +3,109 @@
|
|
3
3
|
# rubocop:disable Metrics/BlockLength
|
4
4
|
require 'spec_helper'
|
5
5
|
|
6
|
+
module CountryDefaulter
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
class_methods do
|
10
|
+
def default_country_attribute(name, country:)
|
11
|
+
define_method("#{name}=") do |value|
|
12
|
+
self[name] = { country: country }.merge(value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
6
18
|
describe JsonValidator do
|
7
|
-
describe :
|
8
|
-
# NOTE: We do not explicitely call `JsonValidator.new` in the tests,
|
9
|
-
# because we let Rails (ActiveModel::Validations) do that when we call
|
10
|
-
# `validates … json: true` on the model.
|
11
|
-
#
|
12
|
-
# This allows us to test the constructor behavior when executed in
|
13
|
-
# different Rails versions that do not pass the same arguments to it.
|
19
|
+
describe :validate_each do
|
14
20
|
before do
|
15
21
|
run_migration do
|
16
22
|
create_table(:users, force: true) do |t|
|
17
|
-
t.string :name
|
18
23
|
t.text :data
|
24
|
+
t.json :smart_data
|
19
25
|
end
|
20
26
|
end
|
21
27
|
|
22
28
|
spawn_model 'User' do
|
23
|
-
|
24
|
-
|
29
|
+
include CountryDefaulter
|
30
|
+
|
31
|
+
schema = '
|
32
|
+
{
|
33
|
+
"type": "object",
|
34
|
+
"properties": {
|
35
|
+
"city": { "type": "string" },
|
36
|
+
"country": { "type": "string" }
|
37
|
+
},
|
38
|
+
"required": ["country"]
|
39
|
+
}
|
40
|
+
'
|
41
|
+
|
42
|
+
default_country_attribute :smart_data, country: 'Canada'
|
43
|
+
|
44
|
+
serialize :data, coder: JSON
|
45
|
+
serialize :other_data, coder: JSON
|
46
|
+
validates :data, json: { schema: schema, message: ->(errors) { errors } }
|
47
|
+
validates :other_data, json: { schema: schema, message: ->(errors) { errors.map { |error| error['details'].to_a.flatten.join(' ') } } }
|
48
|
+
validates :smart_data, json: { value: ->(record, _, _) { record[:smart_data] }, schema: schema, message: ->(errors) { errors } }
|
49
|
+
|
50
|
+
def smart_data
|
51
|
+
OpenStruct.new(self[:smart_data])
|
52
|
+
end
|
25
53
|
end
|
26
|
-
|
27
|
-
record.data = data
|
28
54
|
end
|
29
55
|
|
30
|
-
|
56
|
+
context 'with valid JSON data but schema errors' do
|
57
|
+
let(:user) do
|
58
|
+
User.new(
|
59
|
+
data: '{"city":"Quebec City"}',
|
60
|
+
other_data: '{"city":"Quebec City"}',
|
61
|
+
smart_data: { country: 'Ireland', city: 'Dublin' }
|
62
|
+
)
|
63
|
+
end
|
31
64
|
|
32
|
-
|
33
|
-
|
34
|
-
|
65
|
+
specify do
|
66
|
+
expect(user).not_to be_valid
|
67
|
+
expect(user.errors.full_messages).to eql(['Data object at root is missing required properties: country', 'Other data missing_keys country'])
|
68
|
+
expect(user.errors.group_by_attribute[:data].first).to have_attributes(
|
69
|
+
options: include(errors: ['object at root is missing required properties: country'])
|
70
|
+
)
|
71
|
+
expect(user.errors.group_by_attribute[:other_data].first).to have_attributes(
|
72
|
+
options: include(errors: ['object at root is missing required properties: country'])
|
73
|
+
)
|
74
|
+
expect(user.data).to eql({ 'city' => 'Quebec City' })
|
75
|
+
expect(user.data_invalid_json).to be_nil
|
76
|
+
expect(user.smart_data.city).to eql('Dublin')
|
77
|
+
expect(user.smart_data.country).to eql('Ireland')
|
78
|
+
end
|
35
79
|
end
|
36
80
|
|
37
81
|
context 'with invalid JSON data' do
|
38
|
-
let(:data) {
|
39
|
-
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
describe :validate_each do
|
44
|
-
let(:validator) { JsonValidator.new(options) }
|
45
|
-
let(:options) { { attributes: [attribute], options: { strict: true } } }
|
46
|
-
let(:validate_each!) { validator.validate_each(record, attribute, value) }
|
47
|
-
|
48
|
-
# Doubles
|
49
|
-
let(:attribute) { double(:attribute, to_s: 'attribute_name') }
|
50
|
-
let(:record) { double(:record, errors: record_errors) }
|
51
|
-
let(:record_errors) { double(:errors) }
|
52
|
-
let(:value) { double(:value) }
|
53
|
-
let(:schema) { double(:schema) }
|
54
|
-
let(:validatable_value) { double(:validatable_value) }
|
55
|
-
let(:validator_errors) { double(:validator_errors) }
|
56
|
-
|
57
|
-
before do
|
58
|
-
expect(validator).to receive(:schema).with(record).and_return(schema)
|
59
|
-
expect(validator).to receive(:validatable_value).with(value).and_return(validatable_value)
|
60
|
-
expect(::JSON::Validator).to receive(:fully_validate).with(schema, validatable_value, options[:options]).and_return(validator_errors)
|
61
|
-
end
|
62
|
-
|
63
|
-
context 'with JSON::Validator errors' do
|
64
|
-
before do
|
65
|
-
expect(validator_errors).to receive(:empty?).and_return(false)
|
66
|
-
expect(record).not_to receive(:"#{attribute}_invalid_json")
|
67
|
-
expect(record_errors).to receive(:add).with(attribute, options[:message], value: value)
|
68
|
-
end
|
82
|
+
let(:data) { 'What? This is not JSON at all.' }
|
83
|
+
let(:user) { User.new(data: data, smart_data: data) }
|
69
84
|
|
70
|
-
specify
|
71
|
-
|
85
|
+
specify do
|
86
|
+
expect(user.data_invalid_json).to eql(data)
|
87
|
+
expect(user.data).to eql({})
|
72
88
|
|
73
|
-
|
74
|
-
|
75
|
-
expect(
|
76
|
-
expect(record).to receive(:"#{attribute}_invalid_json").and_return('foo"{]')
|
77
|
-
expect(record_errors).to receive(:add).with(attribute, options[:message], value: value)
|
89
|
+
# Ensure that both setters ran
|
90
|
+
expect(user.smart_data_invalid_json).to eql(data)
|
91
|
+
expect(user.smart_data).to eql(OpenStruct.new({ country: 'Canada' }))
|
78
92
|
end
|
79
|
-
|
80
|
-
specify { validate_each! }
|
81
93
|
end
|
82
94
|
|
83
|
-
context '
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
95
|
+
context 'with missing country in smart data' do
|
96
|
+
let(:user) do
|
97
|
+
User.new(
|
98
|
+
data: '{"city":"Quebec City","country":"Canada"}',
|
99
|
+
other_data: '{"city":"Quebec City","country":"Canada"}',
|
100
|
+
smart_data: { city: 'Quebec City' }
|
101
|
+
)
|
88
102
|
end
|
89
103
|
|
90
|
-
specify
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
let(:options) { { attributes: [attribute], message: message, options: { strict: true } } }
|
95
|
-
let(:message) { ->(errors) { errors.to_a } }
|
96
|
-
|
97
|
-
before do
|
98
|
-
expect(validator_errors).to receive(:empty?).and_return(false)
|
99
|
-
expect(validator_errors).to receive(:to_a).and_return(%i[first_error second_error])
|
100
|
-
expect(record).not_to receive(:"#{attribute}_invalid_json")
|
101
|
-
expect(record_errors).to receive(:add).with(attribute, :first_error, value: value)
|
102
|
-
expect(record_errors).to receive(:add).with(attribute, :second_error, value: value)
|
104
|
+
specify do
|
105
|
+
expect(user).to be_valid
|
106
|
+
expect(user.smart_data.city).to eql('Quebec City')
|
107
|
+
expect(user.smart_data.country).to eql('Canada') # Due to CountryDefaulter
|
103
108
|
end
|
104
|
-
|
105
|
-
specify { validate_each! }
|
106
109
|
end
|
107
110
|
end
|
108
111
|
|
@@ -155,22 +158,6 @@ describe JsonValidator do
|
|
155
158
|
end
|
156
159
|
end
|
157
160
|
|
158
|
-
describe :validatable_value do
|
159
|
-
let(:validator) { JsonValidator.new(options) }
|
160
|
-
let(:options) { { attributes: [:foo] } }
|
161
|
-
let(:validatable_value) { validator.send(:validatable_value, value) }
|
162
|
-
|
163
|
-
context 'with non-String value' do
|
164
|
-
let(:value) { { foo: 'bar' } }
|
165
|
-
it { expect(validatable_value).to eql('{"foo":"bar"}') }
|
166
|
-
end
|
167
|
-
|
168
|
-
context 'with String value' do
|
169
|
-
let(:value) { '{\"foo\":\"bar\"}' }
|
170
|
-
it { expect(validatable_value).to eql(value) }
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
161
|
describe :message do
|
175
162
|
let(:validator) { JsonValidator.new(options) }
|
176
163
|
let(:options) { { attributes: [:foo], message: message_option } }
|
data/spec/spec_helper.rb
CHANGED
@@ -6,6 +6,6 @@ class DatabaseAdapter
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def establish_connection!
|
9
|
-
ActiveRecord::Base.establish_connection(
|
9
|
+
ActiveRecord::Base.establish_connection(ENV.fetch('DATABASE_URL', 'postgres://postgres@localhost/activerecord_json_validator_test'))
|
10
10
|
end
|
11
11
|
end
|
@@ -3,15 +3,6 @@
|
|
3
3
|
require_relative 'database_adapter'
|
4
4
|
|
5
5
|
class Mysql2Adapter < DatabaseAdapter
|
6
|
-
def database_configuration
|
7
|
-
{
|
8
|
-
adapter: 'mysql2',
|
9
|
-
database: @database,
|
10
|
-
username: 'travis',
|
11
|
-
encoding: 'utf8'
|
12
|
-
}
|
13
|
-
end
|
14
|
-
|
15
6
|
def reset_database!
|
16
7
|
ActiveRecord::Base.connection.execute("SELECT concat('DROP TABLE IF EXISTS ', table_name, ';') FROM information_schema.tables WHERE table_schema = '#{@database}';")
|
17
8
|
end
|
@@ -3,15 +3,6 @@
|
|
3
3
|
require_relative 'database_adapter'
|
4
4
|
|
5
5
|
class PostgresqlAdapter < DatabaseAdapter
|
6
|
-
def database_configuration
|
7
|
-
{
|
8
|
-
adapter: 'postgresql',
|
9
|
-
database: @database,
|
10
|
-
user: 'postgres',
|
11
|
-
schema_search_path: 'public'
|
12
|
-
}
|
13
|
-
end
|
14
|
-
|
15
6
|
def reset_database!
|
16
7
|
ActiveRecord::Base.connection.execute('drop schema public cascade;')
|
17
8
|
ActiveRecord::Base.connection.execute('create schema public;')
|
metadata
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord_json_validator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rémi Prévost
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-01-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.12'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.12'
|
27
27
|
- !ruby/object:Gem::Dependency
|
@@ -67,81 +67,81 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: activesupport
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
75
|
+
version: 4.2.0
|
76
|
+
- - "<"
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '9'
|
76
79
|
type: :development
|
77
80
|
prerelease: false
|
78
81
|
version_requirements: !ruby/object:Gem::Requirement
|
79
82
|
requirements:
|
80
83
|
- - ">="
|
81
84
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
85
|
+
version: 4.2.0
|
86
|
+
- - "<"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '9'
|
83
89
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
90
|
+
name: rubocop
|
85
91
|
requirement: !ruby/object:Gem::Requirement
|
86
92
|
requirements:
|
87
|
-
- - "
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: 4.2.0
|
90
|
-
- - "<"
|
93
|
+
- - "~>"
|
91
94
|
- !ruby/object:Gem::Version
|
92
|
-
version: '
|
95
|
+
version: '0.28'
|
93
96
|
type: :development
|
94
97
|
prerelease: false
|
95
98
|
version_requirements: !ruby/object:Gem::Requirement
|
96
99
|
requirements:
|
97
|
-
- - "
|
98
|
-
- !ruby/object:Gem::Version
|
99
|
-
version: 4.2.0
|
100
|
-
- - "<"
|
100
|
+
- - "~>"
|
101
101
|
- !ruby/object:Gem::Version
|
102
|
-
version: '
|
102
|
+
version: '0.28'
|
103
103
|
- !ruby/object:Gem::Dependency
|
104
|
-
name:
|
104
|
+
name: rubocop-rspec
|
105
105
|
requirement: !ruby/object:Gem::Requirement
|
106
106
|
requirements:
|
107
|
-
- - "
|
107
|
+
- - "~>"
|
108
108
|
- !ruby/object:Gem::Version
|
109
|
-
version: '
|
109
|
+
version: '1.44'
|
110
110
|
type: :development
|
111
111
|
prerelease: false
|
112
112
|
version_requirements: !ruby/object:Gem::Requirement
|
113
113
|
requirements:
|
114
|
-
- - "
|
114
|
+
- - "~>"
|
115
115
|
- !ruby/object:Gem::Version
|
116
|
-
version: '
|
116
|
+
version: '1.44'
|
117
117
|
- !ruby/object:Gem::Dependency
|
118
|
-
name: rubocop
|
118
|
+
name: rubocop-standard
|
119
119
|
requirement: !ruby/object:Gem::Requirement
|
120
120
|
requirements:
|
121
121
|
- - "~>"
|
122
122
|
- !ruby/object:Gem::Version
|
123
|
-
version: '0
|
123
|
+
version: '6.0'
|
124
124
|
type: :development
|
125
125
|
prerelease: false
|
126
126
|
version_requirements: !ruby/object:Gem::Requirement
|
127
127
|
requirements:
|
128
128
|
- - "~>"
|
129
129
|
- !ruby/object:Gem::Version
|
130
|
-
version: '0
|
130
|
+
version: '6.0'
|
131
131
|
- !ruby/object:Gem::Dependency
|
132
|
-
name:
|
132
|
+
name: json_schemer
|
133
133
|
requirement: !ruby/object:Gem::Requirement
|
134
134
|
requirements:
|
135
135
|
- - "~>"
|
136
136
|
- !ruby/object:Gem::Version
|
137
|
-
version: '2.
|
137
|
+
version: '2.2'
|
138
138
|
type: :runtime
|
139
139
|
prerelease: false
|
140
140
|
version_requirements: !ruby/object:Gem::Requirement
|
141
141
|
requirements:
|
142
142
|
- - "~>"
|
143
143
|
- !ruby/object:Gem::Version
|
144
|
-
version: '2.
|
144
|
+
version: '2.2'
|
145
145
|
- !ruby/object:Gem::Dependency
|
146
146
|
name: activerecord
|
147
147
|
requirement: !ruby/object:Gem::Requirement
|
@@ -151,7 +151,7 @@ dependencies:
|
|
151
151
|
version: 4.2.0
|
152
152
|
- - "<"
|
153
153
|
- !ruby/object:Gem::Version
|
154
|
-
version: '
|
154
|
+
version: '9'
|
155
155
|
type: :runtime
|
156
156
|
prerelease: false
|
157
157
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -161,7 +161,7 @@ dependencies:
|
|
161
161
|
version: 4.2.0
|
162
162
|
- - "<"
|
163
163
|
- !ruby/object:Gem::Version
|
164
|
-
version: '
|
164
|
+
version: '9'
|
165
165
|
description: ActiveRecord::JSONValidator makes it easy to validate JSON attributes
|
166
166
|
with a JSON schema.
|
167
167
|
email:
|
@@ -170,18 +170,16 @@ executables: []
|
|
170
170
|
extensions: []
|
171
171
|
extra_rdoc_files: []
|
172
172
|
files:
|
173
|
+
- ".github/workflows/ci.yaml"
|
173
174
|
- ".gitignore"
|
174
175
|
- ".rspec"
|
175
176
|
- ".rubocop.yml"
|
176
|
-
- ".travis.yml"
|
177
177
|
- Gemfile
|
178
178
|
- LICENSE.md
|
179
179
|
- README.md
|
180
180
|
- Rakefile
|
181
181
|
- activerecord_json_validator.gemspec
|
182
|
-
-
|
183
|
-
- gemfiles/Gemfile.activerecord-5.0.x
|
184
|
-
- gemfiles/Gemfile.activerecord-6.0.x
|
182
|
+
- docker-compose.yml
|
185
183
|
- lib/active_record/json_validator/validator.rb
|
186
184
|
- lib/active_record/json_validator/version.rb
|
187
185
|
- lib/activerecord_json_validator.rb
|
@@ -196,7 +194,7 @@ homepage: https://github.com/mirego/activerecord_json_validator
|
|
196
194
|
licenses:
|
197
195
|
- BSD 3-Clause
|
198
196
|
metadata: {}
|
199
|
-
post_install_message:
|
197
|
+
post_install_message:
|
200
198
|
rdoc_options: []
|
201
199
|
require_paths:
|
202
200
|
- lib
|
@@ -211,8 +209,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
211
209
|
- !ruby/object:Gem::Version
|
212
210
|
version: '0'
|
213
211
|
requirements: []
|
214
|
-
rubygems_version: 3.
|
215
|
-
signing_key:
|
212
|
+
rubygems_version: 3.5.11
|
213
|
+
signing_key:
|
216
214
|
specification_version: 4
|
217
215
|
summary: ActiveRecord::JSONValidator makes it easy to validate JSON attributes with
|
218
216
|
a JSON schema.
|
data/.travis.yml
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
|
3
|
-
rvm:
|
4
|
-
- 2.4.6
|
5
|
-
- 2.6.3
|
6
|
-
|
7
|
-
gemfile:
|
8
|
-
- gemfiles/Gemfile.activerecord-4.2.x
|
9
|
-
- gemfiles/Gemfile.activerecord-5.0.x
|
10
|
-
- gemfiles/Gemfile.activerecord-6.0.x
|
11
|
-
|
12
|
-
matrix:
|
13
|
-
exclude:
|
14
|
-
- gemfile: gemfiles/Gemfile.activerecord-6.0.x
|
15
|
-
rvm: 2.4.6
|
16
|
-
|
17
|
-
sudo: false
|
18
|
-
|
19
|
-
services:
|
20
|
-
- mysql
|
21
|
-
- postgresql
|
22
|
-
|
23
|
-
env:
|
24
|
-
- DB_ADAPTER=mysql2
|
25
|
-
- DB_ADAPTER=postgresql
|
26
|
-
|
27
|
-
before_script:
|
28
|
-
- mysql -e 'create database activerecord_json_validator_test;'
|
29
|
-
- psql -c 'create database activerecord_json_validator_test;' -U postgres
|
30
|
-
|
31
|
-
script:
|
32
|
-
- 'echo "Checking code style" && bundle exec phare'
|
33
|
-
- 'echo "Running tests" && bundle exec rake spec'
|