2

In my project there are many models with has_many association and dependant: :destroy flag. Also, each model have other belong_to associations with the dependant: :destroy flag. This models are nested between each other so when a destroy is executed on the top one, Rails triggers cascading destroy on children models.

Apart from that, models have callbacks that execute before_destroy.

The following represents what I described above:

class Model1Example < ActiveRecord::Base
  has_many :model2_examples, :dependent => :destroy

  belongs_to :other1_example, :dependent => :destroy
  belongs_to :other2_example, :dependent => :destroy
end

class Model2Example < ActiveRecord::Base
  belongs_to :model1_example
  has_many :model3_examples, :dependent => :destroy

  belongs_to :other3_example, :dependent => :destroy
  belongs_to :other4_example, :dependent => :destroy

  before_destroy :update_something_on_model1
  before_destroy :check_some_inconsistence
end

class Model3Example < ActiveRecord::Base
  belongs_to :model2_example

  belongs_to :other5_example, :dependent => :destroy
  belongs_to :other6_example, :dependent => :destroy

  before_destroy :update_something_on_model2
  before_destroy :check_some_inconsistence
end

Given that on average Model2Example holds about 100+ instances of Model3Example when the Model1Example destroy is triggered many SQL queries are triggered (10k+) because deletion is record by record and also all rules are executed for every instance...and this takes a lot more than what a user could wait for such a simple action.

I could fix this performance issue by using dependant: :delete_all on the has_many associations instead, because I don't really care that all this rules are executed when I trigger Model1Example destroy.

But the problem is that when I execute (from elsewhere in the app) a Model2Example destroy is in my interest that all rules are executed (specially Model3Example rules for each instance), and the previous mentioned approach brakes this.

Is there a "Rails way" to achieve a performance improvement for this case? Or should I just use SQL for Model1Example deletion?

Also, if I have to use this approach and I wanted to check some basic stuff before destroying Model1Example, where is the best place to do this validation? controller?

Rodrigo Martinez
  • 611
  • 5
  • 14
  • 2
    Have you considered to put all this into a background worker? This question has a very good answer for this sort of problem: http://stackoverflow.com/questions/29179141/handle-dependent-destroy-via-active-jobs – Thomas R. Koll Apr 22 '16 at 17:14
  • Thanks for that @ThomasR.Koll. Actually I did think about that, in fact, it was my first idea, but then I thought: how do I let the user know when deletion is completed? The delete flag is a good idea I haven't thought about, but I would have to go to every place where `Model1Example` is used and filter out the flagged as deleted instances, correct? – Rodrigo Martinez Apr 22 '16 at 18:26
  • Thinking about it, I should also flag the `Model2Example` children instances because I can access them from elsewhere - it wouldn't be nice letting the user access those instances while they're being deleted. Also, if `Model3Example` instances where accesible from elsewhere I'd have to do the same. – Rodrigo Martinez Apr 22 '16 at 18:49
  • 1
    Personally I use a paranoia gem in my models (i'm Mongoid btw) so I have a deleted_at timestamp anyways. I'm sure there's one for ActiveRecord and the likes. – Thomas R. Koll Apr 22 '16 at 18:58
  • @ThomasR.Koll you might as well put your tips in an answer so I can at least upvote your ideas ;) – Rodrigo Martinez Apr 26 '16 at 19:25
  • next time. happy to see you actually want to hand our upvotes :) far too few do that. – Thomas R. Koll Apr 26 '16 at 19:31
  • @RodrigoMartinez if you are using a Rails version compatible with active job you could use after_perform to broadcast the result of the destroy action after the job has finished. At least that's what i do to create progress bars in the UI for background tasks that are not instant. – tommyalvarez Nov 13 '17 at 12:09

0 Answers0