Reputation: 5950
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 Tag
s 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 Tag
s 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 Tag
s?
Trying to find in Rails code where the array value is parsed but no luck so far.
Upvotes: 4
Views: 1297
Reputation: 41
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.
Upvotes: 4
Reputation: 5950
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.
Upvotes: 3