Context based validations for model instances.
If it is a bug please open an issue on GitHub.
In your Gemfile
gem 'context_validations'
You can either mixin the modules on a case-by-case basis or make the changes global:
# model
class User < ActiveModel::Base
include ContextValidations::Model
end
# controller
class UsersController < ApplicationController
include ContextValidations::Controller
end
Create an initializer: config/initializers/context_validations.rb
class ActiveRecord::Base
include ContextValidations::Model
end
class ActionController::Base
include ContextValidations::Controller
end
class UserController < ApplicationController
include ContextValidations::Controller
def create
@user = User.new(user_params)
@user.validations = validations(:create)
if @user.save
# happy path
else
# sad path
end
end
private
def create_validations
validates :password, :presence => true
end
def base_validations
validates :first_name, :last_name, :presence => true
validates :password, :confirmation => true
validates :email, :uniqueness => true, :format => EmailFormat
end
end
class User < ActiveRecord::Base
include ContextValidations::Model
end
In the above example we just call validations
and pass the context. We
set the result to #validations=
on the model.
While this does introduce some complexity into our controllers it frees us from the mental gymnastics of conditional validators and state flags.
The corresponding #{context}_validations
method, in this case
create_validations
defines the validations that will be used. Call the
#validates
method just like you would in model validations, the API is
identical.
A #base_validations
method is always called prior to
#{context}_validations
that will allow you to group together common
validations. The result of these methods appends onto a @validations
array.
If you are using Ruby 2.0+
you can use implicit contexts:
def create
@user = User.new(user_params)
# Notice we are only calling the #validations method and not passing
# context. In this example the context is derived from the calling
# method `create`
@user.validations = validations
end
private
def create_validations
...
end
When the ContextValidations::Model
module is mixed into the model all
of the validation callbacks are removed from that model.
Because we are setting the validations on the instance you should not
use ActiveRecord::Base.create
as we do not want to allow the
validations to be set via mass-assignment. This does introduce an extra
step in some places but it shouldn't be that big of a deal.
Currently only MiniTest
is supported. We are open to pull requests for supporting additional test frameworks but we only work with MiniTest
so we probably won't go out of the way to support something we're not using.
We highly recommend using ValidAttribute to test your validations. The following example is done using
ValidAttribute
.
In /test/test_helper.rb
:
require 'context_validations/minitest'
You are given access to a #validations_for(:action_name)
method. You should pass the action in your
controller that is the context of the validations and use the #validations=
setter on the model.
This is a common example of how to test:
require 'test_helper'
describe UserController do
context 'create' do
subject { User.new(:password => 'password', :validations => validations_for(:create)) }
it { must have_valid(:name).when('Brian Cardarella') }
it { wont have_valid(:name).when(nil, '') }
end
end
The ClientSideValidations gem is fully supported.
We are very thankful for the many contributors
This gem follows Semantic Versioning
Please do! We are always looking to improve this gem. Please see our Contribution Guidelines on how to properly submit issues and pull requests.
DockYard, LLC © 2013