2

I have a project with many items; and it's :dependent => :destroy. I'm trying to tell rails when calling callbacks (specifically the after_destroy of Item), to run ONLY if the Item is destroyed "alone", but all of the project is NOT being destroyed. When the whole project is being destroyed, I actually don't need this after_destroy method (of Item) to run at all.

I don't want to do :dependent => :delete since the Item has many other associations connected to it (with :dependent => :destroy).

It works for me only with class variable, but I wish it would had worked with an instance variable:

class Project < ActiveRecord::Base
  has_many :items, :dependent => :destroy
  before_destroy :destroying_the_project

  def destroying_the_project
    # this is a class variable, but I wish I could had @destroying_me 
    # instead of @@destroying_me. 
    @@destroying_me = true
  end

  def destroying_the_project?
    @@destroying_me
  end
end

class Item < ActiveRecord::Base
  belongs_to :project
  after_destroy :update_related_statuses

  def update_related_statuses
    # I with I could had return if project.destroying_the_project?
    # but since the callback gets the project from the DB, it's another instance,
    # so the instance variable is not relevant here
    return if Project::destroying_the_project?

    # do a lot of stuff which is IRRELEVANT if the project is being destroyed. 
    # this doesn't work well since if we destroy the project, 
    # we may have already destroyed the suites and the entity
    suite.delay.suite_update_status 
    entity.delay.update_last_run
  end
end

The other option I can think of is remove the :dependent => :destroy and manually handle the destroy of the items inside the Project after_destroy method, but it seems too ugly as well, especially since Project has many item types with :dependent => :destroy that would have to shift to that method.

Any ideas would be appreciated

KL-7
  • 46,000
  • 9
  • 87
  • 74
user198026
  • 254
  • 3
  • 9
  • A class variable certainly is not right, since that applies to *all* Projects. You really only want the one project instance... – DGM Jan 01 '12 at 14:48
  • DGM - I agree. This class variable is definitely a bad option. – user198026 Jan 01 '12 at 17:31

2 Answers2

1

I hope that's not the best solution, but at least it works and doesn't introduce any global state via class variables:

class Project < ActiveRecord::Base
  has_many :items
  before_destroy :destroying_the_project

  def destroying_the_project
    Rails.logger.info 'Project#destroying_the_project'
    items.each &:destroy_without_statuses_update
  end
end

class Item < ActiveRecord::Base
  belongs_to :project
  after_destroy :update_related_statuses, 
                :unless => :destroy_without_statuses_update?

  def update_related_statuses
    Rails.logger.info 'Item#update_related_statuses'
  end

  def destroy_without_statuses_update
    @destroy_without_statuses_update = true
    destroy
  end

  def destroy_without_statuses_update?
    !!@destroy_without_statuses_update
  end
end
KL-7
  • 46,000
  • 9
  • 87
  • 74
  • Hi, thanks, this is what I meant by "The other option I can think of is remove the :dependent => :destroy and manually handle the destroy of the items inside the Project after_destroy method", so I know it would work, but I'm trying to find a better solution. thanks! – user198026 Jan 01 '12 at 15:15
  • I also really liked the !!@destroy_without_statuses_update (simple and works!) – user198026 Jan 01 '12 at 15:18
  • @user198026, the problem with `:dependent => :destroy` is that associations are destroyed before even calling any before destroy callbacks on parent model. At least that's how it happens on Rails 3.1. That what makes me wondering why your solution works at all. – KL-7 Jan 01 '12 at 15:29
  • I think that the association are destroyed before only if they're declared before the before_destroy. Actually this is what I show in my example above, so you are right - this code should not work. But actually in my code the has_many :items, :dependent => :destroy line is after the before_destroy declaration. That's why it works. I also agree, it's far from being clean, and relates to all projects. That's why your solution is better anyways, but still I also hope to find a simpler one. – user198026 Jan 01 '12 at 17:13
  • Thank you, I didn't know that order of associations declarations and destroy callbacks matters. Have you seen that mentioned somewhere in rails docs? – KL-7 Jan 01 '12 at 17:59
  • I was surprised too when I read that's the way it works (don't remember where). This is another reason to shift to your code above (more stable code, regardless of where I put the has_many associations). Not getting more results makes me believe your solution is the optimal... – user198026 Jan 02 '12 at 10:07
0

If you don't need to use callbacks when deleting the whole project, you could use delete_all instead of destroy:

Rails :dependent => :destroy VS :dependent => :delete_all

Community
  • 1
  • 1
DGM
  • 26,629
  • 7
  • 58
  • 79
  • Hi, this won't do the job, since I have many other has_many relationships to the Item.rb. If I will do the delete_all, it would just delete the items, without the Item's relations. So if Item has_many :papers, :dependent => :destroy, it won't delete the papaers – user198026 Jan 01 '12 at 15:13
  • Are you sure about that? I thought they cascaded. Only difference being whether callbacks are run... – DGM Jan 01 '12 at 15:34
  • 1
    yes. if you will have delete_all, it will just delete it directly from the DB, without loading the Items class, and it won't delete Items' association. – user198026 Jan 02 '12 at 10:04