11. Conditional Validation
:if and :unless
• A Symbol : a method name
• A String : a really short condition
• A Proc : an inline condition
Grouping conditions : with_options
ROR Lab.
12. Custom Validations
• Custom validators modules
: inherited from Two
★ ActiveModel::Validator
★ ActiveModel::EachValidator
★ Get the “record” argument as a
parameter
• Custom validation methods
• Custom validation helpers
ROR Lab.
13. Working with
Validation Errors
• errors
• errors.messages
• errors.full_messages( or errors.to_a)
• errors[:attr] : for a specific attribute
• errors.add(:attr, message)(or errors[:attr]=)
• errors[:base] : object’s state as a whole
• errors.clear : intentionally to clear
• errors.size : count of errors
ROR Lab.
14. Displaying Validation
Errors in the View
★ gem ‘dynamic_form’ ★ Error Messages CSS
.field_with_errors
#errorExplanation
#errorExplanation h2
#errorExplanation p
#errorExplanation ul li
ROR Lab.
15. Validation Errors : https://github.com/joelmoss/dynamic_form
Error CSS
#error_explanation
#error_explanation h2
#error_explanation p
<%= form_for(@product) do |f| %>
<% if @product.errors.any? %>
<div id="error_explanation"> #error_explanation ul li
<h2><%= pluralize(@product.errors.count, "error") %>
prohibited this product from being saved:
</h2>
<ul>
<% @product.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %> .field_with_errors
generated by scaffold
apps/assets/stylesheets/scaffolds.css.scss
ROR Lab.
16. Validation Errors : https://github.com/joelmoss/dynamic_form
Error CSS
#error_explanation
<%= form_for(@product) do |f| %>
#error_explanation h2
<% if @product.errors.any? %> #error_explanation p
<div id="error_explanation">
<h2><%= pluralize(@product.errors.count, "error") %>
#error_explanation ul li
prohibited this product from being saved:
</h2>
<ul>
<% @product.errors.full_messages.each do |msg| %>
<li><%= msg %></li> .field_with_errors
<% end %>
</ul>
</div>
<% end %>
generated by scaffold
apps/assets/stylesheets/scaffolds.css.scss
ROR Lab.
18. Validation Errors :
‘dynamic_form’
• f.error_messages or
• error_messages_for :product
<%= form_for(@product) do |f| %>
<%= f.error_messages %>
<% end %>
ROR Lab.
19. Validation Errors : https://github.com/joelmoss/dynamic_form
‘dynamic_form’
generated by dynamic_form
r_ tag
e
:h ead :header_message
:message
<%= f.error_messages
:header_message => "Invalid product!",
:message => "You'll need to fix the following fields:",
:header_tag => :h3 %>
ROR Lab.
20. Validation Errors : https://github.com/joelmoss/dynamic_form
Error HTML
config/initializers/custom_error_message_html.rb field object
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
errors = Array(instance.error_message).join(',')
unless html_tag =~ /^<label/
%(#{html_tag}<span class="validation-error"> #{errors}</span>).html_safe
else
%(#{html_tag}).html_safe
end
end
#{html_tag} .validation-error
ROR Lab.
21. Validation Errors : https://github.com/joelmoss/dynamic_form
Error HTML
<% if @product.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@product.errors.count, "error") %> prohibited this product from being saved:</h2>
</div>
<% end %>
ROR Lab.
33. Validation Helpers :
acceptance
• a checkbox : a real or virtual attribute
• “must be accepted”
class Person ActiveRecord::Base
validates :terms_of_service, :acceptance = true
class Person ActiveRecord::Base
validates :terms_of_service,
:acceptance = { :accept = 'yes' }
ROR Lab.
34. Validation Helpers :
validates_associated
• should if associated with other models
• “is invalid”
class Library ActiveRecord::Base
has_many :books
validates_associated :books
• should use on one end of your
associations
ROR Lab.
35. Validation Helpers :
confirmation
• a virtual attribute appended with
“_confirmation”
• “doesn’t match confirmation”
class Person ActiveRecord::Base
validates :email, :confirmation = true
validates :email_confirmation, :presence = true
%= text_field :person, :email %
%= text_field :person, :email_confirmation %
ROR Lab.
36. Validation Helpers :
exclusion
• not included in a given set
:any enumerable object
• “is reserved”
class Account ActiveRecord::Base
validates :subdomain,
:exclusion = {
:in = %w(www us ca jp),
:message = Subdomain %{value} is reserved. }
ROR Lab.
37. Validation Helpers :
format
• match a given regular expression
• “is invalid”
class Product ActiveRecord::Base
validates :legacy_code,
:format = { :with = /A[a-zA-Z]+z/,
:message = Only letters allowed }
ROR Lab.
38. Validation Helpers :
inclusion
• included in a given set
: any enumerable object
• “is not included in the list”
class Coffee ActiveRecord::Base
validates :size,
:inclusion = {
:in = %w(small medium large),
:message = %{value} is not a valid size
}
ROR Lab.
40. Validation Helpers :
length (2/2)
• options
- :wrong_length, :too_long, :too_short
- a placeholder - %{count}
class Essay ActiveRecord::Base
validates :content, :size = {
:minimum = 300,
:maximum = 400,
:tokenizer = lambda { |str| str.scan(/w+/) },
:too_short = must have at least %{count} words,
:too_long = must have at most %{count} words
}
ROR Lab.
41. Validation Helpers :
numericality (1/2)
• only numeric values
: (+/−) integer/floating point
• “is not a number”
class Person ActiveRecord::Base
validates :email, :confirmation = true
validates :email_confirmation, :presence = true
%= text_field :person, :email %
%= text_field :person, :email_confirmation %
ROR Lab.
42. Validation Helpers :
numericality (2/2)
class Player ActiveRecord::Base
validates :points, :numericality = true
validates :games_played,
:numericality = { :only_integer = true }
/A[+−]?d+Z/
•:greater_than ➡ “must be greater than %{count}”
•:greater_than_or_equal_to ➡ “must be greater than or equal to %{count}
•:equal_to ➡ “must be equal to %{count}”
•:less_than ➡ “must be less than %{count}”
•:less_than_or_equal_to ➡ “must be less than or equal to %{count}
•:odd ➡ “must be odd”
•:even ➡ “must be even”
ROR Lab.
43. Validation Helpers :
presence (1/2)
• empty or whitespaces
• “can’t be empty”
class Person ActiveRecord::Base
validates :name, :login, :email, :presence = true
end
class LineItem ActiveRecord::Base
belongs_to :order
validates :order_id, :presence = true
ROR Lab.
44. Validation Helpers :
presence (2/2)
• empty or whitespaces
• “can’t be empty”
✘
class Person ActiveRecord::Base
validates :is_admin, :presence = true
end a boolean field ➞ false.blank? is true
class Person ActiveRecord::Base
validates :is_admin,
:inclusion = { :in = [true, false] }
ROR Lab.
45. Validation Helpers :
uniqueness
• add_index :table_name, :column_name, unique = true
• “has already been taken”
class Account ActiveRecord::Base
validates :email, :uniqueness = true other
end attributes
class Holiday ActiveRecord::Base
validates :name, :uniqueness = { :scope = :year,
:message = should happen once per year }
end
class Person ActiveRecord::Base
validates :name,
:uniqueness = { :case_sensitive = false }
ROR Lab.
46. Validation Helpers :
validates_with (1/2)
• a separate class for validation
• no default validate error message
class Person ActiveRecord::Base
validates_with GoodnessValidator,[:if/:unless/:on]
end
class GoodnessValidator ActiveModel::Validator
def validate(record)
if record.first_name == Evil
record.errors[:base] This person is evil
end
end
ROR Lab.
47. Validation Helpers :
validates_with (2/2)
• any additional options ➞ options
class Person ActiveRecord::Base
validates_with GoodnessValidator, :fields =
[:first_name, :last_name]
end
class GoodnessValidator ActiveModel::Validator
def validate(record)
if options[:fields].any?{|field| record.send(field) == Evil }
record.errors[:base] This person is evil
end
end
end
ROR Lab.
48. Validation Helpers :
validates_each
• no predefined validation function
➞a block
• no default error message
class Person ActiveRecord::Base
validates_each :name, :surname do |record, attr, value|
record.errors.add(attr, 'must start with upper case') if value =~ /A[a-z]/
end
end
ROR Lab.
49. Common Validation Options :
:allow_nil
class Coffee ActiveRecord::Base
validates :size,
:inclusion = { :in = %w(small medium large),
:message = %{value} is not a valid size },
:allow_nil = true
ROR Lab.
50. Common Validation Options :
:allow_blank
• nil or an empty string
class Topic ActiveRecord::Base
validates :title,
:length = { :is = 5 },
:allow_blank = true
end
Topic.create(title = ).valid? # = true
ROR Lab.
51. Common Validation Options :
:on
• when the validation should happen
class Person ActiveRecord::Base
# it will be possible to update email with a duplicated value
validates :email, :uniqueness = true, :on = :create
# it will be possible to create the record with a non-numerical age
validates :age, :numericality = true, :on = :update
# the default (validates on both create and update)
validates :name, :presence = true, :on = :save
ROR Lab.
52. Conditional Validation
:if :unless (1/4)
• Using a Symbol : a method name
class Order ActiveRecord::Base
validates :card_number, :presence = true,
:if = :paid_with_card?
def paid_with_card?
payment_type == card
end
ROR Lab.
53. Conditional Validation
:if :unless (2/4)
• Using a String : a really short condition
class Person ActiveRecord::Base
validates :surname, :presence = true,
:if = name.nil?
ROR Lab.
54. Conditional Validation
:if :unless (3/4)
• Using a Proc : an inline condition
class Account ActiveRecord::Base
validates :password, :confirmation = true,
:unless = Proc.new { |a| a.password.blank? }
a model
object
ROR Lab.
55. Conditional Validation
:if :unless (4/4)
• grouping conditional validations
condition
object
class User ActiveRecord::Base
with_options :if = :is_admin? do |admin|
admin.validates :password, :length = { :minimum = 10 }
admin.validates :email, :presence = true
end
ROR Lab.
56. Custom Validations :
Custom Validators (1/2)
• to extend ActiveMode::Validator
• to validate the state of whole record
class MyValidator ActiveModel::Validator
def validate(record)
unless record.name.starts_with? 'X'
record.errors[:name] 'Need a name starting with X please!'
end
end
end
class Person
include ActiveModel::Validations
validates_with MyValidator
end
ROR Lab.
57. Custom Validations :
Custom Validators (2/2)
• to extend ActiveMode::EachValidator
• to validate individual attributes
class EmailValidator ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /A([^@s]+)@((?:[-a-z0-9]+.)+[a-z]{2,})z/i
record.errors[attribute] (options[:message] || is not an email)
end
end
end
EmailValidator
class Person ActiveRecord::Base
validates :email, :presence = true, :email = true
end
ROR Lab.
58. Custom Validations :
Custom Methods (1/2)
• to verify the state of models
class Invoice ActiveRecord::Base
validate :expiration_date_cannot_be_in_the_past,
:discount_cannot_be_greater_than_total_value
def expiration_date_cannot_be_in_the_past
if !expiration_date.blank? and expiration_date Date.today
errors.add(:expiration_date, can't be in the past)
end
end EmailValidator
def discount_cannot_be_greater_than_total_value
if discount total_value
errors.add(:discount, can't be greater than total value)
end
end
end
ROR Lab.
59. Custom Validations :
Custom Methods (2/2)
• to verify the state of models
class Invoice ActiveRecord::Base
validate :active_customer, :on = :create
def active_customer
errors.add(:customer_id, is not active)
unless customer.active?
end
ROR Lab.
60. Custom Validations :
Custom Helpers
• to reuse in several different models
• to put in config/initializers
ActiveRecord::Base.class_eval do
def self.validates_as_choice(attr_name, n, options={})
validates attr_name,
:inclusion = { { :in = 1..n }.merge!(options) }
end
end
class Movie ActiveRecord::Base
validates_as_choice :rating, 5
end
ROR Lab.
61. Validation Errors :
errors
• an instance of ActiveModel::Errors
• key : attribute name
value : an array of strings with all errors
class Person ActiveRecord::Base
validates :name, :presence = true, :length = { :minimum = 3 }
end
person = Person.new
person.valid? # = false
person.errors
# = {:name =
[can't be blank, is too short (minimum is 3 characters)]}
person = Person.new(:name = John Doe)
person.valid? # = true
person.errors # = []
ROR Lab.
62. Validation Errors :
errors[ ]
• to check the error message of a specific
attribute
class Person ActiveRecord::Base
validates :name, :presence = true, :length = { :minimum = 3 }
end
person = Person.new(:name = John Doe)
person.valid? # = true
person.errors[:name] # = []
person = Person.new(:name = JD)
person.valid? # = false
person.errors[:name] # = [is too short (minimum is 3 characters)]
person = Person.new
person.valid? # = false
person.errors[:name]
# = [can't be blank, is too short (minimum is 3 characters)]
ROR Lab.
63. Validation Errors :
errors.add (1/2)
• to manually add messages of a specific
attribute
class Person ActiveRecord::Base
def a_method_used_for_validation_purposes
errors.add(:name, cannot contain the characters !@#%*()_-+=)
end
end
person = Person.create(:name = !@#)
person.errors[:name]
# = [cannot contain the characters !@#%*()_-+=]
person.errors.full_messages
# = [Name cannot contain the characters !@#%*()_-+=]
ROR Lab.
64. Validation Errors :
errors.add (2/2)
-or-
class Person ActiveRecord::Base
def a_method_used_for_validation_purposes
errors[:name] = cannot contain the characters !@#%*()_-+=)
end
end
person = Person.create(:name = !@#)
person.errors[:name]
# = [cannot contain the characters !@#%*()_-+=]
person.errors.to_a
# = [Name cannot contain the characters !@#%*()_-+=]
ROR Lab.
65. Validation Errors :
errors[:base]
• related to the object’s state as a whole
• an array
class Person ActiveRecord::Base
def a_method_used_for_validation_purposes
errors[:base] This person is invalid because ...
end
end
ROR Lab.
66. Validation Errors :
errors.clear
• related to the object’s state as a whole
• an array
class Person ActiveRecord::Base
validates :name, :presence = true, :length = { :minimum = 3 }
end
person = Person.new
person.valid? # = false
person.errors[:name]
# = [can't be blank, is too short (minimum is 3 characters)]
person.errors.clear
person.errors.empty? # = true
p.save # = false
p.errors[:name]
# = [can't be blank, is too short (minimum is 3 characters)]
ROR Lab.
67. Validation Errors :
errors.size
class Person ActiveRecord::Base
validates :name, :presence = true, :length = { :minimum = 3 }
end
person = Person.new
person.valid? # = false
person.errors.size # = 2
person = Person.new(:name = Andrea, :email = andrea@example.com)
person.valid? # = true
person.errors.size # = 0
ROR Lab.