-
Notifications
You must be signed in to change notification settings - Fork 4
Policies
Paradocs ships with a number of built-in policies.
Type coercions (the type
method) and validations (the validate
method) are all policies.
Calls :to_s
on the value
field(:title).type(:string)
Calls :to_i
on the value
field(:age).type(:integer)
Calls :to_f
on the value
field(:price).type(:number)
Returns true
or false
(nil
is converted to false
).
field(:published).type(:boolean)
Attempts parsing value with Datetime.parse. If invalid, the error will be added to the output's errors
object.
field(:expires_on).type(:datetime)
Check that the key exists in the input.
field(:name).required
# same as
field(:name).policy(:required)
Note that :required
policy does not validate that the value is not empty. Use :present
for that.
Check that the key exists and the value is not blank.
field(:name).present
# same as
field(:name).policy(:present)
If the value is a String
, it validates that it's not blank. If an Array
, it checks that it's not empty. Otherwise it checks that the value is not nil
.
Check that a key exists in the input, or stop any further validations otherwise. This is useful when chained to other validations. For example:
field(:name).declared.present
The example above will check that the value is not empty, but only if the key exists. If the key doesn't exist no validations will run.
-
:default
policy is invoked when there are no field presence policies defined or used either:required
or:declared
policies. -
:default
policy is invoked when value is nil or empty -
:default
policy can be a proc. Proc receives the following arguments:key, the whole payload, validation context
field(:role).declared.default("admin")
field(:created_at).declared.default( ->(key, payload, context) { DateTime.now })
Check value against custom regexp
field(:salutation).policy(:format, /^Mr\/s/)
# optional custom error message
field(:salutation).policy(:format, /^Mr\/s\./, "must start with Mr/s.")
field(:business_email).policy(:email)
Compare the value with a number.
field(:age).policy(:gt, 35) # strictly greater than 35
field(:age1).policy(:lt, 11.1) # strictly less than 11.1
field(:age2).policy(:lte, 21) # less or equal to 21
field(:age3).policy(:gte, 11) # greater or equal to 11
Pass allowed values for a field
field(:status).options(["draft", "published"])
# Same as
field(:status).policy(:options, ["draft", "published"])
Specify value's length constraints. Calls #length under the hood.
-
min:
- The attribute cannot have less than the specified length. -
max
- The attribute cannot have more than the specified length. -
eq
- The attribute should be exactly equal to the specified length.
field(:name).length(min: 5, max: 25)
field(:name).length(eq: 10)
Split comma-separated string values into an array. Useful for parsing comma-separated query-string parameters.
field(:status).policy(:split) # turns "pending,confirmed" into ["pending", "confirmed"]
The #meta
field method can be used to add custom meta data to field definitions.
These meta data can be used later when instrospecting schemas (ie. to generate documentation or error notices).
create_user_schema = Paradocs::Schema.new do
field(:name).required.type(:string).meta(label: "User's full name")
field(:status).options(["published", "unpublished"]).default("published")
field(:age).type(:integer).meta(label: "User's age")
field(:friends).type(:array).meta(label: "User friends").schema do
field(:name).type(:string).present.meta(label: "Friend full name")
field(:email).policy(:email).meta(label: "Friend's email")
end
end
You can also register your own custom policy objects. A policy can be not inherited from Paradocs::BasePolicy
, in this case it must implement the following methods: #valid?
, #coerce
, #message
, #meta_data
, #policy_name
class MyPolicy < Paradocs::BasePolicy
# Validation error message, if invalid
def message
'is invalid'
end
# Whether or not to validate and coerce this value
# if false, no other policies will be run on the field
def eligible?(value, key, payload)
true
end
# Transform the value
def coerce(value, key, context)
value
end
# Is the value valid?
def validate(value, key, payload)
true
end
# merge this object into the field's meta data
def meta_data
{type: :string}
end
end
You can register your policy with:
Paradocs.policy :my_policy, MyPolicy
And then refer to it by name when declaring your schema fields
field(:title).policy(:my_policy)
You can chain custom policies with other policies.
field(:title).required.policy(:my_policy)
Note that you can also register instances.
Paradocs.policy :my_policy, MyPolicy.new
For example, a policy that can be configured on a field-by-field basis:
class AddJobTitle
def initialize(job_title)
@job_title = job_title
end
def message
'is invalid'
end
# Noop
def eligible?(value, key, payload)
true
end
# Add job title to value
def coerce(value, key, context)
"#{value}, #{@job_title}"
end
# Noop
def validate(value, key, payload)
true
end
def meta_data
{}
end
end
# Register it
Paradocs.policy :job_title, AddJobTitle
Now you can reuse the same policy with different configuration
manager_schema = Paradocs::Schema.new do
field(:name).type(:string).policy(:job_title, "manager")
end
cto_schema = Paradocs::Schema.new do
field(:name).type(:string).policy(:job_title, "CTO")
end
manager_schema.resolve(name: "Joe Bloggs").output # => {name: "Joe Bloggs, manager"}
cto_schema.resolve(name: "Joe Bloggs").output # => {name: "Joe Bloggs, CTO"}
For simple policies that don't need all policy methods, you can:
Paradocs.policy :cto_job_title do
coerce do |value, key, context|
"#{value}, CTO"
end
end
# use it
cto_schema = Paradocs::Schema.new do
field(:name).type(:string).policy(:cto_job_title)
end
Paradocs.policy :over_21_and_under_25 do
coerce do |age, key, context|
age.to_i
end
validate do |age, key, context|
age > 21 && age < 25
end
end