Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

ActiveRecord Validations, Season 2

1,675 views

Published on

This is a Keynote slide about the "ActiveRecord Validations"

Published in: Technology
  • Be the first to comment

ActiveRecord Validations, Season 2

  1. 1. Ror lab. season 2 - the 6th round -Active RecordValidations September 15th, 2012 Hyoseong Choi ROR Lab.
  2. 2. Contents Validations Callbacks• The Object Life Cycle • Callbacks Overview• Validations Overview • Available Callbacks• Validation Helpers • Running Callbacks• Common Validation • Skipping Callbacks Options • Halting Execution• Conditional Validation • Relational Callbacks• Custom Validations • Conditional Callbacks• Working with Validation • Callback Classes Errors • Observers• Displaying Validation Errors • Transaction Callbacks in the View ROR Lab.
  3. 3. Validation Levels• Native DB constraints - DB-dependent• Client-side validations - javascript ?• Controller-level validations - keep skinny• Model-level validations - the best way ROR Lab.
  4. 4. “new” method• A new object instantiated vs. persisted?• not yet to store to database : new_record?• no validation called, but ... ROR Lab.
  5. 5. Validation Time Point ActiveRecord Web Server Model DB Validation save create update clients Model-level validations ROR Lab.
  6. 6. Validation Event Triggering validations Skipping validations • .create • .decrement! • .create! • #decrement_counter • .save • .increment! • .save! • #increment_counter • #update • .toggle! • .update_attributes • .touch • .update_attributes! • #update_all • .update_attribute deprecated • .update_column • #update_countersRef.: ActiveRecord::Persistence ActiveRecord::CounterCache ROR Lab.
  7. 7. Call Validations• valid? or invalid? ROR Lab.
  8. 8. valid? vs ErrorsBy definition,an object is valid if this collection is emptyafter running validations. ROR Lab.
  9. 9. Validation Helpers• acceptance • validates_associated• confirmation • validates_each• exclusion • validates_with• format• inclusion :on • save(default) • create• length • update• numericality :message• presence• uniqueness ROR Lab.
  10. 10. Common Options:allow_nil - nil:allow_blank - nil or whitespace:message - overriding default error message:on - :create / :update / :save ROR Lab.
  11. 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. 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. 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. 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. 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. 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.
  17. 17. Validation Errors : https://github.com/joelmoss/dynamic_form Error CSSapps/assets/stylesheets/scaffolds.css.scss #error_explanation.field_with_errors { #error_explanation h2 padding: 2px; background-color: red; #error_explanation p display: table;}#error_explanation { #error_explanation ul li width: 450px; border: 2px solid red; padding: 7px; padding-bottom: 0; margin-bottom: 20px; background-color: #f0f0f0; h2 { .field_with_errors text-align: left; font-weight: bold; padding: 5px 5px 5px 15px; font-size: 12px; margin: -7px; margin-bottom: 0px; background-color: #c00; color: #fff; } ul li { generated by scaffold font-size: 12px; list-style: square; ROR Lab.
  18. 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. 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 => "Youll need to fix the following fields:",   :header_tag => :h3 %> ROR Lab.
  20. 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">&nbsp;#{errors}</span>).html_safe else %(#{html_tag}).html_safe end end #{html_tag} .validation-error ROR Lab.
  21. 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.
  22. 22. 감사합니다.
  23. 23.  
  24. 24. appendix
  25. 25.  
  26. 26. Active Record Counter Cache• #decrement_counter• #increment_counter skipping• #reset_counters validations• #update_counters ROR Lab.
  27. 27. decrement vs decrement! ROR Lab.
  28. 28. toggle vs toggle! ROR Lab.
  29. 29. touch ROR Lab.
  30. 30. #update_all ROR Lab.
  31. 31. .update_column ROR Lab.
  32. 32. #update ROR Lab.
  33. 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. 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. 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. 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. 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. 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.
  39. 39. Validation Helpers : length (1/2) • length constraint options - :minimum, :maximum, :in/:within, :is - a placeholder - %{count} class Person ActiveRecord::Base   validates :name, :length = { :minimum = 2 }   validates :bio, :length = { :maximum = 500 }   validates :password, :length = { :in = 6..20 }   validates :registration_number, :length = { :is = 6 } ROR Lab.
  40. 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. 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. 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. 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. 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. 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. 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. 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. 48. Validation Helpers : validates_each • no predefined validation function ➞a block • no default error messageclass 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]/  endend ROR Lab.
  49. 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. 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. 51. Common Validation Options : :on • when the validation should happenclass 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. 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. 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. 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. 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. 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. 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. 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, cant be in the past)     end   end EmailValidator     def discount_cannot_be_greater_than_total_value     if discount total_value       errors.add(:discount, cant be greater than total value)     end   end end ROR Lab.
  59. 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. 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. 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 = [cant be blank, is too short (minimum is 3 characters)]}   person = Person.new(:name = John Doe) person.valid? # = true person.errors # = [] ROR Lab.
  62. 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]  # = [cant be blank, is too short (minimum is 3 characters)] ROR Lab.
  63. 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. 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. 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. 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]  # = [cant be blank, is too short (minimum is 3 characters)]   person.errors.clear person.errors.empty? # = true   p.save # = false   p.errors[:name]  # = [cant be blank, is too short (minimum is 3 characters)] ROR Lab.
  67. 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.

×