我尝试在Ruby中实现我自己的验证以供实践。
下面是一个类Item
,它有2个验证,我需要在BaseClass
中实现它:
require_relative "base_class"
class Item < BaseClass
attr_accessor :price, :name
def initialize(attributes = {})
@price = attributes[:price]
@name = attributes[:name]
end
validates_presence_of :name
validates_numericality_of :price
end
**我的问题是:**验证validates_presence_of
和validates_numericality_of
将是类方法。我如何访问示例对象来验证这些类方法中的名称和价格数据?
class BaseClass
attr_accessor :errors
def initialize
@errors = []
end
def valid?
@errors.empty?
end
class << self
def validates_presence_of(attribute)
begin
# HERE IS THE PROBLEM, self HERE IS THE CLASS NOT THE INSTANCE!
data = self.send(attribute)
if data.empty?
@errors << ["#{attribute} can't be blank"]
end
rescue
end
end
def validates_numericality_of(attribute)
begin
data = self.send(attribute)
if data.empty? || !data.integer?
@valid = false
@errors << ["#{attribute} must be number"]
end
rescue
end
end
end
end
4条答案
按热度按时间ltqd579y1#
查看ActiveModel,您可以看到在调用
validate_presence_of
时它并不执行实际的验证。presence.rb.它实际上通过
validates_with
为验证器列表(类变量_validators
)创建了一个验证器示例;然后在记录示例化期间通过回调调用此验证器列表。参考:带有. rb和验证. rb。我做了一个上面的简化版本,但它与我所相信的ActiveModel相似。(跳过回调和所有这些)
编辑:就像SimpleLime指出的那样,验证器列表将被共享,如果它们在基类中,这将导致所有项共享属性(如果属性集有任何不同,这显然会失败)。
它们可以被提取到一个单独的
module Validations
中并包含在内,但我在这个答案中没有把它们包含在内。参考文献:
xiozqbni2#
首先,让我们尝试将验证融入模型中,一旦它开始工作,我们将提取它。
我们的起点是
Item
,没有任何验证:我们将添加一个方法
Item#validate
,它将返回一个表示错误消息的字符串数组,如果模型有效,则该数组将为空。验证一个模型意味着迭代所有相关的验证器,在模型上运行它们并收集结果,注意我们提供了一个返回空数组的
Item#validators
的伪实现。验证器是一个响应
#run
并返回一个错误数组(如果有的话)的对象。让我们定义NumberValidator
来验证一个给定的属性是否是Numeric
的一个示例。这个类的每个示例负责验证 * 一个参数 *。我们需要将属性名传递给验证器的构造函数,以使它知道要验证哪个属性:如果我们从
Item#validators
返回这个验证器,并将price
设置为"foo"
,它将按预期工作。让我们将与验证相关的方法提取到模块中。
验证器应该在每个模型的基础上定义。为了跟踪它们,我们将在模型类上定义一个类示例变量
@validators
。它只需要为给定模型指定一个验证器数组。我们需要一些元编程来实现这一点。当我们将任何模型包含到类中时,
included
会在模型上被调用,并接收包含该模型的类作为参数,我们可以使用这个方法在包含时定制类,我们将使用#class_eval
来实现这一点:我们需要一种方法来给模型添加验证器,让
Validation
在模型类上定义add_validator
:现在,我们可以做以下几点:
这应该是一个很好的起点,你还可以做很多进一步的改进:
validate_presence_of
)。FooValidator
,您将自动能够调用validate_foo
)。3bygqnnd3#
如果你的目标是模仿ActiveRecord,那么其他的答案你已经知道了,但是如果你真的想专注于一个简单的PORO,那么你可能需要重新考虑类方法:
如果您以后需要访问这些错误,则需要存储它们:
72qzrwbm4#
我不明白在Ruby中实现PORO验证的意义,我宁愿在Rails中而不是Ruby中实现。
假设您有一个Rails项目,为了模拟PORO的活动记录验证,您还需要具备以下3项:
save
示例方法(从中调用验证)。1.处理PORO上的CRUD的Rails控制器。
1.一个带有脚手架即时消息区域的Rails视图。
提供所有这3个条件,我以如下方式实施PORO确认(为简单起见,仅针对
name
):在您的控制器中,您必须手动处理异常(因为您的PORO在ActiveRecord之外):
在视图中--只是一个普通的scaffold生成的代码,类似于(或类似的):
就这样。简单点。