Как контролировать целостность в Rails отношения has_many:through?

Я пытаюсь предотвратить удаление связанной записи, если у нее есть собственные связанные записи. Rails, похоже, подрывает параметр :depend => :restrict и все равно выполняет DELETE SQL. Что мне не хватает?

class User < ActiveRecord::Base
  has_many :user_regions
  has_many :events, :through => :user_regions
  has_many :regions, :through => :user_regions
end

class Region < ActiveRecord::Base
  has_many :user_regions
  has_many :events, :through => :user_regions
end

class UserRegion < ActiveRecord::Base
  belongs_to :user
  belongs_to :region

  has_many :events, :dependent => :restrict
end

class Event < ActiveRecord::Base
  belongs_to :user_region, :readonly => true
  has_one :planner, :through => :user_region, :source => :user
  has_one :region, :through => :user_region
end

Теперь предположим, что у меня есть два региона [R1 и R2], два пользователя [U1 и U2]. U1 был назначен как R1, так и R2 [как UR1 и UR2], а U2 был назначен только R2 [как UR3]. Это дает в общей сложности три UserRegion.

Кроме того, у меня есть события E1, E2 и E3, где E1 находится в UserRegion UR1, E2 — в UserRegion UR2, а E3 — в UserRegion UR3. Это дает U1 два события и U2 одно событие.

ЧТО ДОЛЖНО ПРОИЗОЙТИ: Если я попытаюсь удалить назначение U1 на R2 (т. е. уничтожить UR2), я должен получить сообщение об ошибке, так как U1 имеет события через UR2.

ЧТО СЛУЧИЛОСЬ:

u1 = User.find(1)
urids = u1.regions.map &:id
 => [1,2]
u1.region_ids = [1]
Region Load (0.3ms)  SELECT "regions".* FROM "regions" WHERE "regions"."id" = ? LIMIT 1  [["id", 1]]
Region Load (0.3ms)  SELECT "regions".* FROM "regions" INNER JOIN "user_regions" ON "regions"."id" = "user_regions"."region_id" WHERE "user_regions"."user_id" = 1
 (0.1ms)  begin transaction
SQL (0.3ms)  DELETE FROM "user_regions" WHERE "user_regions"."user_id" = 1 AND "user_regions"."region_id" = 2
 (2.7ms)  commit transaction
 => [1] 

Очевидно, что Rails не выполняет обратные вызовы :destroy для удаляемой области пользователя, потому что, если бы я попытался сделать:

UserRegion.find(2).destroy

Я получаю сообщение об ошибке, и транзакция откатывается.

Что мне нужно сделать, чтобы назначение идентификаторов регионов пользователю не удаляло пользовательские регионы?

Цель здесь состоит в том, чтобы форма «редактировать пользователя» имела список флажков для назначения регионов. Должна быть возможность добавлять регионы пользователю и удалять регионы, в которых у пользователя нет событий.


person Jarrod Carlson    schedule 24.06.2012    source источник


Ответы (2)


Может быть, вы можете сначала выполнить поиск связанных записей и разрешить выполнение строки кода удаления только в том случае, если их количество равно нулю?

def destroy
@hgroup = Hgroup.find(params[:id])
  if 'search for associations' == 0
    @hgroup.destroy
      respond_to do |format|
       format.html { redirect_to hgroups_url }
     end
  else
     'do something else'
  end

конец

Попробуйте что-нибудь подобное.

person OpenCoderX    schedule 24.06.2012
comment
Форма пользователя выглядит примерно так (с использованием SimpleForm): ‹%= f.association :regions, :as =› :check_boxes, :collection =› Region.all %› Это означает, что регионы назначаются пользователю как @user.region_ids = [...] Где я могу подключиться к этому процессу, чтобы проверить существующие события? - person Jarrod Carlson; 24.06.2012
comment
Я бы сделал это в контроллере любым методом, который вы используете в качестве действия «уничтожить». - person OpenCoderX; 24.06.2012
comment
Я только что обновил код из приложения, над которым я работаю. Я не проверял это. - person OpenCoderX; 24.06.2012

Из документации ActiveRecord::Associations в разделе "Удалить или уничтожить?" ' раздел:

Для has_many уничтожение всегда будет вызывать метод уничтожения удаляемых записей, чтобы запускались обратные вызовы. Однако delete либо выполнит удаление в соответствии со стратегией, заданной опцией :dependent, либо, если опция :depend не указана, будет следовать стратегии по умолчанию. Стратегия по умолчанию – :nullify (установка внешних ключей на ноль), за исключением has_many :through, где стратегия по умолчанию – delete_all (удаление записей соединения без запуска их обратных вызовов).

Итак, в моей пользовательской модели выше мне нужно было изменить это:

class User < ActiveRecord::Base
  has_many :user_regions
  has_many :events, :through => :user_regions
  has_many :regions, :through => :user_regions
end

в это:

class User < ActiveRecord::Base
  has_many :user_regions
  has_many :events, :through => :user_regions
  has_many :regions, :through => :user_regions, :dependent => :destroy
end

Это эффективно предотвращает удаление UserRegion, если у пользователя все еще есть события через UserRegion. При попытке сделать это будет вызван ActiveRecord::DeleteRestrictionError.

Обработка этой ошибки на уровне контроллера и настройка моего представления, чтобы это условие никогда не встречалось, — это, конечно, другая история.

person Jarrod Carlson    schedule 24.06.2012