Trước tiên, mình sẽ giới thiệu qua về Dry Validation
dry-validation
là thư viện validation data được cung cấp bởi DSL, nó hoat động với bất kỳ dữ liệu đầu vào nào, cho dù nó là một hash, array hay một đối tượng chứa những liệu được lồng sâu vào với nhau.- Validations được thể hiện thông qua các
Contract object
. MộtContract
sẽ xác dịnh mộtschema
với các kiểu check dữ liệu cơ bản và bất kỳrules
nào có thể áp dụng.rules
trongContract
chỉ được áp dụng một khi các giá trị mà chúng dựa vào đã được pass qua các kiểu check dữ liệu cơ bản củaschema
. - Sử dụng nó sẽ nhanh hơn nhiều so với việc chúng ta sử dụng
ActiveRecord/ActiveModel::Validations
,strong-parameters
. - Để cho thể sử dụng nó trong project của mình ta cần cài đặt nó trước bằng việc add
gem 'dry-validation'
vào gemfile của project ROR chúng ta nhé
Ta bắt đầu tìm hiểu những thứ căn bản của thư viện này nhé:
1. Type Validate Basic
- Một số kiểu check validate thường sử dụng trong dry mình có thể kể đến:
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
- Có thể nói
Schemas
là một phần quan trọng củadry-validation
, nó sẽ xử lý dữ liệu trước khi nó bị validate bởirules
( cònrules
là gì thì bên dưới mình sẽ tìm hiểu rõ hơn nhé ) vàSchemas
có thể cung cấp các thông báo lỗi một cách chi tiết nhất rules
ở đây có thể hiểu là mình sẽ validate các dữ liệu một cách cụ thể hơn (các dữ liệu cần validate một cách logic hơn) mà các type validate của gem không hỗ trợ.
Định nghĩa một Schema cơ bản
- Để định nghĩa một schema chúng ta sẽ sử dụng
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> |
- Ở bên trên ta đã khởi tạo một Contact với việc sử dụng required có nghĩa là đầu vào buộc phải có
email
,age
và value củaemail
phải làstring
và value củaage
phải làinteger
- Bây giờ, ta sẽ thử áp dụng Contract chúng ta vừa tạo nhé:
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> |
Định nghĩa một Schema với Params required
- Để định nghĩa một schema cho việc validate
HTTP parameters
, ta sẽ sử dụngparams
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> |
- Sự khác biệt chính giữa
params
vàschema
đơn giản làparams
sẽ thực hiện các bắt buộc dành riêng choparams
trước khi áp dụngrules
. Trong phầnrules
ta sẽ tìm hiểu rõ ràng hơn.
Định nghĩa một Schema với JSON required
- Ta có thể sử dụng
json
để định nghĩa mộtschema
phù hợp cho việc xác thực 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> |
Sử dụng lại schema từ schema khác
- Ta có thể sử dụng lại một
schema
hiện có hoặc multipleschema
bằng cách passing nó sangschema
chúng ta định nghĩa. Ví dụ:
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 cho Schema
- Khi chúng ta định nghĩa schema sử dụng
param
hayjson
, ta có thể xử lý cho giá trị dữ liệu đầu vào theo cách ta muốn ví dụ như:
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> |