首页 > 解决方案 > 在 Rails JSONB 列中使用数组

问题描述

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

class Printing
  serialize :card_faces, CardFacesSerializer
end

序列化器

class CardFacesSerializer
  include JSONBArraySerializer

  def allowed_attributes
    %i[name image]
  end
end

序列化程序关注

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

评估时:

pr = Printing.first
pr.card_faces = [{hay: 12}]
pr.save!

我收到一个错误:

标准错误:预期 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

标签: ruby-on-railspostgresqlactiverecordjsonb

解决方案


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

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

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

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


推荐阅读