Ruby 如何教会小明实现 Rails 的 Validation

chrishyman · 2018年03月20日 · 最后由 jakit 回复于 2018年05月18日 · 7559 次阅读
本帖已被管理员设置为精华贴

本主题来源于公司内部每周分享,我们正在招聘 Ruby 工程师和架构师欢迎查看我们的招聘信息 https://ruby-china.org/topics/35085 或直接投递简历到 [email protected]

前言:直接写一份教程实现未免生涩,所以我尝试用 8bit 漫画的方式写一个教程,渣画技,试试水。

图多不看党可以直接看 github 地址

数学家波利亚说过:「观察未知量!并尽量想出一道你所熟悉的具有相同未知量或者相似题目未知量的题目。」

我们先要尝试归纳出我们需要的或者说已知的内容——「一个 ActiveModel Validator 能实现什么和做到什么?」

  1. 可以通过 validates 类方法来验证需要验证的字段
  2. 具有 Valid? 方法,可以返回布尔值

课后笔记

  • 这里我们实现了一个最基本的 Validation。
  • 事实上 Rails 的 Validation 更加强大,vaild? 方法执行后会更新执行对象的 errors 变量;options 另外还有类似 on: :create 这类的限制选项;除开 valid? 还有 invalid? 和 validate! (该方法会抛出异常)方法。
  • Rails 的 Validation 支持 custom Validator (其实就是少了 validates_with 的实现)

练习题:

如何实现 Custom Validator 呢?

huacnlee 将本帖设为了精华贴。 03月21日 00:12

这让我明白了,我是个图多不看党。。。。然而你的 github,啥都没呀😂

想到了我的世界的三体。。。

banana 回复

看 README.md 的内容~

chrishyman 回复

just the sentence: four branch tell you how to create a validation. in README.md😂

无二成都的招聘咋样了 有什么消息么?投了简历还没反应。

@shadow 正在筹划中,有消息会第一时间和你联系。😄

还清晰的记得这个问题抛出来的时候,我答非所问,失了方寸去解答时的样子。回来之后,花了一段时间才真正去解决这个问题。

实现代码

module Validate

  VALIDATE_RULES = {
    :presence => {
      :condition => /^\S+$/,
      :message => "%{name} cannot be blank"
    },
    :maxlength => {
     :condition => -> (attr, val){ attr.to_s.length <= val },
     :message => "%{name} max length is %{val}"
    }
  }

  def self.included(base)
    class << base; attr_accessor :defined_validates; end
    base.defined_validates = {}
    base.send(:include, InstanceMethods)
    base.extend ClassMethods
  end

  # def self.extended(base)
  #   class << base; attr_accessor :defined_validates; end
  #   base.defined_validates = {}
  #   base.extend ClassMethods
  #   base.send(:include, InstanceMethods)
  # end

  module ClassMethods
    def inherited(base)
      base.defined_validates = defined_validates.deep_dup
      super
    end

    def validates(name, options)
      defined_validates[name.to_s] = options
    end
  end

  module InstanceMethods
    def valid?
      @error_messages = {}

      self.class.defined_validates.each do |name, conditon|
        conditon.each do |rule, value|
          next unless value
          rule = VALIDATE_RULES[rule]
          cond = rule[:condition]
          attr_value = send(name)

          valid = case cond
            when Regexp
              cond === attr_value
            when Proc
              cond.(attr_value, value)
          end

          @error_messages[name] = rule[:message] % {
            name: name,
            val: value
          } unless valid
        end
      end

      @error_messages.size == 0
    end

    def error_messages
      @error_messages || {}
    end
  end
end

class User
  include Validate
  # extend Validate
  attr_accessor :name
  validates :name, :presence => true, :maxlength => 5

  def self.create(options = {})
    user = new.tap do |_|
      options.each do |attr, value|
        _.send("#{attr}=", value)
      end
    end
  end
end

user = User.create(:name => nil)
user.valid?
user.error_messages

思路:

不继承于任何类,所以只能用 include 或 extend 的方法。这里写一个 Validate 的 module,这样任何类只要 include 或 extend 它就有了 validates 的功能了。 实现: validates 是用来验证一个类的实例的,所以我们只要把 validates 的验证条件存在类的变量就可以了

def validates(name, options)
  defined_validates[name.to_s] = options
end

这里有个类变量叫 defined_validates 通过钩子方法把我写好的方法变成了对应类的类方法和实例方法

def self.included(base)
  class << base; attr_accessor :defined_validates; end
  base.defined_validates = {}
  base.send(:include, InstanceMethods)
  base.extend ClassMethods
end

我们把我们定义的 InstanceMethods 中的所有方法 include 了,成了实例方法 定义的 ClassMethods 中的所有方法 extend 了,成了类方法

class << base; attr_accessor :defined_validates; end
    base.defined_validates = {}

这个只要是定义类变量,就是@@defined_validates,并且初始化,后面在 User 中我们的 validates 其实就存在这个变量中的 我们在使用 valid 的时候,再进行判断就可以了,然后再把判断结果输出到实例变量@error_messages

无二是个好公司,自己还有很长的路要走。

railsboy 回复

这问题也把我问蒙了😵

railsboy 回复

棒棒哒👍

这一定是程序猿画的

需要 登录 后方可回复, 如果你还没有账号请 注册新账号