程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Rails開發細節(六)ActiveRecord Validationa and Callbacks驗證和回調

Rails開發細節(六)ActiveRecord Validationa and Callbacks驗證和回調

編輯:關於JAVA

1.對象生命周期

通常情況下,在rails應用中,對象會被創建,修改和刪除。ActiveRecord針對這些對象提供了攔截,你可以控制你的應用和這些對象。

驗證保證了存入數據庫的數據都是有效的。回調和觀察者允許你在對象狀態發生變化的前後進行一些邏輯操作。

2.驗證

2.1.為什麼需要驗證

驗證保證了只有合法的數據才可以存入數據庫。例如,你的應用需要確保每個用戶都擁有合法的電子郵件地址和郵寄地址。

在存入數據庫之前,有很多方法可以驗證數據的合法性。包括數據庫約束,客戶端的驗證,controller級別的驗證,model級別的驗證。

數據庫約束或者是存儲過程中的驗證是依賴於數據庫的,難以維護和測試。如果你的數據庫還會被其他應用使用,那麼在數據庫級別的約束是個好主意。另外,數據庫級別的驗證可以安全的實現一些事情,例如唯一性約束,這樣的需求在應用中實現相對較難。

客戶端驗證是很有用的,但是不能單獨的信任客戶端驗證。如果使用javascript實現,是可以繞過的。但是,結合其他技術,客戶端驗證可以在用戶訪問你的網站的時候,給用戶很快的反饋。

controller級別的驗證也是可以的,但是通常它們很笨重,難以測試和維護。無論什麼時候,保持controller的精簡都是一個好主意,使得你的應用長期保持一個好的工作。

model級別的驗證是保證合法數據存入數據庫的最好方法。它們是數據庫無關的,不能被終端用戶繞過,很方便測試和維護。在rails中,很容易使用,提供了內置的輔助方法,還可以創建自己的驗證方法。

2.2.驗證在什麼時候發生

有兩種類型的ActiveRecord對象:一種對應於數據庫的一行數據,另一種不是。在你創建一個新的對象,就是調用new方法之後,數據庫中還不存在這條記錄。一旦你調用了save方法,就會存入數據庫。可以使用new_record?實例方法來判斷對象是否存在於數據庫。

class Person < ActiveRecord::Base 
     
end 
     
p = Person.new(:name => "shi") 
p.new_record?  #=> false
     
p.save  #=> true
     
p.new_record?  #=> true

create並調用save方法會給數據庫發送insert語句,更新一個已經存在的記錄就是給數據庫發送update語句。驗證發生在發送這些語句之前。如果驗證失敗,對象被標記為非法,不會發送insert或update語句,這就避免了非法數據存入數據庫。你可以在created,saved和updated的時候執行指定的驗證規則。

下面的方法將會觸發驗證,如果對象是合法的,就會存入數據庫。

create

create!

save

save!

update

update_attributes

update_attributes!

有歎號的方法在對象是非法的時候會拋出異常。沒有歎號的save和update_attributes在非法的時候返回false,create和update返回對象。

2.3.跳過驗證

下面的方法會跳過驗證,不管是否合法都會存入數據庫,使用的時候要小心。

decrement!

decrement_counter

increment!

increment_counter

toggle!

touch

update_all

update_attribute

update_column

update_counters

save方法通過添加:validate => false參數也可以跳過驗證,要小心使用。

p.save(:validate => false)

2.4.Valid? and Invalid?

通過valid?方法來判斷對象是否合法,會觸發在model中定義的validates,如果沒有錯誤,返回true,否則返回false代表不合法。

class Person < ActiveRecord::Base 
  validates :name, :presence => true
end 
      
Person.create(:name => "John Doe").valid? # => true
Person.create(:name => nil).valid? # => false

ActiveRecord執行驗證之後,對象的errors會返回一個集合。如果是合法的,這個集合就是空的。

new之後創建的對象,即使是不合法的,errors集合也是空的,因為在new的時候還沒有觸發驗證。

class Person < ActiveRecord::Base 
  validates :name, :presence => true
end 
      
>> p = Person.new
=> #<Person id: nil, name: nil> 
>> p.errors 
=> {} 
      
>> p.valid? 
=> false
>> p.errors 
=> {:name=>["can't be blank"]} 
      
>> p = Person.create 
=> #<Person id: nil, name: nil> 
>> p.errors 
=> {:name=>["can't be blank"]} 
      
>> p.save 
=> false
      
>> p.save! 
=> ActiveRecord::RecordInvalid: Validation failed: Name can't be blank 
      
>> Person.create! 
=> ActiveRecord::RecordInvalid: Validation failed: Name can't be blank

invalid?和valid?方法相反,它也會觸發驗證,如果有erros就返回true,否則返回false。

2.5.errors[]

通過errors[:attribute]可以驗證在某個指定的屬性是否合法,返回的是一個數組,如果這個屬性沒有問題,返回的數組是空的。

這個方法只在驗證發生之後才有用,因為它只是檢查errors集合,並不觸發驗證,只是檢查在某一個指定的屬性上是否有errors。

class Person < ActiveRecord::Base 
  validates :name, :presence => true
end 
      
>> Person.new.errors[:name].any? # => false
>> Person.create.errors[:name].any? # => true

3.驗證輔助工具

ActiveRecord預先定義了很多的驗證工具,你可以直接使用它們。定義了常見的驗證規則,每次驗證失敗,都會在errors集合中添加對象,信息和驗證的屬性相關。

每個驗證方法都會接受任意屬性的名稱,因此你可以在一條驗證語句中添加多個屬性。

每個驗證都接受:on和:message的可選項,定義驗證在什麼時候觸發,在驗證失敗之後需要在errors中添加什麼信息。:on選項的值可以是:save, :create, :update中的一個,:save是默認值。每個驗證方法都有默認的提示信息。下面列出一些常見的驗證方法。

3.1.acceptance

驗證在提交的表單中,用戶是否勾選了checkbox。常見的場景包括:用戶同意服務條款,確定閱讀了一段文本,等類似場景。這種驗證在web應用中很常用,通常不需要存入數據庫(除非你的數據庫有對應的列)。如果不需要存儲,那麼只是一個虛擬的屬性。

class Person < ActiveRecord::Base 
  validates :terms_of_service, :acceptance => true
end

默認的錯誤信息是:must be accepted。

class Person < ActiveRecord::Base 
  validates :terms_of_service, :acceptance => { :accept => 'yes' } 
end

還可以通過:accept來定義可以接受的值。

3.2.validates_associated

用來驗證表關系,當你save對象的時候,驗證每個關聯。

class Library < ActiveRecord::Base 
  has_many :books 
  validates_associated :books 
end

注意不要在關系的雙方都進行這樣的驗證,會造成死循環驗證。

3.3.confirmation

用來驗證兩個輸入框應該輸入相同的內容,例如驗證郵件和密碼。驗證會創建一個虛擬的屬性,屬性名稱以"_confirmation"結尾。

class Person < ActiveRecord::Base 
  validates :email, :confirmation => true
end

在view中你可以使用下面的方式。

<%= text_field :person, :email %>

<%= text_field :person, :email_confirmation %>

這個驗證只是在email_confirmation不為nil的是才觸發,為了滿足需求,還要在email_confirmation中添加:presence驗證。

class Person < ActiveRecord::Base 
  validates :email, :confirmation => true
  validates :email_confirmation, :presence => true
end

3.4.exclusion

用來驗證一個屬性是否不在指定的集合中。這個集合是一個枚舉對象集合。

class Account < ActiveRecord::Base 
  validates :subdomain, :exclusion => { :in => %w(www us ca jp), 
    :message => "Subdomain %{value} is reserved." } 
end

:in選項用來指定集合,:in有一個別名:within,你也可以用它實現相同的功能。

3.5.format

用來驗證指定的屬性是否符合正則表達式,:with來指定正則表達式。

class Product < ActiveRecord::Base 
  validates :legacy_code, :format => { :with => /\A[a-zA-Z]+\z/, 
    :message => "Only letters allowed" } 
end

3.6.inclusion

用來驗證一個屬性是否在指定的集合中。這個集合是一個枚舉對象集合。

class Account < ActiveRecord::Base 
  validates :subdomain, :inclusion => { :in => %w(www us ca jp), 
    :message => "Subdomain %{value} is reserved." } 
end

:in選項用來指定集合,:in有一個別名:within,你也可以用它實現相同的功能。

3.7.length

用來驗證屬性的長度。

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 } 
end
class Person < ActiveRecord::Base 
  validates :bio, :length => { :maximum => 1000, 
    :too_long => "%{count} characters is the maximum allowed" } 
end 
     
     
class Essay < ActiveRecord::Base 
  validates :content, :length => { 
    :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"
  } 
end

size是length的別名,上面的length可以用size代替。

3.8.numericality

驗證屬性只接受數值類型。

:only_integer => true相當於使用

/\A[+-]?\d+\Z/

正則表達式。否則將會嘗試使用Float轉換這個值。

class Player < ActiveRecord::Base 
  validates :points, :numericality => true
  validates :games_played, :numericality => { :only_integer => true } 
end

除了:only_integer還接受其他的選項。

:greater_than

:greater_than_or_equal_to

:equal_to

:less_than

:less_than_or_equal_to

:odd

:even

3.9.presence

用來驗證屬性不可空。調用blank?方法檢查字符串是否nil或者空白,空白包括空字符串和空格字符串兩種類型。

class Person < ActiveRecord::Base 
  validates :name, :login, :email, :presence => true
end

驗證關聯是必須存在的,只要驗證外鍵就可以。

class LineItem < ActiveRecord::Base 
  belongs_to :order 
  validates :order_id, :presence => true
end

驗證boolean類型的字段。

validates :field_name, :inclusion => { :in => [true, false] }.

3.10.uniqueness

驗證屬性的唯一性。它不會在數據庫中創建唯一約束。還是會發生兩個不同的數據庫連接,創建兩個相同值的記錄。所以最好在數據庫創建唯一約束。

class Account < ActiveRecord::Base 
  validates :email, :uniqueness => true
end

這個驗證發生在執行sql語句的時候,查詢是否存在相同的記錄。

:scope選項用來限制驗證的范圍。

class Holiday < ActiveRecord::Base 
  validates :name, :uniqueness => { :scope => :year, 
    :message => "should happen once per year" } 
end

:case_sensitive選項指定是否大小寫敏感。

class Person < ActiveRecord::Base 
  validates :name, :uniqueness => { :case_sensitive => false } 
end

有些數據庫是可以配置大小寫敏感的。

3.11.validates_with

指定單獨的驗證類。

class Person < ActiveRecord::Base 
  validates_with GoodnessValidator 
end 
      
class GoodnessValidator < ActiveModel::Validator 
  def validate(record) 
    if record.first_name == "Evil"
      record.errors[:base] << "This person is evil"
    end 
  end 
end

指定驗證的字段。

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

3.12.validates_each

自定義驗證block。

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

4.常用的驗證選項

4.1.:allow_nil

class Coffee < ActiveRecord::Base 
  validates :size, :inclusion => { :in => %w(small medium large), 
    :message => "%{value} is not a valid size" }, :allow_nil => true
end

4.2.:allow_blank

class Topic < ActiveRecord::Base 
  validates :title, :length => { :is => 5 }, :allow_blank => true
end 
      
Topic.create("title" => "").valid?  # => true
Topic.create("title" => nil).valid? # => true

4.3.:message

非法之後的提示消息

4.4.:on

指定驗證發生的時機。默認的時機是save(創建和更新的時候)。

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 
end

5.條件驗證

class Order < ActiveRecord::Base 
  validates :card_number, :presence => true, :if => :paid_with_card? 
      
  def paid_with_card? 
    payment_type == "card"
  end 
end
class Person < ActiveRecord::Base 
  validates :surname, :presence => true, :if => "name.nil?"
end
class Account < ActiveRecord::Base 
  validates :password, :confirmation => true, 
    :unless => Proc.new { |a| a.password.blank? } 
end
class User < ActiveRecord::Base 
  with_options :if => :is_admin? do |admin| 
    admin.validates :password, :length => { :minimum => 10 } 
    admin.validates :email, :presence => true
  end 
end

6.自定義驗證

6.1.自定義驗證類

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
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 
      
class Person < ActiveRecord::Base 
  validates :email, :presence => true, :email => true
end

6.2.自定義驗證方法

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 
      
  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
class Invoice < ActiveRecord::Base 
  validate :active_customer, :on => :create 
      
  def active_customer 
    errors.add(:customer_id, "is not active") unless customer.active? 
  end 
end
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
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved