在 Rails JSONB 列中使用数组

lee*_*our 1 postgresql activerecord ruby-on-rails jsonb

我想在 PostgreSQL 的 JSONB 列中存储对象数组。我正在使用 Rails 5.2。我使用自定义序列化程序来确保分配给 JSONB 字段的值是数组而不是哈希。当我向该字段分配类似内容时遇到错误[{a: 1}]。这是代码:
模型

class Printing
  serialize :card_faces, CardFacesSerializer
end
Run Code Online (Sandbox Code Playgroud)

序列化器

class CardFacesSerializer
  include JSONBArraySerializer

  def allowed_attributes
    %i[name image]
  end
end
Run Code Online (Sandbox Code Playgroud)

序列化器关注点

module JSONBArraySerializer
  extend ActiveSupport::Concern

  def initialize(data)
    return [] if data.blank?

    if data.is_a?(String)
      json = Oj.load(data, symbol_keys: true)
    end
    raise ArgumentError, "#{json} must be [{},{}], not {}" if json.is_a?(Hash)
    # Will only set the properties that are allowed
    json.map do |hash|
      hash.slice(self.allowed_attributes)
    end
  end

  class_methods do
    def load(json)
      return [] if json.blank?
      self.new(json)
    end

    def dump(obj)
      # Make sure the type is right.
      if obj.is_a?(self)
        obj.to_json
      else
       raise StandardError, "Expected #{self}, got #{obj.class}"
      end
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

评估时:

pr = Printing.first
pr.card_faces = [{hay: 12}]
pr.save!
Run Code Online (Sandbox Code Playgroud)

我收到错误:

标准错误:预期的 CardFacesSerializer,得到数组

我认为我不清楚转储/加载是如何工作的。为什么dump在保存过程中被调用?如何修复我的代码以使其正常工作?

更新
我设法让它与序列化器关注的代码一起工作:

module JSONBArraySerializer
  extend ActiveSupport::Concern

  class_methods do
    def load(data)
      return [] if data.blank?

      if data.is_a?(String)
        json = Oj.load(data, symbol_keys: true)
      end
      raise ArgumentError, "#{json} must be [{},{}], not {}" if json.is_a?(Hash)

      # Will only set the properties that are allowed
      json.map do |hash|
        hash.slice(*allowed_attributes)
      end
    end

    def dump(obj)
      # Make sure the type is right.
      if obj.is_a?(Array)
        obj.to_json
      else
       raise ArgumentError, "Expected Array, got #{obj.class}"
      end
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

max*_*max 5

不要对 JSON/JSONB 列使用序列化。

请记住,数据库适配器会为您处理某些序列化任务。例如:PostgreSQL 中的 json 和 jsonb 类型将在 JSON 对象/数组语法和 Ruby Hash 或 Array 对象之间透明地转换。在这种情况下不需要使用序列化。 https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html

serialize是一个旧的 hack,用于在字符串列中存储 JSON/YAML/任何内容。说真的 - 不要使用它。它只会导致双重转换的问题。

您正在做的事情应该由常规模型验证和/或自定义设置器来处理。