Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting to custom type a PostgreSQL array

In a Rails (5.2) app I have the Project model with a tags attribute defined as a Postgresql array.

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

Instead of handling tags as strings I'd like to cast them to Tags objects

class Tag
  ...
  attr_reader :name

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

To achieve so I'm trying to use the attributes API that comes with Rails 5.

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

This kind of work, it creates Tags object but the first and last have brackets in the name.

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

Is there a better way than manually removing the brackets from names in TagType to get proper Tags?

Trying to find in Rails code where the array value is parsed but no luck so far.

like image 611
Sig Avatar asked Nov 04 '25 17:11

Sig


2 Answers

This is a more generic version of this, which is what I was looking for:

# 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)

This will enable you to add tags like:

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

class Project < ApplicationRecord
  attribute :tags, :text_array
end

What I wanted to achieve was to be able to add tags both as an array and as a comma separated list like:

Project.new(tags: ["x", "y", "z"] # => tags: ["x", "y", "z"]
Project.new(tags: "x, y, z") # => tags: ["x", "y", "z"]

This will enable you to add multiple tags in a form as a comma separated list:

f.text_area :tags, value: @project.tags.join(", ")

as well as managing tags as an array everywhere else in the project.

like image 100
Johan Avatar answered Nov 07 '25 09:11

Johan


Here the code I ended up with

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

Hope this helps.

like image 29
Sig Avatar answered Nov 07 '25 11:11

Sig



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!