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?实例方法来判断对象是否存在于数据库。
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参数也可以跳过验证,要小心使用。
2.4.Valid? and Invalid?
通过valid?方法来判断对象是否合法,会触发在model中定义的validates,如果没有错误,返回true,否则返回false代表不合法。
ActiveRecord执行验证之后,对象的errors会返回一个集合。如果是合法的,这个集合就是空的。
new之后创建的对象,即使是不合法的,errors集合也是空的,因为在new的时候还没有触发验证。
invalid?和valid?方法相反,它也会触发验证,如果有erros就返回true,否则返回false。
2.5.errors[]
通过errors[:attribute]可以验证在某个指定的属性是否合法,返回的是一个数组,如果这个属性没有问题,返回的数组是空的。
这个方法只在验证发生之后才有用,因为它只是检查errors集合,并不触发验证,只是检查在某一个指定的属性上是否有errors。
3.验证辅助工具
ActiveRecord预先定义了很多的验证工具,你可以直接使用它们。定义了常见的验证规则,每次验证失败,都会在errors集合中添加对象,信息和验证的属性相关。
每个验证方法都会接受任意属性的名称,因此你可以在一条验证语句中添加多个属性。
每个验证都接受:on和:message的可选项,定义验证在什么时候触发,在验证失败之后需要在errors中添加什么信息。:on选项的值可以是:save, :create, :update中的一个,:save是默认值。每个验证方法都有默认的提示信息。下面列出一些常见的验证方法。
3.1.acceptance
验证在提交的表单中,用户是否勾选了checkbox。常见的场景包括:用户同意服务条款,确定阅读了一段文本,等类似场景。这种验证在web应用中很常用,通常不需要存入数据库(除非你的数据库有对应的列)。如果不需要存储,那么只是一个虚拟的属性。
默认的错误信息是:must be accepted。
还可以通过:accept来定义可以接受的值。
3.2.validates_associated
用来验证表关系,当你save对象的时候,验证每个关联。
注意不要在关系的双方都进行这样的验证,会造成死循环验证。
3.3.confirmation
用来验证两个输入框应该输入相同的内容,例如验证邮件和密码。验证会创建一个虚拟的属性,属性名称以"_confirmation"结尾。
在view中你可以使用下面的方式。
这个验证只是在email_confirmation不为nil的是才触发,为了满足需求,还要在email_confirmation中添加:presence验证。
3.4.exclusion
用来验证一个属性是否不在指定的集合中。这个集合是一个枚举对象集合。
:in选项用来指定集合,:in有一个别名:within,你也可以用它实现相同的功能。
3.5.format
用来验证指定的属性是否符合正则表达式,:with来指定正则表达式。
3.6.inclusion
用来验证一个属性是否在指定的集合中。这个集合是一个枚举对象集合。
:in选项用来指定集合,:in有一个别名:within,你也可以用它实现相同的功能。
3.7.length
用来验证属性的长度。
size是length的别名,上面的length可以用size代替。
3.8.numericality
验证属性只接受数值类型。
:only_integer => true相当于使用
正则表达式。否则将会尝试使用Float转换这个值。
除了: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或者空白,空白包括空字符串和空格字符串两种类型。
验证关联是必须存在的,只要验证外键就可以。
验证boolean类型的字段。
3.10.uniqueness
验证属性的唯一性。它不会在数据库中创建唯一约束。还是会发生两个不同的数据库连接,创建两个相同值的记录。所以最好在数据库创建唯一约束。
这个验证发生在执行sql语句的时候,查询是否存在相同的记录。
:scope选项用来限制验证的范围。
:case_sensitive选项指定是否大小写敏感。
有些数据库是可以配置大小写敏感的。
3.11.validates_with
指定单独的验证类。
指定验证的字段。
3.12.validates_each
自定义验证block。
4.常用的验证选项
4.1.:allow_nil
4.2.:allow_blank
4.3.:message
非法之后的提示消息
4.4.:on
指定验证发生的时机。默认的时机是save(创建和更新的时候)。
5.条件验证
6.自定义验证
6.1.自定义验证类
6.2.自定义验证方法
参考文献
1.Active Record Validations 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
相关文章推荐
- rails active record validation and callbacks
- http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
- "Integration" between Rails' ActiveRecord and Java's Hibernate - Stack Overflow
- C# and .NET MongoDB Driver 开发细节
- 回发或回调参数无效。在配置中使用 或在页面中使用 启用了事件验证。出于安全目的,此功能验证回发或回调事件的参数是否来源于最初呈现这些事件的服务器控件。如果数据有效并且是预期的,则使用 ClientScriptManager.RegisterForEventValidation 方法来注册回发或回调数据以进行验证。
- Rails执行数据库回滚时报错:ActiveRecord::IrreversibleMigration exception
- 回发或回调参数无效。在配置中使用 <pages enableEventValidation="true"/> 或在页面中使用 <%@ Page EnableEventValidation="true" %> 启用了事件验证。
- Rails/ActiveRecord order by Array
- How to use ActiveRecord without Rails
- 微信开发,回调模式开启及服务端验证
- Castle ActiveRecord 学习之 .net快速开发 (1)
- Rails开发细节《八》Rails应用的安全
- 使用 Castle ActiveRecord 开发发现的一些问题
- 回发或回调参数无效。在配置中使用 enableEventValidation=true或在页面中使用 启用了事件验证。
- 设置短信验证码开发的回调地址-短信平台验证码开发9
- Rails开发细节《四》Transactions事务
- Castle ActiveRecord学习实践(8):数据有效性的验证
- 回发或回调参数无效。ClientScriptManager.RegisterForEventValidation 方法来注册回发或回调数据以进行验证。
- Rails开发细节《七》ActiveRecord Associations关联
- Castle ActiveRecord学习实践(9):数据有效性的验证