Rails scope by absence of has_one polymorphic association

960 Views Asked by At

I have records that, instead of being deleted, are invalidated by creating an entry in a polymorphic record_invalidations table.

The setup looks like this:

class RecordInvalidation < ActiveRecord::Base
  belongs_to :archivable, polymorphic: true
end


class Record < ActiveRecord::Base
  has_one :invalidation, class_name: "RecordInvalidation", as: :archivable

  def self.default_scope
    where(invalidation: nil)
  end
end

(There are multiple record classes, hence the polymorphic relation.)

Question: How do I make a scope, that returns records based on the absence of entries in another class pointing back at the class in question.

The current code does not work because the relation pointer is in the records_invalidations table, not the records table.

UPDATE 2

The record_invalidation class matches the table name just fine, I think the problem is the column name. Here's the (simplified) schema:

create_table "records", force: :cascade do |t|
  t.text     "content",        null: false
end

create_table "record_invalidations", force: :cascade do |t|
  t.integer  "archivable_id"
  t.string   "archivable_type"
end

UPDATE 3

I see know why the first error was happening, so I removed that update. Now I am passing the correct table name that I am joining in the SQL query, but I have a number of arguments mismatch. Can I pass another argument to the where clause?

self.joins(:invalidation).where('record_invalidations.id is null')

Gives:

ActiveRecord::StatementInvalid: PG::ProtocolViolation: ERROR:  bind 
message supplies 1 parameters, but prepared statement "a56" requires 2

: SELECT  "records".* FROM "records" 
    INNER JOIN "record_invalidations" ON 
    "record_invalidations"."archivable_id" = "records"."id" AND 
    "record_invalidations"."archivable_type" = $1 WHERE 
    (record_invalidations.id is null) AND "plans"."id" = $2 LIMIT 1
1

There are 1 best solutions below

0
Soren Berg On

This may not be the most efficient way, but it works:

def self.default_scope
  # Only show records that were not invalidated
  # Makes a list of all invalidated record ids in a subquery then checks for id
  where.not(:id => RecordInvalidation.select(:archivable_id)
                                     .where(archivable_type: self.name)
                                     .uniq)
end

Thanks to rails scope to check if association does NOT exist

I just added the required filtering for the polymorphic association to that answer.

If anyone has a better solution feel free to add and I will accept that instead of my own.