您的位置:首页 > 大数据 > 人工智能

Rails开发细节《六》ActiveRecord Validationa and Callbacks验证和回调

2012-10-09 14:31 429 查看
Rails开发细节《六》ActiveRecord Validationa and Callbacks验证和回调

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.Active Record Validations and Callbacks
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐