2

I know that in Rails 5 before_destroy callback does not take into account false return value but throw(:abort) instead. You can find more about it here and there and in Rails API docs.

But prepend option still does not work in the following situation. I need to prevent the deletion of a last record in the following model:

class ShopLanguage < ApplicationRecord
  belongs_to :shop
  belongs_to :language
  has_many :descriptions, inverse_of: :shop_language, dependent: :destroy

  validates :language, :shop, presence: true
  validates :language, uniqueness: { scope: :shop, message: "already defined for this shop"}

  before_destroy :check_last_language, prepend: true


  private

  def check_last_language 
    if shop.languages.count < 2
      errors[:base] << "You can't delete the last language"
      false
    end
  end
end

Here is Shop model:

class Shop < ApplicationRecord
  has_many :users
  has_many :links, inverse_of: :shop
  has_many :shop_languages, inverse_of: :shop, dependent: :destroy
  has_many :languages, through: :shop_languages
...

Even if I move the callback upper just before declaring the relations:

class ShopLanguage < ApplicationRecord
  before_destroy :check_last_language, prepend: true
...

it does not work either. Here is what I tried in a rails console:

shop = Shop.first
#<Shop id: 2, category: "Sport", fax: nil, identifier: 1, leader: "XXXXX", modified_by: "batch.update.shops", name: "ENGLOS", opening_date: "1976-07-27", phone: "0320001700", status: "open", created_at: "2018-03-22 16:39:35", updated_at: "2018-03-22 16:39:35"> 

2.4.0 :002 > lang = Language.find(245)
  Language Load (0.4ms)  SELECT  "languages".* FROM "languages" WHERE "languages"."id" = $1 LIMIT $2  [["id", 245], ["LIMIT", 1]]
 => #<Language id: 245, tag: "fr-FR", created_at: "2018-03-22 16:25:59", updated_at: "2018-03-22 16:25:59"> 

shop.languages
  Language Load (1.0ms)  SELECT  "languages".* FROM "languages" INNER JOIN "shop_languages" ON "languages"."id" = "shop_languages"."language_id" WHERE "shop_languages"."shop_id" = $1 LIMIT $2  [["shop_id", 2], ["LIMIT", 11]]
 => #<ActiveRecord::Associations::CollectionProxy [#<Language id: 245, tag: "fr-FR", created_at: "2018-03-22 16:25:59", updated_at: "2018-03-22 16:25:59">]> 


shop.languages.delete(lang)
   (0.4ms)  SAVEPOINT active_record_1
  SQL (0.5ms)  DELETE FROM "shop_languages" WHERE "shop_languages"."shop_id" = $1 AND "shop_languages"."language_id" = 245  [["shop_id", 2]]
   (0.2ms)  RELEASE SAVEPOINT active_record_1
 => [#<Language id: 245, tag: "fr-FR", created_at: "2018-03-22 16:25:59", updated_at: "2018-03-22 16:25:59">] 
2.4.0 :007 > shop.languages.size
   (3.5ms)  SELECT COUNT(*) FROM "languages" INNER JOIN "shop_languages" ON "languages"."id" = "shop_languages"."language_id" WHERE "shop_languages"."shop_id" = $1  [["shop_id", 2]]
 => 0 

So, the callback hasn't been called. The problem is that, as explained in Rails Guides,

5 Skipping Callbacks Just as with validations, it is also possible to skip callbacks by using the following methods:

decrement decrement_counter delete delete_all

So, if I got it right, when I called delete on my collection:

shop.languages.delete(lang)

my callback method will never be called. More of that, Active Record or Active Model callbacks no longer halt when returning false, so the only solution would be to use:

throw(:abort) if shop.languages.count < 2

The question is how to delete properly a record from the table shop_languages without deleting the corresponding language record in the table languages? Using throw(:abort) if ... seems not to be enough readable and convenient way for an API response.

belgoros
  • 3,590
  • 7
  • 38
  • 76
  • For Rails 5, after adding error use `throw(:abort)` instead of `false`. And If association has some depend destroy then use prepend option to run those validation before it. – Sanket Nov 05 '19 at 08:26

1 Answers1

0

I couldn't be sure if this will solve your problem but you could override #destroy:

def destroy
  if ...
    super
  else
    # no-op
  end
end
Kris
  • 19,188
  • 9
  • 91
  • 111