dry-validation
History
Rails 3
mass-assignment protection
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# email :string
# name :string
# admin :boolean default(FALSE)
#
class User < ActiveRecor::Base
attr_accessible :email, :name
end
class UsersController < ApplicationController
def create
# { "user" => { "email" => "user@example.com", "name" => "Jon Snow", "admin" => true }
User.create(params[:user])
end
end
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# email :string
# name :string
# admin :boolean default(FALSE)
#
class User < ActiveRecor::Base
attr_accessible :email, :name
attr_accessible :email, :name, :admin, as: :admin
end
class Admin::UsersController < ApplicationController
def create
# { "user" => { "email" => "user@example.com", "name" => "Jon Snow", "admin" => true }
User.create(params[:user], as: :admin)
end
end
irb(main):001:0> user = User.create(email: "user@example.com", name: "Jon Snow", admin: true)
irb(main):002:0> user.admin
falseFormObject for conditional changes
2011 - Virtus
Rails 4
strong parameters
class PeopleController < ActionController::Base
# This will raise an ActiveModel::ForbiddenAttributes exception
# because it's using mass assignment without an explicit permit step.
def create
Person.create(params[:person])
end
# This will pass with flying colors as long as there's a person key
# in the parameters, otherwise it'll raise an ActionController::ParameterMissing
# exception, which will get caught by ActionController::Base and turned
# into that 400 Bad Request reply.
def update
person = current_account.people.find(params[:id])
person.update_attributes!(person_params)
redirect_to person
end
private
# Using a private method to encapsulate the permissible parameters
# is just a good pattern since you'll be able to reuse the same permit
# list between create and update. Also, you can specialize this method
# with per-user checking of permissible attributes.
def person_params
params.require(:person).permit(:name, :age)
end
end# nested attributes
params.permit(:name, {emails: []}, friends: [:name, { family: [:name], hobbies: []}])
# outside controller
raw_parameters = { :email => "john@example.com", :name => "John", :admin => true }
parameters = ActionController::Parameters.new(raw_parameters)
user = User.create(parameters.permit(:name, :email))Issues
- Data type validation
- Hard or impossible to define complex rules
- Coupled with ActiveRecord
- ...
dry-validation
class MyContract < Dry::Validation::Contract
params do
required(:id).filled(:integer)
required(:name).filled(:string)
end
end
MyContract.new.call("id" => 1, "name" => "Jon Snow")class MyContract < Dry::Validation::Contract
params do
required(:id).filled(:integer)
required(:name).filled(:string)
end
end
MyContract.new.call("id" => 1, "name" => "Jon Snow")module Types
include Dry.Types
Multiline = Types::Array.of(Types::String).constructor { |v|
v.split(/\r?\n/)
}
end
class MyContract < Dry::Validation::Contract
params do
required(:textarea).filled(Types::Multiline)
end
end
# ideas: UUID, ID, CountryCode, PostCodeDry::Validation.Contract do
register_macro(:qux) do
if values[:bar]
key.failure("do not repeat bar") if value == values[:bar]
end
end
params do
optional(:foo).hash do
required(:bar).value(:integer)
required(:baz).maybe(:string)
end
end
rule("foo.bar") do
key.failed("invalid") if value > 3
end
rule("foo.baz").validate(:qux)
endDry::Validation.Contract do
params do
required(:foo).optional(:string)
required(:bar).optional(:string)
end
rule do
if values[:foo].blank? && values[:bar].blank?
base.failure("provide at least foo or bar")
end
end
endRails + dry-validation
gem "gate", "~> 1.0"
class MyController < ApplicationController
include Gate::Rails
before_action :verify_contract
contract(handler: :method_to_handle_error) do
params do
required(:name).filled(:string)
end
end
def create
claimed_params[:name]
# ...
end
endQuestions?
dry-validation
By Jan Dudulski
dry-validation
- 145