Search code examples
ruby-on-railsactiverecordrakeruby-on-rails-5

ActiveRecord::ReadOnlyRecord: Diff is marked as readonly on destroy_all


I have a rake task that looks like this:

desc "Cleanup Snippets with Empty Diffs"

task cleanup_snippets_with_empty_diffs: :environment do
  Snippet.includes(:diffs).where(diffs: { body: "<div class=\"diff\"></div>"}).destroy_all
end

Yet when I run it, I get this:

$ rake cleanup_snippets_with_empty_diffs
rake aborted!
ActiveRecord::ReadOnlyRecord: Diff is marked as readonly

What could be the cause of this?

Edit 1

Note that my Snippet.rb model looks like this:

class Snippet < ApplicationRecord
  has_many :diffs, dependent: :destroy
end

And Diff.rb like this:

class Diff < ApplicationRecord
  belongs_to :snippet
end

Solution

  • With includes, Rails will determine whether to use multiple queries (using preload) or a single left outer join query (using eager_load). In your case, because your where clause is on the association, Rails will use eager_load and therefore a LEFT OUTER JOIN. Also note that associations loaded via a join are marked as readonly and is the cause of the error you're receiving. The solution is to switch includes to left_joins and set readonly(false) prior to calling destroy_all.

    Snippet.left_joins(:diffs).readonly(false).where(diffs: { body: "<div class=\"diff\"></div>"}).destroy_all
    

    NOTE

    I originally thought you could do

    Snippet.includes(:diffs).readonly(false).where(diffs: { body: "<div class=\"diff\"></div>"}).destroy_all
    

    but this doesn't work for some reason (and don't have enough time to investigate atm). In any case, since using includes would result in a LEFT OUTER JOIN anyways, you can use the above solution to get the desired outcome.