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

SpiderEvgn · 2020年02月24日 · 最后由 SpiderEvgn 回复于 2020年02月25日 · 2237 次阅读

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,即可判定了

jasl 回复

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

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

SpiderEvgn 关闭了讨论 02月29日 01:26
SpiderEvgn 重新开启了讨论 02月29日 01:26
SpiderEvgn 关闭了讨论 03月05日 15:06
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册