使用RubyOnRails

时间:2017-07-11 14:32:22

标签: ruby-on-rails arrays validation jsonb

如何使用Rails验证对象数组中的每个对象?

我正在我们的Rails应用程序中构建用户配置文件表单。在用户模型内部,我们有基本的字符串属性,但也有一些jsonb字段。 JSONb字段默认为[],因为我们希望在该属性中存储一组对象。以下是简化用户模型属性的示例:

  • name: string
  • email: string
  • education: jsonb, default: []

教育是一系列对象,例如:

[{
  school: 'Harvard university',
  degree: 'Computer Science',
  from_date: 'Tue, 11 Jul 2017 16:22:12 +0200`,
  to_date: 'Tue, 11 Jul 2017 16:22:12 +0200'
},{
  school: 'High school',
  degree: 'Whatever',
  from_date: 'Tue, 11 Jul 2017 16:22:12 +0200`,
  to_date: 'Tue, 11 Jul 2017 16:22:12 +0200'
}]

用户应该可以点击添加学校按钮,通过jquery添加更多字段。那个jquery部分对于这个问题并不重要 - 可能只是解释了为什么我们使用了一个对象数组。

如何验证教育数组中的每个项目,以便我可以将包含验证错误的文本字段标记为红色?我被告知使用FormObject模式可能会有所帮助。我也尝试编写继承自ActiveModel::Validator类的自定义验证器,但主要问题仍然在于,我正在处理数组,而不是实际对象..

感谢您提供任何建设性帮助。

2 个答案:

答案 0 :(得分:7)

您可以通过为他们引入非数据库支持的ActiveModel模型类,将教育记录视为Rails模型层中的一等公民:

class Education
  include ActiveModel::Model

  attr_accessor :school, :degree, :from_date, :to_date

  validates :school, presence: true
  validates :degree, presence: true

  def initialize(**attrs)
    attrs.each do |attr, value|
      send("#{attr}=", value)
    end
  end

  def attributes
    [:school, :degree, :from_date, :to_date].inject({}) do |hash, attr|
      hash[attr] = send(attr)
      hash
    end
  end

  class ArraySerializer
    class << self
      def load(arr)
        arr.map do |item|
          Education.new(item)
        end
      end

      def dump(arr)
        arr.map(&:attributes)
      end
    end
  end
end

然后,您可以透明地序列化和反序列化education模型中的User数组:

class User
   # ...
   serialize :education, Education::ArraySerializer
   # ...
end

此解决方案应该允许您使用内置的Rails验证器验证Education个对象的各个属性,将它们嵌入到嵌套的表单中,等等。

重要提示:我在不对其进行测试的情况下编写了上述代码,因此可能需要进行一些修改。

答案 1 :(得分:1)

也许这样的事情? (可能你会希望将哈希作为字符串而不是符号传递,然后验证项目[字符串]而不是项目[符号]

class MyModel < ActiveRecord::Base
  include ActiveModel::Validations
  validates :education, evalidation: true

  class EvalidationValidator < ActiveModel::EachValidator
    def validate_each(record, attribute, value)
      record.errors[attribute] << "must be a valid array & json" unless (check_json_array(value) rescue nil)
    end
  end

  def check_json_array(value)
     return false unless value.is_a?(Array)
     value.each do |item|
       return false unless item.is_a?(Hash)
       return false if item[:school].blank? || item[:degree].blank? || !valid_date_time(item[:from_date]) || !valid_date_time(item[:to_date])
     end
     true
  end

  def valid_date_time(date)
     #this needs work as it will pass for instance "Tue" as true, since DateTime will parse even single day initials to the current closest date
    (DateTime.parse(date) rescue nil)
  end
end