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?实例方法来判断对象是否存在于数据库。

  1. class Person < ActiveRecord::Base
  2. end
  3. p = Person.new(:name => "shi")
  4. p.new_record?  #=> false
  5. p.save  #=> true
  6. 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参数也可以跳过验证,要小心使用。

  1. p.save(:validate => false)

2.4.Valid? and Invalid?

通过valid?方法来判断对象是否合法,会触发在model中定义的validates,如果没有错误,返回true,否则返回false代表不合法。

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

ActiveRecord执行验证之后,对象的errors会返回一个集合。如果是合法的,这个集合就是空的。

new之后创建的对象,即使是不合法的,errors集合也是空的,因为在new的时候还没有触发验证。

  1. class Person < ActiveRecord::Base
  2. validates :name, :presence => true
  3. end
  4. >> p = Person.new
  5. => #<Person id: nil, name: nil>
  6. >> p.errors
  7. => {}
  8. >> p.valid?
  9. => false
  10. >> p.errors
  11. => {:name=>["can't be blank"]}
  12. >> p = Person.create
  13. => #<Person id: nil, name: nil>
  14. >> p.errors
  15. => {:name=>["can't be blank"]}
  16. >> p.save
  17. => false
  18. >> p.save!
  19. => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
  20. >> Person.create!
  21. => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank

invalid?和valid?方法相反,它也会触发验证,如果有erros就返回true,否则返回false。

2.5.errors[]

通过errors[:attribute]可以验证在某个指定的属性是否合法,返回的是一个数组,如果这个属性没有问题,返回的数组是空的。

这个方法只在验证发生之后才有用,因为它只是检查errors集合,并不触发验证,只是检查在某一个指定的属性上是否有errors。

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

3.验证辅助工具

ActiveRecord预先定义了很多的验证工具,你可以直接使用它们。定义了常见的验证规则,每次验证失败,都会在errors集合中添加对象,信息和验证的属性相关。

每个验证方法都会接受任意属性的名称,因此你可以在一条验证语句中添加多个属性。

每个验证都接受:on和:message的可选项,定义验证在什么时候触发,在验证失败之后需要在errors中添加什么信息。:on选项的值可以是:save, :create, :update中的一个,:save是默认值。每个验证方法都有默认的提示信息。下面列出一些常见的验证方法。

3.1.acceptance

验证在提交的表单中,用户是否勾选了checkbox。常见的场景包括:用户同意服务条款,确定阅读了一段文本,等类似场景。这种验证在web应用中很常用,通常不需要存入数据库(除非你的数据库有对应的列)。如果不需要存储,那么只是一个虚拟的属性。

  1. class Person < ActiveRecord::Base
  2. validates :terms_of_service, :acceptance => true
  3. end

默认的错误信息是:must be accepted。

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

还可以通过:accept来定义可以接受的值。

3.2.validates_associated

用来验证表关系,当你save对象的时候,验证每个关联。

  1. class Library < ActiveRecord::Base
  2. has_many :books
  3. validates_associated :books
  4. end

注意不要在关系的双方都进行这样的验证,会造成死循环验证。

3.3.confirmation

用来验证两个输入框应该输入相同的内容,例如验证邮件和密码。验证会创建一个虚拟的属性,属性名称以"_confirmation"结尾。

  1. class Person < ActiveRecord::Base
  2. validates :email, :confirmation => true
  3. end

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

  1. <%= text_field :person, :email %>
  2. <%= text_field :person, :email_confirmation %>

这个验证只是在email_confirmation不为nil的是才触发,为了满足需求,还要在email_confirmation中添加:presence验证。

  1. class Person < ActiveRecord::Base
  2. validates :email, :confirmation => true
  3. validates :email_confirmation, :presence => true
  4. end

3.4.exclusion

用来验证一个属性是否不在指定的集合中。这个集合是一个枚举对象集合。

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

:in选项用来指定集合,:in有一个别名:within,你也可以用它实现相同的功能。

3.5.format

用来验证指定的属性是否符合正则表达式,:with来指定正则表达式。

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

3.6.inclusion

用来验证一个属性是否在指定的集合中。这个集合是一个枚举对象集合。

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

:in选项用来指定集合,:in有一个别名:within,你也可以用它实现相同的功能。

3.7.length

用来验证属性的长度。

  1. class Person < ActiveRecord::Base
  2. validates :name, :length => { :minimum => 2 }
  3. validates :bio, :length => { :maximum => 500 }
  4. validates :password, :length => { :in => 6..20 }
  5. validates :registration_number, :length => { :is => 6 }
  6. end
  1. class Person < ActiveRecord::Base
  2. validates :bio, :length => { :maximum => 1000,
  3. :too_long => "%{count} characters is the maximum allowed" }
  4. end
  5. class Essay < ActiveRecord::Base
  6. validates :content, :length => {
  7. :minimum   => 300,
  8. :maximum   => 400,
  9. :tokenizer => lambda { |str| str.scan(/\w+/) },
  10. :too_short => "must have at least %{count} words",
  11. :too_long  => "must have at most %{count} words"
  12. }
  13. end

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

3.8.numericality

验证属性只接受数值类型。

:only_integer => true相当于使用

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

正则表达式。否则将会尝试使用Float转换这个值。

  1. class Player < ActiveRecord::Base
  2. validates :points, :numericality => true
  3. validates :games_played, :numericality => { :only_integer => true }
  4. 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或者空白,空白包括空字符串和空格字符串两种类型。

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

验证关联是必须存在的,只要验证外键就可以。

  1. class LineItem < ActiveRecord::Base
  2. belongs_to :order
  3. validates :order_id, :presence => true
  4. end

验证boolean类型的字段。

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

3.10.uniqueness

验证属性的唯一性。它不会在数据库中创建唯一约束。还是会发生两个不同的数据库连接,创建两个相同值的记录。所以最好在数据库创建唯一约束。

  1. class Account < ActiveRecord::Base
  2. validates :email, :uniqueness => true
  3. end

这个验证发生在执行sql语句的时候,查询是否存在相同的记录。

:scope选项用来限制验证的范围。

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

:case_sensitive选项指定是否大小写敏感。

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

有些数据库是可以配置大小写敏感的。

3.11.validates_with

指定单独的验证类。

  1. class Person < ActiveRecord::Base
  2. validates_with GoodnessValidator
  3. end
  4. class GoodnessValidator < ActiveModel::Validator
  5. def validate(record)
  6. if record.first_name == "Evil"
  7. record.errors[:base] << "This person is evil"
  8. end
  9. end
  10. end

指定验证的字段。

  1. class Person < ActiveRecord::Base
  2. validates_with GoodnessValidator, :fields => [:first_name, :last_name]
  3. end
  4. class GoodnessValidator < ActiveModel::Validator
  5. def validate(record)
  6. if options[:fields].any?{|field| record.send(field) == "Evil" }
  7. record.errors[:base] << "This person is evil"
  8. end
  9. end
  10. end

3.12.validates_each

自定义验证block。

  1. class Person < ActiveRecord::Base
  2. validates_each :name, :surname do |record, attr, value|
  3. record.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/
  4. end
  5. end

4.常用的验证选项

4.1.:allow_nil

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

4.2.:allow_blank

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

4.3.:message

非法之后的提示消息

4.4.:on

指定验证发生的时机。默认的时机是save(创建和更新的时候)。

  1. class Person < ActiveRecord::Base
  2. # it will be possible to update email with a duplicated value
  3. validates :email, :uniqueness => true, :on => :create
  4. # it will be possible to create the record with a non-numerical age
  5. validates :age, :numericality => true, :on => :update
  6. # the default (validates on both create and update)
  7. validates :name, :presence => true, :on => :save
  8. end

5.条件验证

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

6.自定义验证

6.1.自定义验证类

  1. class MyValidator < ActiveModel::Validator
  2. def validate(record)
  3. unless record.name.starts_with? 'X'
  4. record.errors[:name] << 'Need a name starting with X please!'
  5. end
  6. end
  7. end
  8. class Person
  9. include ActiveModel::Validations
  10. validates_with MyValidator
  11. end
  1. class EmailValidator < ActiveModel::EachValidator
  2. def validate_each(record, attribute, value)
  3. unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
  4. record.errors[attribute] << (options[:message] || "is not an email")
  5. end
  6. end
  7. end
  8. class Person < ActiveRecord::Base
  9. validates :email, :presence => true, :email => true
  10. end

6.2.自定义验证方法

  1. class Invoice < ActiveRecord::Base
  2. validate :expiration_date_cannot_be_in_the_past,
  3. :discount_cannot_be_greater_than_total_value
  4. def expiration_date_cannot_be_in_the_past
  5. if !expiration_date.blank? and expiration_date < Date.today
  6. errors.add(:expiration_date, "can't be in the past")
  7. end
  8. end
  9. def discount_cannot_be_greater_than_total_value
  10. if discount > total_value
  11. errors.add(:discount, "can't be greater than total value")
  12. end
  13. end
  14. end
  1. class Invoice < ActiveRecord::Base
  2. validate :active_customer, :on => :create
  3. def active_customer
  4. errors.add(:customer_id, "is not active") unless customer.active?
  5. end
  6. end
  1. ActiveRecord::Base.class_eval do
  2. def self.validates_as_choice(attr_name, n, options={})
  3. validates attr_name, :inclusion => { { :in => 1..n }.merge!(options) }
  4. end
  5. end
  1. class Movie < ActiveRecord::Base
  2. validates_as_choice :rating, 5
  3. end

本文转自 virusswb 51CTO博客,原文链接:http://blog.51cto.com/virusswb/1019696,如需转载请自行联系原作者

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

  1. Rails开发细节《七》ActiveRecord Associations关联

    Rails开发细节<七>ActiveRecord Associations关联 1.为什么需要关联 很多时候,比如说电子商务中的用户和订单,一个用户会有很多的订单,一个订单只属于一个用户, ...

  2. Rails开发细节《一》

    常用命令 rails new new_app cd new_app rake db:create rails server rails generate controller Blog action1 ...

  3. [Ruby on Rails系列]3、初试Rails:使用Rails开发第一个Web程序

    本系列前两部分已经介绍了如何配置Ruby on Rails开发环境,现在终于进入正题啦! Part1.开发前的准备 本次的主要任务是开发第一个Rails程序.需要特别指出的是,本次我选用了一个(Paa ...

  4. LGD模型开发细节|全网首发

    量化风控中始终有个贯穿始终的公式即:ECLPDECL,这三者分别称为: ECL-风险敞口 PD-逾期损失 LGD-违约损失率 上面这三个内容,在番茄风控历史的文章中也稍有提及,特别是ECL与PD写到的 ...

  5. 【开发细节】用C语言基础写学生管理系统(七)

    前情回顾 完成了所有自定义头文件的编写 一.本次目标 完成程序主入口,一一对应功能实现算法,直到所有功能基本上实现 GitHub:https://github.com/ITchujian/Studen ...

  6. ASP.NET2.0自定义控件组件开发 第六章 深入讲解控件的属性

    深入讲解控件的属性持久化(一) 系列文章链接: ASP.NET自定义控件组件开发 第一章 待续 ASP.NET自定义控件组件开发 第一章 第二篇 接着待续 ASP.NET自定义控件组件开发 第一章 第 ...

  7. QT开发(六十四)——QT样式表(二)

    QT开发(六十四)--QT样式表 本文主要翻译自QT官方文档Qt Style Sheets . 五.QT样式表参考 QT样式表支持多种的属性.状态和子控件,使得定制组件的外观成为可能. 1.组件 以下 ...

  8. 利用vagrant快速搭建rails开发环境

    为什么80%的码农都做不了架构师?>>>    Deprecated 前言 当我们学习一门新的语言或技术的时候,最麻烦或比较浪费时间的事情就是搭建开发环境.而搭建开发环境与我们将要学 ...

  9. QT开发(六十六)——登录对话框的验证机制

    QT开发(六十六)--登录对话框的验证机制 一.验证码机制 为了避免被恶意程序***,程序通常要使用安全机制.验证码机制是提供产生随机验证码,由用户识别填写来判断用户有效性的安全机制. 验证码必须动态 ...

  10. docker容器没有apt_使用Docker快速搭建Rails开发环境

    引言 Docker with rails 学习 Ruby On Rails 开发的同学经常会遇到因为电脑系统环境不同,同样的程序在自己这边跑起来没问题,给了其他人之后就是各种依赖或者环境问题,尤其是在 ...

最新文章

  1. 通用分销渠道和通用产品组的解析
  2. 程序员如何面对 HR 面试的 40 个问题
  3. Wait waitpid
  4. 贪心 HDOJ 5090 Game with Pearls
  5. Dubbo学习总结(6)——Dubbo开源现状与未来规划
  6. 使用寄存器点亮LED——编程实战
  7. 如何快速看透一个人?
  8. PAT1021 Deepest Root
  9. 数据从mysql迁移至oracle时知识点记录(一)
  10. BZOJ-4008: [HNOI2015]亚瑟王 (概率期望DP)
  11. bzoj4326 NOIP2015 运输计划
  12. 日常塑料用品有哪些种类?
  13. Python多线程获取上证50成分股交易数据
  14. android 国家代码
  15. 正则表达式限制只能输入中文英文数字
  16. 1.5_全网最全 Windows Java IDE 环境 MyEclipse 10 安装过程和 cracker 以及 cracker 失败或者过期处理还有如何配置界面和使用中文包!
  17. 科学计算机统计说明书,科学计算器的使用方法
  18. JAVA 用户登录图形验证码
  19. ⚡写一个有发音的背单词软件⚡——四六级必过系列
  20. linux脚本编写图形,shell图形化界面脚本实现

热门文章

  1. 极速火箭网络助手怎么用_在检测火箭队方面,神经网络比灰烬更好吗? 如果是这样,如何?...
  2. pymysql.err.OperationalError: 1136, Column count doesn t match value count at row 1
  3. PyCharm+PyTorch0.4.0安装使用
  4. we8iso8859p1 java_字符集WE8ISO8859P1 是不能改为ZHS16GBK的
  5. 广播风暴检测_什么是广播路由算法?如何解决广播风暴?
  6. 新版本安装包需求汇总
  7. 深圳试行“智能行人过街系统”,行人违规将被“拉出来示众”
  8. image转base64
  9. Linux运维之道(大量经典案例、问题分析,运维案头书,红帽推荐)
  10. php生成图形验证码的几种方法