Rails Error Updating Enum on Join Table

2023/10/31


Enums in Rails

Rails offers some really useful built-in features for enums which make working with something like enum statuses fairly straightforward.

Like this example from the Rails documentation:

class Conversation < ActiveRecord::Base
  enum status: [ :active, :archived ]
end

# conversation.update! status: 0
conversation.active!
conversation.active? # => true
conversation.status  # => "active"

# conversation.update! status: 1
conversation.archived!
conversation.archived? # => true
conversation.status    # => "archived"

# conversation.status = 1
conversation.status = "archived"

conversation.status = nil
conversation.status.nil? # => true
conversation.status      # => nil

It also gives you ready made scopes which is great because you've likely added a status in order to filter records by them anyway.

Conversation.active
Conversation.archived

Issue

With this in mind, I thought this would be a perfect solution to a task of adding a status to an existing system.

For some context, a user in this app has many events through a join table like so:

class User < ApplicationRecord
  has_many :users_events
  has_many :events, through: :users_events
end

class Event < ApplicationRecord
  has_many :users_events
  has_many :users, through: :users_events
end

class UsersEvent < ApplicationRecord
  belongs_to :user
  belongs_to :event

  # The new statuses I added in (migration not shown)
  enum :status, {
    pending: 0,
    approved: 1,
    rejected: 2
  }
end

However, while testing this functionality I kept getting a PG error:

PG::SyntaxError: ERROR:  zero-length delimited identifier at or near """" (ActiveRecord::StatementInvalid)
LINE 1: ...rs_events" SET "status" = $1 WHERE "users_events"."" IS NULL

Thinking this was because I was using the in built .approved! methods, I tried using .update(status: :approved) and even tried explicitly setting an integer value. Not having all the rails enum methods wouldn't be ideal but at least it'd work right?

Still got the same error. If I switched to doing a .status = 1 and .save I wouldn't get an error but the record obviously wouldn't be updated.

Solution

I couldn't find anything online about this issue, which is partly the reason for writing this post.

Because I'd used enums like this elsewhere in this app, I suspected this was something to do with the fact that I was adding the status to a joins table.

Looking back at the migrations showed that rails had created the table with a user_id and event_id, this works perfectly for doing things like user.events etc. but I guessed that the absence of a primary key was somehow eluding the ActiveRecord where sql queries and causing the error as it was looking for an id on the users_events table.

To test this theory I retroactively added in a primary key:

def change
  add_column :users_events, :id, :serial, null: false

  add_index :users_events, :id, unique: true

  change_column :users_events, :id, :integer, null: false
end

But I had the same error.

As it turns out, although the model now had a primary id, rails was still ignoring it and treating it as a standard joins table (why wouldn't it?).

To fix this I had to explicitly set it in the model.

class UsersEvent < ApplicationRecord
  self.primary_key = :id  # <-- added this line

  belongs_to :user
  belongs_to :event

  enum :status, {
    pending: 0,
    approved: 1,
    rejected: 2
  }
end

And finally everything worked, including all the fancy rails enum methods too.

I'm not sure if this was the best solution to the problem or if this was the wrong way to structure things but it fixed my issue.

Feel free to reach out if you know more on this or have any feedback on this approach!

< Back to all posts