将PostgreSQL数组转换为自定义类型

4
在一个Rails(5.2)应用程序中,我有一个名为Project的模型,其中定义了一个作为Postgresql数组的tags属性。
  create_table :projects do |t|
    ...
    t.text :tags, array: true, default: []
    ...
  end

我希望将标签作为字符串处理的方式改成将其转换为 Tag 对象。

class Tag
  ...
  attr_reader :name

  def initialize(name)
    @name = name
  end
  ...
end

为了实现这一点,我正在尝试使用Rails 5提供的属性API。
class Project < ApplicationRecord
  attribute :tags, TagType.new, array: true
  ...
end

class TagType < ActiveRecord::Type::Value
  def cast(names)
    names.split(',').map { |name| Tag.new(name) }
  end
end

这种工作会创建Tag对象,但第一个和最后一个名称中有括号。

Project.create(tags: ['one', 'two', 'three'])
Project.first.tags.map(&:name) #=> ['{one', 'two', 'three}']

有没有比手动从TagType中删除括号以获得正确的Tag更好的方法?

试图在Rails代码中查找解析数组值的位置,但迄今为止没有成功。


1
尝试从ActiveRecord源码中的connection_adapters/postgresql/oid/array.rb开始,解码数组比简单的String#split调用要复杂一些,因为你需要处理像'{"a b", c, d}'这样的情况。 - undefined
谢谢,那正是我最终使用的。 - undefined
你可能想回答自己的问题,这对未来的寻求者可能是有用的信息。 - undefined
2个回答

4
这是更通用的版本,这正是我所需要的:
# config/initializers/text_array.rb
class TextArrayType < ActiveRecord::Type::Value
  include ActiveModel::Type::Helpers::Mutable

  def cast(value)
    case
    when value.is_a?(Array)
      value
    when value.present?
      value.split(/[\s,]+/)
    else
      []
    end
  end

  def deserialize(value)
    PG::TextDecoder::Array.new.decode(value)
  end

  def serialize(value)
    PG::TextEncoder::Array.new.encode(value)
  end

end

ActiveRecord::Type.register(:text_array, TextArrayType)

这将使您能够添加诸如以下标签:

create_table :projects do |t|
  ...
  t.text :tags, array: true, default: []
  ...
end

class Project < ApplicationRecord
  attribute :tags, :text_array
end

我想实现的目标是能够将标签作为数组和逗号分隔列表添加,例如:
Project.new(tags: ["x", "y", "z"] # => tags: ["x", "y", "z"]
Project.new(tags: "x, y, z") # => tags: ["x", "y", "z"]

这将使您能够在表单中添加多个标签,以逗号分隔的列表形式:
f.text_area :tags, value: @project.tags.join(", ")

此外,在项目的其他地方将标签作为数组进行管理。


3
这是我最终得到的代码。
class TagType < ActiveRecord::Type::Value
  include ActiveModel::Type::Helpers::Mutable

  def cast(name)
    Tag.new(name)
  end

  def deserialize(names)
    PG::TextDecoder::Array.new.decode(names).map { |name| cast(name) }
  end

  def serialize(tags)
    PG::TextEncoder::Array.new.encode(tags.map(&:name))
  end
end

希望这能帮到你。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接