README.md in hydra-validations-0.4.0 vs README.md in hydra-validations-0.5.0
- old
+ new
@@ -1,18 +1,20 @@
hydra-validations
=======================
-Custom validators for Hydra applications, based on ActiveModel::Validations.
-
[](https://travis-ci.org/projecthydra-labs/hydra-validations)
[](http://badge.fury.io/rb/hydra-validations)
+Custom validators based on ActiveModel::Validations, developed for Hydra applications.
+
## Dependencies
* Ruby >= 1.9.3
* ActiveModel 4.x
+ActiveFedora 7.x is a run-time dependency of the UniquenessValidator only, and is not provided by this gem.
+
## Installation
Include in your Gemfile:
```ruby
@@ -23,21 +25,113 @@
```sh
bundle install
```
+## Why?
+
+- Metadata values are (often) arrays (XML or RDF).
+- ActiveModel validators don't distinguish between array values and scalar values.
+- We want validators that validate each member of an array value.
+
+### Example: PORO with ActiveModel::Validations
+
+```ruby
+class Validatable
+ include ActiveModel::Validations
+ attr_accessor :type
+ validates_inclusion_of :type, in: %w(text image audio video)
+end
+
+>> record = Validatable.new
+>> record.type = "text"
+>> record.valid?
+=> true
+
+>> record.type = ["text", "image"]
+>> record.valid?
+=> false # not what we want :(
+
+# now let's add Hydra::Validations ...
+
+class CorrectlyValidatable
+ include ActiveModel::Validations
+ include Hydra::Validations
+ attr_accessor :type
+ validates_inclusion_of :type, in: %w(text image audio video)
+end
+
+>> record = CorrectlyValidatable.new
+>> record.type = ["text", "image"]
+>> record.valid?
+=> true # Yay!
+
+>> record.type = ["text", "newspaper"]
+>> record.valid?
+=> false
+
+# tailors error message to specific invalid array value
+>> puts record.errors.full_messages
+Type value "newspaper" is not included in the list
+```
+
+### Example: ActiveTriples::Resource
+
+```ruby
+class MyResource < ActiveTriples::Resource
+ # ActiveTriples::Resource includes ActiveModel::Validations
+ property :type, predicate: RDF::DC.type
+ validates_inclusion_of :type, in: %w(text image audio video)
+end
+
+>> resource = MyResource.new
+>> resource.type = "text"
+>> resource.valid?
+=> false # Huh? Oh yeah, resource.type is actually an array ...
+>> resource.type
+=> ["text"]
+
+class YourResource < ActiveTriples::Resource
+ include Hydra::Validations
+ property :type, predicate: RDF::DC.type
+ validates_inclusion_of :type, in: %w(text image audio video)
+end
+
+>> resource = YourResource.new
+>> resource.type = "text"
+=> "text"
+>> resource.valid?
+=> true # Yay!
+
+>> resource.type << "newspapers"
+>> resource.type
+=> ["text", "newspapers"]
+>> resource.valid?
+=> false
+>> puts resource.errors.full_messages
+Type value "newspapers" is not included in the list
+```
+
## EnumerableBehavior Mixin
+`Hydra::Validations::EnumerableBehavior` is a mixin for an `ActiveModel::EachValidator` that validates each member of an enumerable value. See the [FormatValidator](#formatvalidator) and [InclusionValidator](#inclusionvalidator) below for examples.
+
+EnumerableBehavior overrides `validate_each(record, attribute, value)` calling `super(record, attribute, member)` for each member of an enumerable value (i.e., responds to `:each`). The module "fixes" any error messages to include the specific member that failed validation -- for example, `"is invalid"` becomes `"value \"foo1\" is invalid"`, so the full message `"Identifier is invalid"` becomes `"Identifer value \"foo1\" is invalid"`.
+
+**allow_nil, allow_blank, and allow_empty**
+
+With EnumerableBehavior, validation of non-enumerables is unchanged (`validate_each` simply returns `super`); however, the validator options `allow_nil` and `allow_blank` *apply only to the original value* (the enumerable) and not to its members. For example, the value `[""]` with the option `allow_blank: true` will *not* bypass validation. As a result, empty enumerables will fail validation unless `allow_blank` is true or the special enumerable option `allow_empty` is true (that option does not bypass validation for non-enumerables that respond to `:empty?`, e.g., empty strings).
+
## Validators
See also the source code and spec tests.
### FormatValidator
-Extends the ActiveModel::Validations::FormatValidator, adding EnumerableBehavior.
+Extends `ActiveModel::Validations::FormatValidator`, adding EnumerableBehavior.
-See documentation for ActiveModel::Validations::FormatValidator for usage and options.
+See `ActiveModel::Validations::FormatValidator` for usage and options.
```ruby
class FormatValidatable
include ActiveModel::Validations # required if not already included in class
include Hydra::Validations
@@ -45,31 +139,38 @@
validates :field, format: { with: /\A[[:alpha:]]+\Z/ }
# ... or
# validates_format_of :field, with: /\A[[:alpha:]]+\Z/
end
-> v = FormatValidatable.new
- => #<FormatValidatable:0x007ffc55175300>
-> v.field = ["foo", "bar"]
- => ["foo", "bar"]
-> v.valid?
- => true
-> v.field = ["foo1", "bar2"]
- => ["foo1", "bar2"]
-> v.valid?
- => false
-> v.errors[:field]
- => ["value \"foo1\" is invalid", "value \"bar2\" is invalid"]
-> v.errors.full_messages
- => ["Field value \"foo1\" is invalid", "Field value \"bar2\" is invalid"]
+>> record = FormatValidatable.new
+=> #<FormatValidatable:0x007fe3cc0ece70>
+>> record.field = "foo"
+=> "foo"
+>> record.valid?
+=> true
+>> record.field = ["foo"]
+=> ["foo"]
+>> record.valid?
+=> true
+>> record.field = ["foo", "bar"]
+=> ["foo", "bar"]
+>> record.valid?
+=> true
+>> record.field = ["foo1", "bar2"]
+=> ["foo1", "bar2"]
+>> record.valid?
+=> false
+>> puts record.errors.full_messages
+Field value "foo1" is invalid
+Field value "bar2" is invalid
```
### InclusionValidator
-Extends ActiveModel::Validations::InclusionValidator, adding EnumerableBehavior.
+Extends `ActiveModel::Validations::InclusionValidator`, adding EnumerableBehavior.
-See documentation for ActiveModel::Validations::InclusionValidator for usage and options.
+See `ActiveModel::Validations::InclusionValidator` for usage and options.
```ruby
class InclusionValidatable
include ActiveModel::Validations # required if not already included in class
include Hydra::Validations
@@ -77,35 +178,45 @@
validates :field, inclusion: { in: ["foo", "bar", "baz"] }
# or using helper method ...
# validates_inclusion_of :field, in: ["foo", "bar", "baz"]
end
-> v = InclusionValidatable.new
- => #<InclusionValidatable:0x007ffc53079318>
-> v.field = ["foo", "bar"]
- => ["foo", "bar"]
-> v.valid?
- => true
-> v.field = ["foo", "bar", "spam", "eggs"]
- => ["foo", "bar", "spam", "eggs"]
-> v.valid?
- => false
-> v.errors[:field]
- => ["value \"spam\" is not included in the list", "value \"eggs\" is not included in the list"]
-> v.errors.full_messages
- => ["Field value \"spam\" is not included in the list", "Field value \"eggs\" is not included in the list"]
+>> record = InclusionValidatable.new
+=> #<InclusionValidatable:0x007fe3cbc40098>
+>> record.field = "foo"
+=> "foo"
+>> record.valid?
+=> true
+>> record.field = "foo1"
+=> "foo1"
+>> record.valid?
+=> false
+>> record.field = ["foo"]
+=> ["foo"]
+>> record.valid?
+=> true
+>> record.field = ["foo", "bar"]
+=> ["foo", "bar"]
+>> record.valid?
+=> true
+>> record.field = ["foo", "bar1", "baz"]
+=> ["foo", "bar1", "baz"]
+>> record.valid?
+=> false
+>> puts record.errors.full_messages
+Field value "bar1" is not included in the list
```
### UniquenessValidator
Validates the uniqueness of an attribute based on a Solr index query.
Intended for ActiveFedora 7.x.
```ruby
class UniquenessValidatable < ActiveFedora::Base
- include Hydra::Validations
+ include Hydra::Validations # ActiveFedora::Base includes ActiveModel::Validations
has_metadata name: 'descMetadata', type: ActiveFedora::QualifiedDublinCoreDatastream
has_attributes :title, datastream: 'descMetadata', multiple: false
# Can use with multi-value attributes, but single cardinality is required.
has_attributes :source, datastream: 'descMetadata', multiple: true
validates :source, uniqueness: { solr_name: "source_ssim" }
@@ -116,13 +227,13 @@
### CardinalityValidator
Validates the cardinality of the attribute value.
-CardinalityValidator is a subclass of ActiveModel::Validations::LengthValidator which
+CardinalityValidator extends `ActiveModel::Validations::LengthValidator` and
"tokenizes" values with `Array.wrap(value)`. The "cardinality" of the value
-is the length the array. Hence,
+is therefore the length the array. Accordingly,
- `nil` and empty enumerables have cardinality 0
- scalar values (including empty string) have cardinality 1.
CardinalityValidator customizes the `:wrong_length`, `:too_short` and `:too_long` messages
@@ -139,22 +250,22 @@
# validates_cardinality_of :field, is: 1
# or, for single cardinality (same as above) ...
# validates_single_cardinality_of :field
end
-> CardinalityValidatable.validators
- => [#<Hydra::Validations::CardinalityValidator:0x007fb91d1e9460 @attributes=[:field], @options={:is=>1}>]
-> v = Validatable.new
- => #<Validatable:0x007fb91d1c9188>
-> v.field = "foo"
- => "foo"
-> v.valid?
- => true
-> v.field = ["foo"]
- => ["foo"]
-> v.valid?
- => true
-> v.field = ["foo", "bar"]
- => ["foo", "bar"]
-> v.valid?
- => false
+>> record = CardinalityValidatable.new
+=> #<CardinalityValidatable:0x007fe3cbc632c8>
+>> record.field = "foo"
+=> "foo"
+>> record.valid?
+=> true
+>> record.field = ["foo"]
+=> ["foo"]
+>> record.valid?
+=> true
+>> record.field = ["foo", "bar"]
+=> ["foo", "bar"]
+>> record.valid?
+=> false
+>> puts record.errors.full_messages
+Field has the wrong cardinality (should have 1 value(s))
```