Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/ruby-on-rails/56.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ruby on rails 在PORO中实现验证_Ruby On Rails_Ruby_Validation_Rubygems - Fatal编程技术网

Ruby on rails 在PORO中实现验证

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

我正在尝试用Ruby实现我自己的验证以供实践

下面是一个类
,它有两个验证,我需要在
基类
中实现:

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
define
add\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 %>