Lotus::Validations
Validations mixins for objects
Status
Contact
Rubies
Lotus::Validations supports Ruby (MRI) 2+, JRuby 9k+ & Rubinius 2.3+
Installation
Add this line to your application's Gemfile:
gem 'lotus-validations'
And then execute:
$ bundle
Or install it yourself as:
$ gem install lotus-validations
Usage
Lotus::Validations
is a set of lightweight validations for Ruby objects.
Attributes
The framework allows you to define attributes for each object.
It defines an initializer, whose attributes can be passed as a hash.
All unknown values are ignored, which is useful for whitelisting attributes.
require 'lotus/validations'
class Person
include Lotus::Validations
attribute :name, presence: true
attribute :email, presence: true
end
person = Person.new(name: 'Luca', email: 'me@example.org', age: 32)
person.name
person.email
person.age
Blank Values
The framework will treat as valid any blank attributes, without presence
, for both format
and size
predicates.
require 'lotus/validations'
class Person
include Lotus::Validations
attribute :name, type: String, size: 5..45
attribute :email, type: String, size: 20..80, format: /@/
attribute :skills, type: Array, size: 1..3
attribute :keys, type: Hash, size: 1..3
end
Person.new.valid?
Person.new(name: '').valid?
Person.new(skills: '').valid?
Person.new(skills: ['ruby', 'lotus']).valid?
Person.new(skills: []).valid?
Person.new(keys: {}).valid?
Person.new(keys: {a: :b}, skills: []).valid?
If you want to disable this behaviour, please, refer to presence.
Validations
If you prefer Lotus::Validations to only define validations, but not attributes,
you can use the following alternative syntax.
require 'lotus/validations'
class Person
include Lotus::Validations
attr_accessor :name, :email
def initialize(attributes = {})
@name, @email = attributes.values_at(:name, :email)
end
validates :name, presence: true
validates :email, presence: true
end
person = Person.new(name: 'Luca', email: 'me@example.org')
person.name
person.email
This is a bit more verbose, but offers a great level of flexibility for your
Ruby objects. It also allows to use Lotus::Validations in combination with
other frameworks.
Coercions
If a Ruby class is passed to the :type
option, the given value is coerced, accordingly.
Standard coercions
require 'lotus/validations'
class Person
include Lotus::Validations
attribute :fav_number, type: Integer
end
person = Person.new(fav_number: '23')
person.valid?
person.fav_number
Allowed types are:
Array
BigDecimal
Boolean
Date
DateTime
Float
Hash
Integer
Pathname
Set
String
Symbol
Time
Custom coercions
If a user defined class is specified, it can be freely used for coercion purposes.
The only limitation is that the constructor should have arity of 1.
require 'lotus/validations'
class FavNumber
def initialize(number)
@number = number
end
end
class BirthDate
end
class Person
include Lotus::Validations
attribute :fav_number, type: FavNumber
attribute :date, type: BirthDate
end
person = Person.new(fav_number: '23', date: 'Oct 23, 2014')
person.valid?
person.fav_number
person.date
Validations
Each attribute definition can receive a set of options to define one or more
validations.
Validations are triggered when you invoke #valid?
.
Acceptance
An attribute is valid if its value is truthy.
require 'lotus/validations'
class Signup
include Lotus::Validations
attribute :terms_of_service, acceptance: true
end
signup = Signup.new(terms_of_service: '1')
signup.valid?
signup = Signup.new(terms_of_service: 'true')
signup.valid?
signup = Signup.new(terms_of_service: '')
signup.valid?
signup = Signup.new(terms_of_service: '0')
signup.valid?
Confirmation
An attribute is valid if its value and the value of a corresponding attribute
is valid.
By convention, if you have a password
attribute, the validation looks for password_confirmation
.
require 'lotus/validations'
class Signup
include Lotus::Validations
attribute :password, confirmation: true
end
signup = Signup.new(password: 'secret', password_confirmation: 'secret')
signup.valid?
signup = Signup.new(password: 'secret', password_confirmation: 'x')
signup.valid?
Exclusion
An attribute is valid, if the value isn't excluded from the value described by
the validator.
The validator value can be anything that responds to #include?
.
In Ruby, this includes most of the core objects: String
, Enumerable
(Array
, Hash
,
Range
, Set
).
See also Inclusion.
require 'lotus/validations'
class Signup
include Lotus::Validations
attribute :music, exclusion: ['pop']
end
signup = Signup.new(music: 'rock')
signup.valid?
signup = Signup.new(music: 'pop')
signup.valid?
Format
An attribute is valid if it matches the given Regular Expression.
require 'lotus/validations'
class Signup
include Lotus::Validations
attribute :name, format: /\A[a-zA-Z]+\z/
end
signup = Signup.new(name: 'Luca')
signup.valid?
signup = Signup.new(name: '23')
signup.valid?
Inclusion
An attribute is valid, if the value provided is included in the validator's
value.
The validator value can be anything that responds to #include?
.
In Ruby, this includes most of the core objects: like String
, Enumerable
(Array
, Hash
,
Range
, Set
).
See also Exclusion.
require 'prime'
require 'lotus/validations'
class PrimeNumbers
def initialize(limit)
@numbers = Prime.each(limit).to_a
end
def include?(number)
@numbers.include?(number)
end
end
class Signup
include Lotus::Validations
attribute :age, inclusion: 18..99
attribute :fav_number, inclusion: PrimeNumbers.new(100)
end
signup = Signup.new(age: 32)
signup.valid?
signup = Signup.new(age: 17)
signup.valid?
signup = Signup.new(fav_number: 23)
signup.valid?
signup = Signup.new(fav_number: 8)
signup.valid?
Presence
An attribute is valid if present.
require 'lotus/validations'
class Signup
include Lotus::Validations
attribute :name, presence: true
end
signup = Signup.new(name: 'Luca')
signup.valid?
signup = Signup.new(name: '')
signup.valid?
signup = Signup.new(name: nil)
signup.valid?
Size
An attribute is valid if its #size
falls within the described value.
require 'lotus/validations'
class Signup
MEGABYTE = 1024 ** 2
include Lotus::Validations
attribute :ssn, size: 11
attribute :password, size: 8..64
attribute :avatar, size: 1..(5 * MEGABYTE)
end
signup = Signup.new(password: 'a-very-long-password')
signup.valid?
signup = Signup.new(password: 'short')
signup.valid?
Note that in the example above you are able to validate the weight of the file,
because Ruby's File
and Tempfile
both respond to #size
.
Uniqueness
Uniqueness validations aren't implemented because this library doesn't deal with persistence.
The other reason is that this isn't an effective way to ensure uniqueness of a value in a database.
Please read more at: The Perils of Uniqueness Validations.
Nested validations
Nested validations are handled with a nested block syntax.
class ShippingDetails
include Lotus::Validations
attribute :full_name, presence: true
attribute :address do
attribute :street, presence: true
attribute :city, presence: true
attribute :country, presence: true
attribute :postal_code, presence: true, format: /.../
end
end
validator = ShippingDetails.new
validator.valid?
Bulk operations on errors are guaranteed by #each
.
This method yields a flattened collection of errors.
validator.errors.each do |error|
error.name
end
Errors for a specific attribute can be accessed via #for
.
error = validator.errors.for('full_name').first
error.name
error.attribute_name
error = validator.errors.for('address.street').first
error.name
error.attribute_name
Composable validations
Validations can be reused via composition:
require 'lotus/validations'
module NameValidations
include Lotus::Validations
attribute :name, presence: true
end
module EmailValidations
include Lotus::Validations
attribute :email, presence: true, format: /.../
end
module PasswordValidations
include Lotus::Validations
attribute :password, presence: true
end
module CommonValidations
include EmailValidations
include PasswordValidations
end
class Signup
include NameValidations
include CommonValidations
attribute :password, confirmation: true
end
class Signin
include CommonValidations
end
class ForgotPassword
include EmailValidations
end
Complete example
require 'lotus/validations'
class Signup
include Lotus::Validations
attribute :first_name, presence: true
attribute :last_name, presence: true
attribute :email, presence: true, format: /\A(.*)@(.*)\.(.*)\z/
attribute :password, presence: true, confirmation: true, size: 8..64
end
Errors
When you invoke #valid?
, validation errors are available at #errors
.
It's a set of errors grouped by attribute. Each error contains the name of the
invalid attribute, the failed validation, the expected value, and the current one.
require 'lotus/validations'
class Signup
include Lotus::Validations
attribute :email, presence: true, format: /\A(.*)@(.*)\.(.*)\z/
attribute :age, size: 18..99
end
signup = Signup.new(email: 'user@example.org')
signup.valid?
signup = Signup.new(email: '', age: 17)
signup.valid?
signup.errors
Lotus::Entity
Integration with Lotus::Entity
is straight forward.
require 'lotus/model'
require 'lotus/validations'
class Product
include Lotus::Entity
include Lotus::Validations
attribute :name, type: String, presence: true
attribute :price, type: Integer, presence: true
end
product = Product.new(name: 'Book', price: '100')
product.valid?
product.name
product.price
Contributing
- Fork it ( https://github.com/lotus/lotus-validations/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
Copyright
Copyright © 2014-2016 Luca Guidi – Released under MIT License