Skip to content

Commit

Permalink
Add support for contextual schema in validation
Browse files Browse the repository at this point in the history
  • Loading branch information
remi committed Jan 26, 2015
1 parent 63116ee commit ec14803
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 11 deletions.
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,86 @@ user.valid? # => false
user.profile_invalid_json # => '{invalid JSON":}'
```

#### Options

| Option | Description
|------------|-----------------------------------------------------
| `:schema` | The JSON schema to validate the data against (see **JSON schema option** section)
| `:message` | The ActiveRecord message added to the record errors (default: `:invalid_json`)

##### JSON schema option

You can specify four kinds of value for the `:schema` option.

###### A path to a file containing a JSON schema

```ruby
class User < ActiveRecord::Base
# Constants
PROFILE_JSON_SCHEMA = Rails.root.join('config', 'schemas', 'profile.json_schema').to_s

# Validations
validates :profile, presence: true, json: { schema: PROFILE_JSON_SCHEMA }
end
```

###### A Ruby `Hash` representing a JSON schema

```ruby
class User < ActiveRecord::Base
# Constants
PROFILE_JSON_SCHEMA = {
type: 'object',
:'$schema' => 'http://json-schema.org/draft-03/schema',
properties: {
city: { type: 'string', required: false },
country: { type: 'string', required: true }
}
}

# Validations
validates :profile, presence: true, json: { schema: PROFILE_JSON_SCHEMA }
end
```

###### A plain JSON schema as a Ruby `String`

```ruby
class User < ActiveRecord::Base
# Constants
PROFILE_JSON_SCHEMA = '{
"type": "object",
"$schema": "http://json-schema.org/draft-03/schema",
"properties": {
"city": { "type": "string", "required": false },
"country": { "type": "string", "required": true }
}
}'

# Validations
validates :profile, presence: true, json: { schema: PROFILE_JSON_SCHEMA }
end
```

###### A lambda that will get evaluated in the context of the validated record

The lambda must return a valid value for the `:schema` option (file path, JSON `String` or Ruby `Hash`).

```ruby
class User < ActiveRecord::Base
# Constants
PROFILE_REGULAR_JSON_SCHEMA = Rails.root.join('config', 'schemas', 'profile.json_schema').to_s
PROFILE_ADMIN_JSON_SCHEMA = Rails.root.join('config', 'schemas', 'profile_admin.json_schema').to_s

# Validations
validates :profile, presence: true, json: { schema: lambda { dynamic_profile_schema } }

def dynamic_profile_schema
admin? ? PROFILE_ADMIN_JSON_SCHEMA : PROFILE_REGULAR_JSON_SCHEMA
end
end
```

## License

`ActiveRecord::JSONValidator` is © 2013-2015 [Mirego](http://www.mirego.com) and may be freely distributed under the [New BSD license](http://opensource.org/licenses/BSD-3-Clause). See the [`LICENSE.md`](https://github.com/mirego/activerecord_json_validator/blob/master/LICENSE.md) file.
Expand Down
10 changes: 9 additions & 1 deletion lib/active_record/json_validator/validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def validate_each(record, attribute, value)
end

# Validate value with JSON::Validator
errors = ::JSON::Validator.fully_validate(options.fetch(:schema), json_value)
schema = fetch_schema_for_record(record)
errors = ::JSON::Validator.fully_validate(schema, json_value)

# Everything is good if we don’t have any errors and we got valid JSON value
return true if errors.empty? && record.send(:"#{attribute}_invalid_json").blank?
Expand Down Expand Up @@ -60,4 +61,11 @@ def inject_setter_method(klass, attributes)
RUBY
end
end

def fetch_schema_for_record(record)
schema = options.fetch(:schema)
return schema unless schema.is_a?(Proc)

record.instance_exec(&schema)
end
end
48 changes: 38 additions & 10 deletions spec/json_validator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,36 @@
end
end

json_schema = schema
spawn_model :User do
schema = {
type: 'object',
:'$schema' => 'http://json-schema.org/draft-03/schema',
properties: {
city: { type: 'string', required: false },
country: { type: 'string', required: true }
}
}

serialize :profile, JSON
validates :name, presence: true
validates :profile, presence: true, json: { schema: schema }
validates :profile, presence: true, json: { schema: json_schema }

def dynamic_json_schema
{
type: 'object',
:'$schema' => 'http://json-schema.org/draft-03/schema',
properties: {
foo: { type: 'string', required: false },
bar: { type: 'string', required: true }
}
}
end
end
end

let(:user) { User.create(attributes) }
let(:schema) do
{
type: 'object',
:'$schema' => 'http://json-schema.org/draft-03/schema',
properties: {
city: { type: 'string', required: false },
country: { type: 'string', required: true }
}
}
end

context 'with blank JSON value' do
let(:attributes) { { name: 'Samuel Garneau', profile: {} } }
Expand Down Expand Up @@ -65,4 +78,19 @@
expect(user.profile_invalid_json).to eql('foo:}bar')
end
end

context 'with lambda schema option' do
# The dynamic schema makes `country` and `city` keys mandatory
let(:schema) { lambda { dynamic_json_schema } }

context 'with valid JSON value' do
let(:attributes) { { name: 'Samuel Garneau', profile: { foo: 'bar', bar: 'foo' } } }
it { expect(user).to be_valid }
end

context 'with invalid JSON value' do
let(:attributes) { { name: 'Samuel Garneau', profile: {} } }
it { expect(user).not_to be_valid }
end
end
end

0 comments on commit ec14803

Please sign in to comment.