A
A
Andriy Stepanyuk2015-10-09 22:14:08
Ruby on Rails
Andriy Stepanyuk, 2015-10-09 22:14:08

ActiveRecord interesting case?

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

Now if we do
a = A.create(b_ids: [ 12 ]) # real ids
then a.valid? will be true
but a.presisted? will be false
2.2.2 :005 > B.create
   (0.1ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "bs" ("created_at", "updated_at") VALUES (?, ?)  
   (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  
   (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 (?, ?)  
  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 (?, ?, ?, ?)  
  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 >

I understand that this problem can be solved differently and everything will work, but why is what happens?
The main question is why ololo_validator is called 2 times, as a result everything breaks.

Answer the question

In order to leave comments, you need to log in

1 answer(s)
K
Kirill Penzin, 2015-10-10
@kir_vesp

Everything is logical. First, the first connecting model is created (id 12), then when trying to create the second one, we see that another model from b already has a connection with this record a. So, yes, it must break. In fact, your validator checks that you have a one-to-one-through relationship (however, it is not clear why this is).
UPD: relationships are not fully declared in models, it should be like this:

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: The reasons are still unknown, but it looks very unusual
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: As a result, the Rails logic comes down to the following. Let's create a record in the linking table, and then, before saving the main record, we will run all validations, including for related records.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question