Ruby on rails 在PORO中实现验证
我正在尝试用Ruby实现我自己的验证以供实践 下面是一个类Ruby on rails 在PORO中实现验证,ruby-on-rails,ruby,validation,rubygems,Ruby On Rails,Ruby,Validation,Rubygems,我正在尝试用Ruby实现我自己的验证以供实践 下面是一个类项,它有两个验证,我需要在基类中实现: require_relative "base_class" class Item < BaseClass attr_accessor :price, :name def initialize(attributes = {}) @price = attributes[:price] @name = attributes[:name] end validat
项
,它有两个验证,我需要在基类
中实现:
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
查看ActiveModel,您可以看到,当调用
validate\u presence\u of
时,它不会执行实际的验证。参考文献:
它实际上通过validates_with
,将一个验证器实例创建到一个验证器列表(这是一个类变量\u validators
);然后在记录的实例化过程中通过回调调用验证程序列表。参考:和
我制作了上述内容的简化版本,但它与我所相信的ActiveModel类似。(跳过回调等)
编辑:正如SimpleTime所指出的,验证器列表将被共享,如果它们在基类中,则会导致所有项共享属性(如果属性集有任何不同,则显然会失败) 可以将它们提取到单独的
模块验证中
,并将其包括在内,但我将它们留在了这个答案中
require_relative "base_class"
class Item < BaseClass
attr_accessor :price, :name
@@_validators = []
def initialize(attributes = {})
super()
@price = attributes[:price]
@name = attributes[:name]
end
def self.validates_presence_of(attribute)
@@_validators << PresenceValidator.new(attribute)
end
validates_presence_of :name
def valid?
@@_validators.each do |v|
v.validate(self)
end
@errors.empty?
end
end
p Item.new(name: 'asdf', price: 2).valid?
p Item.new(price: 2).valid?
需要相对的“基本类”
类项<基类
属性存取器:价格,:名称
@@_验证程序=[]
def初始化(属性={})
超级()
@价格=属性[:价格]
@名称=属性[:名称]
结束
def self.验证(属性)的存在
@@_validators查看ActiveModel,您可以看到当调用validate\u presence\u of
时,它不会执行实际的验证。参考文献:
它实际上通过validates_with
,将一个验证器实例创建到一个验证器列表(这是一个类变量\u validators
);然后在记录的实例化过程中通过回调调用验证程序列表。参考:和
我制作了上述内容的简化版本,但它与我所相信的ActiveModel类似。(跳过回调等)
编辑:正如SimpleTime所指出的,验证器列表将被共享,如果它们在基类中,则会导致所有项共享属性(如果属性集有任何不同,则显然会失败)
可以将它们提取到单独的模块验证中
,并将其包括在内,但我将它们留在了这个答案中
require_relative "base_class"
class Item < BaseClass
attr_accessor :price, :name
@@_validators = []
def initialize(attributes = {})
super()
@price = attributes[:price]
@name = attributes[:name]
end
def self.validates_presence_of(attribute)
@@_validators << PresenceValidator.new(attribute)
end
validates_presence_of :name
def valid?
@@_validators.each do |v|
v.validate(self)
end
@errors.empty?
end
end
p Item.new(name: 'asdf', price: 2).valid?
p Item.new(price: 2).valid?
需要相对的“基本类”
类项<基类
属性存取器:价格,:名称
@@_验证程序=[]
def初始化(属性={})
超级()
@价格=属性[:价格]
@名称=属性[:名称]
结束
def self.验证(属性)的存在
@@_验证器首先,让我们尝试将验证烘焙到模型中。一旦它起作用,我们就会提取出来
我们的出发点是项目
,没有任何形式的验证:
class Item
attr_accessor :name, :price
def initialize(name: nil, price: nil)
@name = name
@price = price
end
end
我们将添加一个方法Item#validate
,该方法将返回表示错误消息的字符串数组。如果模型有效,则数组将为空
class Item
attr_accessor :name, :price
def initialize(name: nil, price: nil)
@name = name
@price = price
end
def validate
validators.flat_map do |validator|
validator.run(self)
end
end
private
def validators
[]
end
end
验证模型意味着迭代所有相关的验证器,在模型上运行它们并收集结果。注意,我们提供了一个返回空数组的Item#validators
的虚拟实现
验证器是响应#run
并返回错误数组(如果有)的对象。让我们定义NumberValidator
,它验证给定属性是否是Numeric
的实例。此类的每个实例负责验证单个参数。我们需要将属性名称传递给验证器的构造函数,以使其知道要验证的属性:
class NumberValidator
def initialize(attribute)
@attribute = attribute
end
def run(model)
unless model.public_send(@attribute).is_a?(Numeric)
["#{@attribute} should be an instance of Numeric"]
end
end
end
如果我们从Item#validators
返回此验证器,并将price
设置为“foo”
,它将按预期工作
让我们将与验证相关的方法提取到一个模块中
module Validation
def validate
validators.flat_map do |validator|
validator.run(self)
end
end
private
def validators
[NumberValidator.new(:price)]
end
end
class Item
include Validation
# ...
end
验证器应该在每个模型的基础上定义。为了跟踪它们,我们将在模型类上定义一个类实例变量@validators
。它将通过为给定模型指定的一组验证器来实现。我们需要一些元编程来实现这一点
当我们将任何模型包含到一个类中时,将对该模型调用included
,并接收该模型作为参数包含在其中的类。我们可以使用此方法在包含时自定义类。我们将使用#class_eval
来执行此操作:
module Validation
def self.included(klass)
klass.class_eval do
# Define a class instance variable on the model class.
@validators = [NumberValidator.new(:price)]
def self.validators
@validators
end
end
end
def validate
validators.flat_map do |validator|
validator.run(self)
end
end
def validators
# The validators are defined on the class so we need to delegate.
self.class.validators
end
end
我们需要一种向模型中添加验证器的方法。让我们在模型类上进行Validation
defineadd\u validator
:
module Validation
def self.included(klass)
klass.class_eval do
@validators = []
# ...
def self.add_validator(validator)
@validators << validator
end
end
end
# ...
end
这应该是一个很好的起点。您还可以进行许多进一步的增强:
- 更多的验证器
- 可配置验证器
- 条件验证器
- 用于验证器的DSL(例如,
validate\u presence\u of
)
- 自动验证程序发现(例如,如果您定义
foovalidater
,您将能够自动调用validate\u foo
)
首先,让我们尝试将验证烘焙到模型中。一旦它起作用,我们就会提取出来
我们的出发点是项目
,没有任何形式的验证:
class Item
attr_accessor :name, :price
def initialize(name: nil, price: nil)
@name = name
@price = price
end
end
我们将添加一个方法Item#validate
,该方法将返回表示错误消息的字符串数组。如果模型有效,则数组将为空
class Item
attr_accessor :name, :price
def initialize(name: nil, price: nil)
@name = name
@price = price
end
def validate
validators.flat_map do |validator|
validator.run(self)
end
end
private
def validators
[]
end
end
验证模型意味着迭代所有相关的验证器,在模型上运行它们并收集结果。注意,我们提供了一个返回空数组的Item#validators
的虚拟实现
验证器是响应#run
并返回错误数组(如果有)的对象。让我们定义NumberValidator
,它验证给定属性是否是Numeric
的实例。此类的每个实例负责验证单个参数。我们需要将属性名传递给验证器的构造函数,使其知道哪个属性是
class Item
include Validation
attr_accessor :name, :price
add_validator NumberValidator.new(:price)
def initialize(name: nil, price: nil)
@name = name
@price = price
end
end
class Item < BaseClass
attr_accessor :price, :name
def initialize(attributes = {})
@price = attributes[:price]
@name = attributes[:name]
end
# validators are defined in BaseClass and are expected to return
# an error message if the attribute is invalid
def valid?
errors = [
validates_presence_of(name),
validates_numericality_of(price)
]
errors.compact.none?
end
end
class Item < BaseClass
attr_reader :errors
# ...
def valid?
@errors = {
name: [validates_presence_of(name)].compact,
price: [validates_numericality_of(price)].compact
}
@errors.values.flatten.compact.any?
end
end
require_relative "base_class"
class Item < BaseClass
attr_accessor :price, :name
include ActiveModel::Validations
class MyValidator
def initialize(attrs, record)
@attrs = attrs
@record = record
end
def validate!
if @attrs['name'].blank?
@record.errors[:name] << 'can\'t be blank.'
end
raise ActiveRecord::RecordInvalid.new(@record) unless @record.errors[:name].blank?
end
end
def initialize(attributes = {})
@price = attributes[:price]
@name = attributes[:name]
end
# your PORO save method
def update_attributes(attrs)
MyValidator.new(attrs, self).validate!
#...actual update code here
save
end
end
class PorosController < ApplicationController
rescue_from ActiveRecord::RecordInvalid do |exception|
redirect_to :back, alert: exception.message
end
...
end
<%= form_with(model: poro, local: true) do |form| %>
<% if poro.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(poro.errors.count, "error") %> prohibited this poro from being saved:</h2>
<ul>
<% poro.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name, id: :poro_name %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>