数据验证 给出一个实例 model -- User
class User < ActiveRecord::Base
validate :first_name, presence: true # case 1
validate :last_name, presence: true, on: :create # case 2
validate :nickname, presence: true, on: :update # case 3
validates :balance, numericality: true, on: :admin # case 4 一种定制的 context
end
构造 User 数据:
FactoryGirl.define do
factory :user do
first_name { Faker::Name.first_name }
last_name { Faker::Name.first_name }
nickname { Faker::Name.name }
balance { Faker::Number.number(6) }
factory :user_without_first_name do
first_name nil
end
factory :user_without_last_name do
last_name nil
end
factory :user_without_nickname do
nickname nil
end
factory :user_without_balance do
balance nil
end
factory :user_with_negative_balance do
balance { Faker::Number.negative(10, 20) }
end
end
end
详细解释一下示例中的四种校验
假设:user
是 User 的一个实例。
on: **
的存在), 这种验证在 user
每次 save 的时候都会被触发。user
是新建数据,满足#new_record?为 true
, save 的时候触发。nickname 的验证,同样定义了 context(on: :update), 该验证理论上应该在更新数据 (数据 user#persisted?为 true
) 的时候才会触发,测试中 on: :update
== on: [:create, :update]
, 无论是新建数据还是更新数据都会触发。
# 验证on: :create
# 新建一个没有 last_name 的 user
user_without_last_name = FactoryGirl.build(:user_without_last_name)
user_without_last_name.valid? # => false
# 更新一个没有 last_name 的 user
user_without_last_name.save(validate: false)
user_without_last_name.valid? # => true
# 验证on: :update
# 新建一个没有 nickname 的 user
user_without_nickname = FactoryGirl.build(:user_without_nickname)
user_without_nickname.valid? # => false
# 更新一个没有 nickname 的 user
user_without_nickname.save(validate: false)
user_without_nickname.valid? # => false
balance 的验证,自定义了 context(on: :admin), 这种方式需要特殊的触发方式: context 源码
# Runs all the validations within the specified context. Returns +true+ if
# no errors are found, +false+ otherwise.
#
# If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
# <tt>new_record?</tt> is +true+, and to <tt>:update</tt> if it is not.
#
# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
# some <tt>:on</tt> option will only run in the specified context.
def valid?(context = nil)
context ||= (new_record? ? :create : :update)
output = super(context)
errors.empty? && output
end
使用:
user = FactoryGirl.build(:user)
# valid?
user.valid?(:admin)
# save
user.save(context: :admin)
注意点:
user = FactoryGirl.build(:user)
user.valid?(:admin)
的时候,只会验证first_name, 与balance, 并不会验证last_name nickname
on: :admin 做为允许的条件,可以在保存的时候 #save(context: :admin), 如果默认保存的手做其他的限制,可以:
validates :balance, numericality: true, on: :admin
validates :balance, numericality: { greater_than_or_equal_to: 0 },
on: [:create, :update]
这样 user.save 就会限制 balance 大于 0
context 在 autosave 中的使用: 添加 Article
class Article < ActiveRecord::Base
belongs_to :user, autosave: true
end
构造 Article 数据:
FactoryGirl.define do
factory :article do
user
end
end
使用:
user = FactoryGirl.create(:user)
article = FactoryGirl.create(:article, user: user)
article.user.assign_attributes(balance: -1)
# valid?
article.valid? # => true
# valid?(:admin)
article.valid?(:admin) # => false
# save
article.save # => true
# save(:admin)
article.save(:admin) # => false
context 在测试中写法
require 'rails_helper'
RSpec.describe User, type: :model do
it { expect(build(:user_without_balance)).to be_valid(:admin) }
end