First, I will introduce about Dry Validation
dry-validation
is the validation data library provided by DSL, it works with any input data, whether it is a hash, an array or an object containing deep nested data.- Validations are expressed through
Contract object
. AContract
will define aschema
with basic data check types and any applicablerules
.Contract
rules
are only applied once the values they are based on are passed through the basicschema
data check types. - Using it will be much faster than using
ActiveRecord/ActiveModel::Validations
,strong-parameters
. - To be able to use it in your project we need to install it first by adding the
gem 'dry-validation'
thegem 'dry-validation'
of the ROR project.
Let’s begin to learn the basics of this library:
1. Type Validate Basic
- Some types of check validate commonly used in dry I can mention:
1 2 3 4 5 6 | - required: buộc trong dữ liệu đầu vào của ta phải có nó - filled: phải điền dữ liệu cho key, attribute,... (ví dụ: {email: " <a class="__cf_email__" href="/cdn-cgi/l/email-protection">[email protected]</a> "}) - optional: nó sẽ ko yêu cầu dữ liệu đầu vào phải có, thường sẽ kết hợp với filled để validate dữ liệu - value: kiểu dữ liệu (string, integer,...) cho key, attribute,...của dữ liệu đầu vào |
2. Schemas
- It can be said that
Schemas
is an important part ofdry-validation
, it will process the data before it is validated by therules
(and whatrules
are below I will learn more) andSchemas
can provide Error message in the most detailed way rules
here can understand that I will validate the data in a more specific way (the data needs to be logically validated) that the gem validate types do not support.
Define a basic schema
- To define a schema we will use the
schema
method:
1 2 3 4 5 6 7 | <span class="token keyword">class</span> <span class="token class-name">ExampleContract</span> <span class="token operator"><</span> <span class="token constant">Dry</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Validation</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Contract</span> schema <span class="token keyword">do</span> <span class="token function">required</span> <span class="token punctuation">(</span> <span class="token symbol">:email</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">value</span> <span class="token punctuation">(</span> <span class="token symbol">:string</span> <span class="token punctuation">)</span> <span class="token function">required</span> <span class="token punctuation">(</span> <span class="token symbol">:age</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">value</span> <span class="token punctuation">(</span> <span class="token symbol">:integer</span> <span class="token punctuation">)</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
- Above we have created a Contact with the required use which means the input must have
email
, theage
and value of theemail
must bestring
and the value ofage
must beinteger
- Now, we will try to apply the Contract we just created:
1 2 3 4 5 6 7 8 9 10 11 | example <span class="token operator">=</span> <span class="token constant">ExampleContract</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token class-name">result</span> <span class="token operator">=</span> example <span class="token punctuation">.</span> <span class="token function">call</span> <span class="token punctuation">(</span> age <span class="token punctuation">:</span> <span class="token number">21</span> <span class="token punctuation">)</span> <span class="token comment"># => #<Dry::Validation::Result{:age=>21} errors={:email=>["is missing"]}></span> result <span class="token punctuation">.</span> to_h <span class="token comment"># => {:age=>21}</span> result <span class="token punctuation">.</span> errors <span class="token punctuation">.</span> to_h <span class="token comment"># => {:email=>["is missing"]}</span> |
Defining a Schema with Params required
- To define a schema for validating
HTTP parameters
, we will use theparams
method:
1 2 3 4 5 6 7 | <span class="token keyword">class</span> <span class="token class-name">ExampleContract</span> <span class="token operator"><</span> <span class="token constant">Dry</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Validation</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Contract</span> params <span class="token keyword">do</span> <span class="token function">required</span> <span class="token punctuation">(</span> <span class="token symbol">:email</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">value</span> <span class="token punctuation">(</span> <span class="token symbol">:string</span> <span class="token punctuation">)</span> <span class="token function">required</span> <span class="token punctuation">(</span> <span class="token symbol">:age</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">value</span> <span class="token punctuation">(</span> <span class="token symbol">:integer</span> <span class="token punctuation">)</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
1 2 3 4 5 6 | result <span class="token operator">=</span> <span class="token constant">ExampleContract</span> <span class="token punctuation">.</span> <span class="token function">call</span> <span class="token punctuation">(</span> <span class="token string">'email'</span> <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">' <a class="__cf_email__" href="/cdn-cgi/l/email-protection">[email protected]</a> '</span> <span class="token punctuation">,</span> <span class="token string">'age'</span> <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">'18'</span> <span class="token punctuation">)</span> <span class="token comment"># => #<Dry::Validation::Result{:email=>" <a class="__cf_email__" href="/cdn-cgi/l/email-protection">[email protected]</a> ", :age=>21} errors={}></span> result <span class="token punctuation">.</span> to_h <span class="token comment"># => {:email=>" <a class="__cf_email__" href="/cdn-cgi/l/email-protection">[email protected]</a> ", :age=>21}</span> |
- The main difference between
params
andschema
is simply thatparams
will implementparams
enforcedrules
before applyingrules
. In therules
section we will learn more clearly.
Define a schema with json required
- We can use
json
to define an appropriateschema
for validating JSON data:
1 2 3 4 5 6 7 | <span class="token keyword">class</span> <span class="token class-name">ExampleContract</span> <span class="token operator"><</span> <span class="token constant">Dry</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Validation</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Contract</span> json <span class="token keyword">do</span> <span class="token function">required</span> <span class="token punctuation">(</span> <span class="token symbol">:email</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">value</span> <span class="token punctuation">(</span> <span class="token symbol">:string</span> <span class="token punctuation">)</span> <span class="token function">required</span> <span class="token punctuation">(</span> <span class="token symbol">:age</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">value</span> <span class="token punctuation">(</span> <span class="token symbol">:integer</span> <span class="token punctuation">)</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
1 2 3 4 5 6 7 8 9 | result <span class="token operator">=</span> <span class="token constant">ExampleContract</span> <span class="token punctuation">.</span> <span class="token function">call</span> <span class="token punctuation">(</span> <span class="token string">'email'</span> <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">' <a class="__cf_email__" href="/cdn-cgi/l/email-protection">[email protected]</a> '</span> <span class="token punctuation">,</span> <span class="token string">'age'</span> <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">'18'</span> <span class="token punctuation">)</span> <span class="token comment"># => #<Dry::Validation::Result{:email=>" <a class="__cf_email__" href="/cdn-cgi/l/email-protection">[email protected]</a> ", :age=>"21"} errors={:age=>["must be an integer"]}></span> result <span class="token operator">=</span> <span class="token constant">ExampleContract</span> <span class="token punctuation">.</span> <span class="token function">call</span> <span class="token punctuation">(</span> <span class="token string">'email'</span> <span class="token operator">=</span> <span class="token operator">></span> <span class="token string">' <a class="__cf_email__" href="/cdn-cgi/l/email-protection">[email protected]</a> '</span> <span class="token punctuation">,</span> <span class="token string">'age'</span> <span class="token operator">=</span> <span class="token operator">></span> <span class="token number">18</span> <span class="token punctuation">)</span> <span class="token comment"># => #<Dry::Validation::Result{:email=>" <a class="__cf_email__" href="/cdn-cgi/l/email-protection">[email protected]</a> ", :age=>18} errors={}></span> result <span class="token punctuation">.</span> to_h <span class="token comment"># => {:email=>" <a class="__cf_email__" href="/cdn-cgi/l/email-protection">[email protected]</a> ", :age=>18}</span> |
Reuse schema from other schema
- We can use a
schema
is or multipleschema
by passing it intoschema
we define. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <span class="token constant">CategorySchema</span> <span class="token operator">=</span> <span class="token constant">Dry</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Schema</span> <span class="token punctuation">.</span> <span class="token constant">Params</span> <span class="token keyword">do</span> <span class="token function">required</span> <span class="token punctuation">(</span> <span class="token symbol">:category_id</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">value</span> <span class="token punctuation">(</span> <span class="token symbol">:integer</span> <span class="token punctuation">)</span> <span class="token function">required</span> <span class="token punctuation">(</span> <span class="token symbol">:type</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">value</span> <span class="token punctuation">(</span> <span class="token symbol">:string</span> <span class="token punctuation">)</span> <span class="token keyword">end</span> <span class="token constant">UserSchema</span> <span class="token operator">=</span> <span class="token constant">Dry</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Schema</span> <span class="token punctuation">.</span> <span class="token constant">Params</span> <span class="token keyword">do</span> <span class="token function">required</span> <span class="token punctuation">(</span> <span class="token symbol">:user_id</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">value</span> <span class="token punctuation">(</span> <span class="token symbol">:integer</span> <span class="token punctuation">)</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">PostContract</span> <span class="token operator"><</span> <span class="token constant">Dry</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Validation</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Contract</span> <span class="token function">params</span> <span class="token punctuation">(</span> <span class="token constant">CategorySchema</span> <span class="token punctuation">,</span> <span class="token constant">UserSchema</span> <span class="token punctuation">)</span> <span class="token keyword">do</span> <span class="token function">required</span> <span class="token punctuation">(</span> <span class="token symbol">:name</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">value</span> <span class="token punctuation">(</span> <span class="token symbol">:string</span> <span class="token punctuation">)</span> <span class="token function">required</span> <span class="token punctuation">(</span> <span class="token symbol">:content</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">value</span> <span class="token punctuation">(</span> <span class="token symbol">:string</span> <span class="token punctuation">)</span> <span class="token keyword">end</span> <span class="token keyword">end</span> post <span class="token operator">=</span> <span class="token constant">PostContract</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token class-name">post <span class="token punctuation">.</span></span> <span class="token punctuation">(</span> name <span class="token punctuation">:</span> <span class="token string">"New Post"</span> <span class="token punctuation">,</span> content <span class="token punctuation">:</span> <span class="token number">1</span> <span class="token punctuation">,</span> user_id <span class="token punctuation">:</span> <span class="token number">2</span> <span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token operator">></span> <span class="token comment">#<Dry::Validation::Result{:name=>"New Post", :content=>1, :user_id=>2} </span> <span class="token comment"># errors={:category_id=>["is missing"], :type=>["is missing"], :content=>["must be a string"]}> </span> |
Custom Types for Schema
- When we define the schema using
param
orjson
, we can handle the value of the input data the way we want, for example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token keyword">module</span> <span class="token constant">Types</span> include <span class="token constant">Dry</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token function">Types</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token constant">StripString</span> <span class="token operator">=</span> <span class="token constant">Types</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token punctuation">.</span> <span class="token function">constructor</span> <span class="token punctuation">(</span> <span class="token operator">&</span> <span class="token symbol">:strip</span> <span class="token punctuation">)</span> <span class="token keyword">end</span> <span class="token keyword">class</span> <span class="token class-name">EmailContract</span> <span class="token operator"><</span> <span class="token constant">Dry</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Validation</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">Contract</span> params <span class="token keyword">do</span> <span class="token function">required</span> <span class="token punctuation">(</span> <span class="token symbol">:email</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">value</span> <span class="token punctuation">(</span> <span class="token constant">Types</span> <span class="token punctuation">:</span> <span class="token punctuation">:</span> <span class="token constant">StripString</span> <span class="token punctuation">)</span> <span class="token keyword">end</span> <span class="token keyword">end</span> email <span class="token operator">=</span> <span class="token constant">EmailContract</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token class-name">email <span class="token punctuation">.</span> call</span> <span class="token punctuation">(</span> email <span class="token punctuation">:</span> <span class="token string">' <a class="__cf_email__" href="/cdn-cgi/l/email-protection">[email protected]</a> '</span> <span class="token punctuation">)</span> <span class="token comment"># => #<Dry::Validation::Result{:email=>" <a class="__cf_email__" href="/cdn-cgi/l/email-protection">[email protected]</a> "} errors={}></span> |