0
votes

I've got some STI in my data model. There are two types of Task records: PrimaryTask and SecondaryTask. So my ActiveRecord models look like this:

class Task < ActiveRecord::Base
end

class PrimaryTask < Task
  has_many :secondary_tasks
end

class SecondaryTask < Task
  belongs_to :primary_task
end

I want to provide a way to "promote" a SecondaryTask to a PrimaryTask permanently (as in, persisted in the database). From perusing the docs, looks like the #becomes! method is what I want, but I can't get it to save the changes in the database.

id = 1
secondary_task = SecondaryTask.find(id)
primary_task = secondary_task.becomes!(PrimaryTask)

primary_task.id          # => 1
primary_task.class       # => PrimaryTask
primary_task.type        # => "PrimaryTask"
primary_task.new_record? # => false
primary_task.changes     # => { "type"=>[nil,"PrimaryTask"] }

primary_task.save!       # => true
primary_task.reload      # => raises ActiveRecord::RecordNotFound: Couldn't find PrimaryTask with id=1 [WHERE "tasks"."type" IN ('PrimaryTask')]

# Note: secondary_task.reload works fine, because the task's type did not change in the DB

Any idea what's up? I tried the following things, to no avail. Am I misunderstanding becomes!?

  • Force the record to be 'dirty' in case the save! call was a no-op because none of the attributes were marked dirty (primary_task.update_attributes(updated_at: Time.current) -- didn't help)
  • Destroy secondary_task in case the fact that they both have the same id was a problem. Didn't help. The SecondaryTask record was deleted but no PrimaryTask was created (despite the call to save! returning true)

UPDATE 1

The logs show the probable issue:

UPDATE "tasks" SET "type" = $1 WHERE "tasks"."type" IN ('PrimaryTask') AND "tasks"."id" = 2  [["type", "PrimaryTask"]]

So the update is failing because the WHERE clause causes the record not to be found.

1
is showing something the log?Aguardientico
Aha! Good question, didn't even think of looking there. Updating question...Nathan Wallace
Did you tried using #becomes instead of #becomes!?Aguardientico

1 Answers

0
votes

Figured it out. Turns out there was a bug in ActiveRecord version 4.0.0. It has since been patched. The key change this patch introduced was to set the changes correctly in both instances. So now you can call save on the original instance (in my example secondary_task) and it will change the type in the database. Note that calling save on the new instance (for me primary_task) will NOT save the changes, because of the behavior described in the question: it will include a WHERE clause in the SQL UPDATE call that will cause the record not to be found and thus the call to do nothing.

Here's what works with ActiveRecord > 4.1.0:

id = 1
secondary_task = SecondaryTask.find(id)
primary_task = secondary_task.becomes!(PrimaryTask)

secondary_task.changes # => { "type"=>["SecondaryTask","PrimaryTask"] }
primary_task.changes   # => { "type"=>["SecondaryTask","PrimaryTask"] }

secondary_task.save!   # => true

primary_task.reload    # => works because the record was updated as expected
secondary_task.reload  # => raises ActiveRecord::RecordNotFound, as expected