yskkin
yskkin

Reputation: 890

Using Rails 5 enum with PG array

I'm trying to use Rails' enum with PostgreSQL's array column.

class Post < ActiveRecord::Base
  enum tags: { a: 0, b: 1, c: 2 }, array: true
end

However the above code does not work

Is there any way to using enum on array column like arrtibute supporting array: true?

EDIT

I would like to see that the following test case passes, but actually it fails.

# frozen_string_literal: true

begin
  require "bundler/inline"
rescue LoadError => e
  $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
  raise e
end

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  # Activate the gem you are reporting the issue against.
  gem "activerecord", "5.1.4"
  gem "pg"
end

require "active_record"
require "minitest/autorun"
require "logger"

# Ensure backward compatibility with Minitest 4
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "postgresql", database: "test")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :products, force: true do |t|
    t.integer :type_delivery, default: [], array: true, limit: 8
  end
end

class Product < ActiveRecord::Base
  enum type_delivery: { a: 1, b: 2, c: 3, d: 5 }, array: true
end

class BugTest < Minitest::Test
  def test_array_enum
    product = Product.create!(type_delivery: %w[a b c])
    assert_equal products.type_delivery, %w[a b c]
  end
end

error is:

/usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.1.4/lib/active_record/enum.rb:172:in `block (2 levels) in enum': undefined method `each_with_index' for true:TrueClass (NoMethodError)
    from /usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.1.4/lib/active_record/enum.rb:171:in `module_eval'
    from /usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.1.4/lib/active_record/enum.rb:171:in `block in enum'
    from /usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.1.4/lib/active_record/enum.rb:154:in `each'
    from /usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.1.4/lib/active_record/enum.rb:154:in `enum'
    from guides/bug_report_templates/active_record_gem.rb:38:in `<class:Product>'
    from guides/bug_report_templates/active_record_gem.rb:37:in `<main>'

Upvotes: 12

Views: 7527

Answers (4)

morgoth
morgoth

Reputation: 1451

You can use array_enum gem that was created just for this usecase https://github.com/freeletics/array_enum

Upvotes: 7

codenamev
codenamev

Reputation: 2208

Rails' enum api maps attributes to a single integer column. If you wanted, you could create your own bit mask to support multiple attributes (like is often done with user roles).

I think what you're looking for is the attributes api and you could even implement a custom ActiveRecord::Type to handle validation.

Upvotes: 1

Marcus Ilgner
Marcus Ilgner

Reputation: 7231

Since this question is still without answer, this is how it can be done:

First, there is no array: true option on the enum method, just leave it out.

Second, add a custom scope to retrieve the products matching the delivery

scope :with_delivery_type, ->(*delivery_types) do
  normalized = Array(delivery_types).flatten
  where('delivery_types @> ?', "{#{normalized.join(',')}}")
end

Last but not least, I'd recommend using a string or Postgres enum type instead of integer columns. Integer columns are problematic because for one, to read it, one needs the source code of the application that wrote the record (the version at the time of insertion) and second it is unnecessarily hard to remove or replace values.

Upvotes: 2

gilcierweb
gilcierweb

Reputation: 2744

Rails 5++

rails generate migration AddTypeDeliveryToProducts type_delivery:integer

class AddTypeDeliveryToProducts < ActiveRecord::Migration[5.1]
  def change
    add_column :products, :type_delivery, :integer, array:true, default: [], limit: 8
  end
end

class Product < ApplicationRecord
  enum type_delivery: { a: 1, b: 2, c: 3, d: 4 }
end

Upvotes: -3

Related Questions