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. - [![Build Status](https://travis-ci.org/projecthydra-labs/hydra-validations.svg?branch=master)](https://travis-ci.org/projecthydra-labs/hydra-validations) [![Gem Version](https://badge.fury.io/rb/hydra-validations.svg)](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)) ```