Ensure uniqueness of a nested value in JSONB in Rails ActiveRecord without self.class.exists?

Viewed 227

Normally, we could use self.class.exists?(api_key: api_key) to check if any other row in the table has a value before we save:

def set_api_key
  self.api_key ||= loop do
    api_key = SecureRandom.hex(24)
    break api_key unless self.class.exists?(api_key: api_key)
  end
end

But in this example, api_key is actually a value on a JSONB column called config:

class User < ApplicationRecord
  before_validation :set_api_key, on: [:create]
  validates :api_key, presence: true, uniqueness: true

  store_accessor :config, :status, :plan, :api_key

  def set_api_key
    self.api_key ||= loop do
      api_key = SecureRandom.hex(24)
      break api_key unless self.class.exists?(api_key: api_key)
    end
  end
end

Assume :status, :plan, :api_key are all top-level attributes of a JSONB column called config.

This results in: ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column users.api_key does not exist

Is there a slick Rails-way of achieving this in a similar way that self.class.exists?(key: value) works? Is there a low-cost SQL query to use instead?

1 Answers

There's special operators for use with json(b) (Postgres Docs)

The ->> will access a jsonb element as a string, ie. config->>api_key instead of config.api_key

You should be able to do this self.class.exists?('config->>api_key = ?', api_key)

Related