新手问题 [已解决] custom validator 在 model 保存后报错

SpiderEvgn · February 24, 2020 · Last by SpiderEvgn replied at February 25, 2020 · 2525 hits

Rails 版本 6.0.1

  • user.rb
class DateValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    rawValue = record.send(attribute.to_s + '_before_type_cast')

    if (Date.parse(rawValue) rescue ArgumentError) == ArgumentError
      record.errors.add attribute, :data_error
    end

  end
end

class User < ApplicationRecord
  validates :date_of_birth, date: true
end
  • rails console:
irb(main):001:0> user = User.new date_of_birth: '2020-02-02'
irb(main):002:0> user.valid?
=> true
irb(main):003:0> user.save
   (0.2ms)  BEGIN
  User Create (0.3ms)  INSERT INTO "users" ("date_of_birth") VALUES ($1, $2, $3) RETURNING "id"  [["date_of_birth", "2020-02-02"], ["created_at", "2020-02-24 05:12:27.657736"], ["updated_at", "2020-02-24 05:12:27.657736"]]
   (0.9ms)  COMMIT
=> true
irb(main):004:0> user.valid?
=> false
irb(main):005:0> user.errors
=> #<ActiveModel::Errors:0x00005606b0872eb0 @base=#<User id: 1, date_of_birth: "2020-02-02", created_at: "2020-02-24 05:12:27", updated_at: "2020-02-24 05:12:27">, @messages={:date_of_birth=>["日期有误"]}, @details={:date_of_birth=>[{:error=>:data_error}]}>

谁知道到底哪里出了问题?

如果你想校验日期格式 https://github.com/adzap/validates_timeliness 用这个就好

如果你要想练习,最简单的办法就是,你先搞明白 rawValue = record.send(attribute.to_s + '_before_type_cast') 出来的值和类型是什么 (取 attribute.to_s + '_before_type_cast' 的意图没看懂)

puts rawValue
puts rawValue.class

看一看

继续,做做测试,比如

2.6.5 :009 > User.first.created_at_before_type_cast
  User Load (0.5ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
 => 2020-02-22 16:33:42 UTC
2.6.5 :010 > Date.parse User.first.created_at_before_type_cast
  User Load (0.5ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
Traceback (most recent call last):
        2: from (irb):10
        1: from (irb):10:in `parse'
TypeError (no implicit conversion of Time into String)

2.6.5 :011 > Date.parse User.first.created_at.to_date
  User Load (0.5ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
Traceback (most recent call last):
        3: from (irb):11
        2: from (irb):11:in `rescue in irb_binding'
        1: from (irb):11:in `parse'
TypeError (no implicit conversion of Date into String)

可见字段类型如果是 Datetime 甚至就是 Date 一样可以引发异常,这明显违背了这个验证器的意图。

这里正确的做法是用专门的库(开头提了),或者利用 ActiveRecord::Type

2.6.5 :012 > ActiveRecord::Type.lookup(:date).cast("x")
 => nil

2.6.5 :014 > ActiveRecord::Type.lookup(:date).cast('2020-02-02')
 => Sun, 02 Feb 2020

2.6.5 :015 > ActiveRecord::Type.lookup(:date).cast(Date.today)
 => Tue, 25 Feb 2020

2.6.5 :016 > ActiveRecord::Type.lookup(:date).cast(Time.now)
 => Tue, 25 Feb 2020

那么只要判断返回值是否为 nil,即可判定了

Reply to jasl

我明白了,在第一次新建模型 create 的时候 rawValue 获取到的是用户 input 输入,是 String。保存之后变成 Date 或者 DateTime,此时如果再去验证这个用户实例,获取到的 rawValue 就是保存后的 Date 或者 DateTime 格式了。

的确是验证器逻辑有问题,感谢建议!

SpiderEvgn closed this topic. 29 Feb 01:26
SpiderEvgn reopened this topic. 29 Feb 01:26
SpiderEvgn closed this topic. 05 Mar 15:06
You need to Sign in before reply, if you don't have an account, please Sign up first.