ActiveRecord интересный кейс?

class A < ActiveRecord::Base
  has_many :ab_relationships
  has_many :bs, through: :ab_relationships, source: :b
end

class B < ActiveRecord::Base
  has_many :ab_relationships
  has_many :as, through: :ab_relationships, source: :a
end

class AbRelationship < ActiveRecord::Base
  belongs_to :a
  belongs_to :b

  validate :ololo_validator

private
  def ololo_validator
    if AbRelationship.where(a: self.a).first.present?
      errors.add(:ab_relationships, "error")
    end
  end
end

Теперь если сделать
a = A.create(b_ids: [ 12 ]) # реальные ids

то a.valid? будет true
но a.presisted? будет false
2.2.2 :005 > B.create
   (0.1ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "bs" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", "2015-10-10 08:46:40.136477"], ["updated_at", "2015-10-10 08:46:40.136477"]]
   (1.7ms)  commit transaction
 => #<B id: 2, created_at: "2015-10-10 08:46:40", updated_at: "2015-10-10 08:46:40"> 
2.2.2 :006 > a = A.create(b_ids: [B.first.id])
  B Load (0.2ms)  SELECT  "bs".* FROM "bs"  ORDER BY "bs"."id" ASC LIMIT 1
  B Load (0.1ms)  SELECT  "bs".* FROM "bs" WHERE "bs"."id" = ? LIMIT 1  [["id", 1]]
   (0.1ms)  begin transaction
  AbRelationship Load (0.1ms)  SELECT  "ab_relationships".* FROM "ab_relationships" WHERE "ab_relationships"."a_id" IS NULL  ORDER BY "ab_relationships"."id" ASC LIMIT 1
  SQL (0.3ms)  INSERT INTO "as" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", "2015-10-10 08:46:58.276925"], ["updated_at", "2015-10-10 08:46:58.276925"]]
  AbRelationship Load (0.1ms)  SELECT  "ab_relationships".* FROM "ab_relationships" WHERE "ab_relationships"."a_id" = 1  ORDER BY "ab_relationships"."id" ASC LIMIT 1
  SQL (0.1ms)  INSERT INTO "ab_relationships" ("b_id", "a_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["b_id", 1], ["a_id", 1], ["created_at", "2015-10-10 08:46:58.279207"], ["updated_at", "2015-10-10 08:46:58.279207"]]
  AbRelationship Load (0.1ms)  SELECT  "ab_relationships".* FROM "ab_relationships" WHERE "ab_relationships"."a_id" = 1  ORDER BY "ab_relationships"."id" ASC LIMIT 1
   (0.8ms)  rollback transaction
 => #<A id: nil, created_at: "2015-10-10 08:46:58", updated_at: "2015-10-10 08:46:58"> 
2.2.2 :007 > a.valid?
  AbRelationship Load (0.2ms)  SELECT  "ab_relationships".* FROM "ab_relationships" WHERE "ab_relationships"."a_id" IS NULL  ORDER BY "ab_relationships"."id" ASC LIMIT 1
 => true 
2.2.2 :008 > a.persisted?
 => false 
2.2.2 :009 >

я понимаю, что эту задачу можно по другому решить и все будет работать, но почему происходит то что происходит?
Основной вопрос в том почему ololo_validator вызывается 2 раза, в результате все ломается.
  • Вопрос задан
  • 454 просмотра
Пригласить эксперта
Ответы на вопрос 1
@kir_vesp
Web Developer
Всё логично же. Сначала создаётся первая связующая модель(id 12), потом при попытке создать вторую видим, что уже с данной записью a существует связь у другой модели из b. Так что, да, оно должно ломаться. Фактически, ваш валидатор проверяет, чтобы у вас была связь one-to-one-through(правда, непонятно зачем такое).

UPD: не объявлены полностью связи в моделях, должно быть так:
class A < ActiveRecord::Base
has_many :ab_relationships,  class_name: ABRelationship
has_many :bs, through: :ab_relationships, source: :b
end

class B < ActiveRecord::Base
has_many :ab_relationships,  class_name: ABRelationship
has_many :as, through: :ab_relationships, source: :a
end


UPD2: Причины пока неизвестны, но выглядит очень необычно
a = A.create!(b_ids: [1])
  B Load (0.2ms)  SELECT  `bs`.* FROM `bs` WHERE `bs`.`id` = 1 LIMIT 1
   (0.1ms)  BEGIN
  ABRelationship Load (0.4ms)  SELECT  `ab_relationships`.* FROM `ab_relationships` WHERE `ab_relationships`.`a_id` IS NULL  ORDER BY `ab_relationships`.`id` ASC LIMIT 1
  SQL (21.7ms)  INSERT INTO `as` (`created_at`, `updated_at`) VALUES ('2015-10-10 08:43:24', '2015-10-10 08:43:24')
  ABRelationship Load (0.6ms)  SELECT  `ab_relationships`.* FROM `ab_relationships` WHERE `ab_relationships`.`a_id` = 4  ORDER BY `ab_relationships`.`id` ASC LIMIT 1
  SQL (0.5ms)  INSERT INTO `ab_relationships` (`b_id`, `a_id`, `created_at`, `updated_at`) VALUES (1, 4, '2015-10-10 08:43:24', '2015-10-10 08:43:24')
  ABRelationship Load (0.6ms)  SELECT  `ab_relationships`.* FROM `ab_relationships` WHERE `ab_relationships`.`a_id` = 4  ORDER BY `ab_relationships`.`id` ASC LIMIT 1
   (85.8ms)  ROLLBACK
ActiveRecord::RecordInvalid: Validation failed: Ab relationships error


UPD3: В итоге, логика Rails сводится здесь к следующему. Создадим запись в связывающей таблице, а потом перед сохранением основной записи запустим все валидации, в том числе и у связанных записей.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы