
Security News
New Website “Is It Really FOSS?” Tracks Transparency in Open Source Distribution Models
A new site reviews software projects to reveal if they’re truly FOSS, making complex licensing and distribution models easy to understand.
Validations mixins for objects
Lotus::Validations supports Ruby (MRI) 2+, JRuby 9k+ & Rubinius 2.3+
Add this line to your application's Gemfile:
gem 'lotus-validations'
And then execute:
$ bundle
Or install it yourself as:
$ gem install lotus-validations
Lotus::Validations
is a set of lightweight validations for Ruby objects.
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 # => "Luca"
person.email # => "me@example.org"
person.age # => raises NoMethodError because `:age` wasn't defined as attribute.
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? # < true
Person.new(name: '').valid? # < true
Person.new(skills: '').valid? # < true
Person.new(skills: ['ruby', 'lotus']).valid? # < true
Person.new(skills: []).valid? # < false
Person.new(keys: {}).valid? # < false
Person.new(keys: {a: :b}, skills: []).valid? # < false
If you want to disable this behaviour, please, refer to presence.
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
# Custom initializer
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 # => "Luca"
person.email # => "me@example.org"
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.
If a Ruby class is passed to the :type
option, the given value is coerced, accordingly.
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 # => 23
Allowed types are:
Array
BigDecimal
Boolean
Date
DateTime
Float
Hash
Integer
Pathname
Set
String
Symbol
Time
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 # => #<FavNumber:0x007ffc644bba00 @number="23">
person.date # => this raises an error, because BirthDate#initialize doesn't accept any arg
Each attribute definition can receive a set of options to define one or more validations.
Validations are triggered when you invoke #valid?
.
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? # => true
signup = Signup.new(terms_of_service: 'true')
signup.valid? # => true
signup = Signup.new(terms_of_service: '')
signup.valid? # => false
signup = Signup.new(terms_of_service: '0')
signup.valid? # => false
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? # => true
signup = Signup.new(password: 'secret', password_confirmation: 'x')
signup.valid? # => false
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? # => true
signup = Signup.new(music: 'pop')
signup.valid? # => false
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? # => true
signup = Signup.new(name: '23')
signup.valid? # => false
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? # => true
signup = Signup.new(age: 17)
signup.valid? # => false
signup = Signup.new(fav_number: 23)
signup.valid? # => true
signup = Signup.new(fav_number: 8)
signup.valid? # => false
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? # => true
signup = Signup.new(name: '')
signup.valid? # => false
signup = Signup.new(name: nil)
signup.valid? # => false
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 # exact match
attribute :password, size: 8..64 # range
attribute :avatar, size: 1..(5 * MEGABYTE)
end
signup = Signup.new(password: 'a-very-long-password')
signup.valid? # => true
signup = Signup.new(password: 'short')
signup.valid? # => false
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 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 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? # => false
Bulk operations on errors are guaranteed by #each
.
This method yields a flattened collection of errors.
validator.errors.each do |error|
error.name
# => on the first iteration it returns "full_name"
# => the second time it returns "address.street" and so on..
end
Errors for a specific attribute can be accessed via #for
.
error = validator.errors.for('full_name').first
error.name # => "full_name"
error.attribute_name # => "full_name"
error = validator.errors.for('address.street').first
error.name # => "address.street"
error.attribute_name # => "street"
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
# We validate only the presence here
attribute :password, presence: true
end
module CommonValidations
include EmailValidations
include PasswordValidations
end
# A valid signup requires:
# * name (presence)
# * email (presence and format)
# * password (presence and confirmation)
class Signup
include NameValidations
include CommonValidations
# We decorate PasswordValidations behavior, by requiring the confirmation too.
# This additional validation is active only in this case.
attribute :password, confirmation: true
end
# A valid signin requires:
# * email (presence)
# * password (presence)
class Signin
include CommonValidations
end
# A valid "forgot password" requires:
# * email (presence)
class ForgotPassword
include EmailValidations
end
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
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? # => true
signup = Signup.new(email: '', age: 17)
signup.valid? # => false
signup.errors
# => #<Lotus::Validations::Errors:0x007fe00ced9b78
# @errors={
# :email=>[
# #<Lotus::Validations::Error:0x007fe00cee3290 @attribute=:email, @validation=:presence, @expected=true, @actual="">,
# #<Lotus::Validations::Error:0x007fe00cee31f0 @attribute=:email, @validation=:format, @expected=/\A(.*)@(.*)\.(.*)\z/, @actual="">
# ],
# :age=>[
# #<Lotus::Validations::Error:0x007fe00cee30d8 @attribute=:age, @validation=:size, @expected=18..99, @actual=17>
# ]
# }>
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? # => true
product.name # => "Book"
product.price # => 100
git checkout -b my-new-feature
)git commit -am 'Add some feature'
)git push origin my-new-feature
)Copyright © 2014-2016 Luca Guidi – Released under MIT License
FAQs
Unknown package
We found that lotus-validations demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
A new site reviews software projects to reveal if they’re truly FOSS, making complex licensing and distribution models easy to understand.
Security News
Astral unveils pyx, a Python-native package registry in beta, designed to speed installs, enhance security, and integrate deeply with uv.
Security News
The Latio podcast explores how static and runtime reachability help teams prioritize exploitable vulnerabilities and streamline AppSec workflows.