强制转换为自定义类型PostgreSQL数组

时间:2018-10-09 00:31:47

标签: ruby-on-rails postgresql

在Rails(5.2)应用程序中,我有一个Project模型,该模型的tags属性定义为Postgresql数组。

  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}']

是否有比从names中的TagType手动删除括号来获得正确的Tag更好的方法?

试图在Rails代码中找到解析数组值但到目前为止没有运气的地方。

2 个答案:

答案 0 :(得分:1)

这是我正在寻找的更通用的版本:

# 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(", ")

以及在项目中其他任何地方将标签作为数组进行管理。

答案 1 :(得分:0)

这是我最终得到的代码

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

希望这会有所帮助。