Invalid associated child object is silently not auto-saved and does not make saving of parent fail

**TL;DR:
child.valid? == false
parent.save #=> true
child.new_record? #=> true
child is not saved, but parent is saved
I had expected this to block saving of parent

Hi,

I am confused by this behavior (ruby 1.9.3 Rails 3.2.0):

class Parent < ActiveRecord::Base
has_one :child
end

class Child < ActiveRecord::Base
validates :name, :presence => true
end

$ rails c
Loading development environment (Rails 3.2.0)
1.9.3-p0 :001 > p = Parent.new(:name => “dad”)
=> #<Parent id: nil, name: “dad”, created_at: nil, updated_at: nil>

1.9.3-p0 :002 > p.child = Child.new(:name => “Sarah”)
(0.2ms) BEGIN
(0.2ms) COMMIT
=> #<Child id: nil, name: “Sarah”, parent_id: nil, created_at: nil,
updated_at: nil>

1.9.3-p0 :003 > p.save!
(0.2ms) BEGIN
SQL (4.8ms) INSERT INTO “parents” (“created_at”, “name”,
“updated_at”)
VALUES ($1, $2, $3) RETURNING “id” [[“created_at”, Tue, 24 Jan 2012
10:06:59 UTC +00:00], [“name”, “dad”], [“updated_at”, Tue, 24 Jan 2012
10:06:59 UTC +00:00]]
SQL (0.8ms) INSERT INTO “children” (“created_at”, “name”,
“parent_id”,
“updated_at”) VALUES ($1, $2, $3, $4) RETURNING “id” [[“created_at”,
Tue,
24 Jan 2012 10:06:59 UTC +00:00], [“name”, “Sarah”], [“parent_id”, 2],
[“updated_at”, Tue, 24 Jan 2012 10:06:59 UTC +00:00]]
(9.8ms) COMMIT
=> true

both are saved as expected (with child “auto-saved”)

1.9.3-p0 :004 > m = Parent.new(:name => “mom”)
=> #<Parent id: nil, name: “mom”, created_at: nil, updated_at: nil>

1.9.3-p0 :005 > m.child = Child.new(:name => nil) # EMPTY NAME
(0.2ms) BEGIN
(0.2ms) COMMIT
=> #<Child id: nil, name: nil, parent_id: nil, created_at: nil,
updated_at: nil>
1.9.3-p0 :006 > m.valid?
=> true
1.9.3-p0 :007 > m.child.valid?
=> false

child is not valid (name is not present)

1.9.3-p0 :008 > m.save!
(0.2ms) BEGIN
SQL (0.5ms) INSERT INTO “parents” (“created_at”, “name”,
“updated_at”)
VALUES ($1, $2, $3) RETURNING “id” [[“created_at”, Tue, 24 Jan 2012
10:07:42 UTC +00:00], [“name”, “mom”], [“updated_at”, Tue, 24 Jan 2012
10:07:42 UTC +00:00]]
(12.3ms) COMMIT
=> true

the save of the parent happily continues and the child is silently not

auto-saved.

I had expected that the entire save! would have failed in a transaction,
so
that
either ALL or NOTHING are saved.

When I add to the model e.g. the :autosave => true option, I get the
expected behavior:

class Parent < ActiveRecord::Base
has_one :child, :autosave => true
end

class Child < ActiveRecord::Base
validates :name, :presence => true
end

$ rails c
Loading development environment (Rails 3.2.0)
1.9.3-p0 :001 > # with :autosave => true on the has_one :child
association
1.9.3-p0 :002 > m = Parent.new(:name => “mom”)
=> #<Parent id: nil, name: “mom”, created_at: nil, updated_at: nil>
1.9.3-p0 :003 > m.valid?
=> true
1.9.3-p0 :004 > m.child = Child.new(:name => nil)
(0.1ms) BEGIN
(0.1ms) COMMIT
=> #<Child id: nil, name: nil, parent_id: nil, created_at: nil,
updated_at: nil>
1.9.3-p0 :005 > m.valid?
=> false

it seems :autosave => true also implies validates_associated on

the
association ?

1.9.3-p0 :006 > m.child.valid?
=> false
1.9.3-p0 :007 > m.save!
(0.2ms) BEGIN
(0.2ms) ROLLBACK
ActiveRecord::RecordInvalid: Validation failed: Child name can’t be
blank

Next to :autosave => true, also using validates_associated :child or
accepts_nested_attributes_for all result in the behavior I had
expected
(save does “all or nothing”).

But, I would expect the standard functionality (without :autosave =>
true or
validates_associated) to not save anything (neither parent or children)
in
the
transaction when one of the objects for saving is invalid.

I feel the current behavior allows a “silent” failure where only half of
the expected
objects is saved while the save(!) returns success.

I am not pleading to make :autosave => true or validates_associated
the
default on all associations.

I am pleading for the “ad-hoc” measure that

  • if ActiveRecord decides to auto-save associated objects together with
    the
    main object
  • and one of thos auot-saves fails on any of those associated objects
  • then the entire transaction is rolled back and a non-success result is
    returned

If there is interest in this, I may look in the code and try to find the
place to fix it,
but maybe there are fundamental reasons for the way it works today.

Thanks for your time,

Peter

Hai!

One-to-one

use has_one in the base, and belongs_to in the associated model.

class Parent < ActiveRecord::Base
has_one :child
end

class Child < ActiveRecord::Base
belongs_to :parent # foreign key - parent_id
end

Try this way:

Bye:)
Bdeveloper01

On Wed, Jan 25, 2012 at 11:31 AM, Bala TS [email protected] wrote:

class Child < ActiveRecord::Base
belongs_to :parent # foreign key - parent_id
end

Thank you for the feedback. I tried and this is the result:

class Child < ActiveRecord::Base
has_one :parent
validates :name, :presence => true
end

class Parent < ActiveRecord::Base
has_one :child
end

$ rails c
Loading development environment (Rails 3.2.0)
1.9.3-p0 :001 > p = Parent.new
=> #<Parent id: nil, name: nil, created_at: nil, updated_at: nil>
1.9.3-p0 :002 > c1 = p.build_child
(0.2ms) BEGIN
(0.2ms) COMMIT
=> #<Child id: nil, name: nil, parent_id: nil, created_at: nil,
updated_at: nil>
1.9.3-p0 :003 > p.valid?
=> true
1.9.3-p0 :004 > c1.valid?
=> false
1.9.3-p0 :005 > p.save
(0.2ms) BEGIN
SQL (5.8ms) INSERT INTO “parents” (“created_at”, “name”,
“updated_at”)
VALUES ($1, $2, $3) RETURNING “id” [[“created_at”, Wed, 25 Jan 2012
11:58:00 UTC +00:00], [“name”, nil], [“updated_at”, Wed, 25 Jan 2012
11:58:00 UTC +00:00]]
(24.2ms) COMMIT
=> true

The line above reports “success” (true), while the child is not
auto-saved

Now trying to implicitly save a non-valid child with a straight
assignment.

1.9.3-p0 :022 > p.child = Child.new
(0.2ms) BEGIN
(0.4ms) UPDATE “children” SET “parent_id” = NULL, “updated_at” =
‘2012-01-25 12:08:36.168342’ WHERE “children”.“id” = 4
(0.2ms) ROLLBACK
ActiveRecord::RecordNotSaved: Failed to save the new associated child.
from
/home/peterv/.rvm/gems/[email protected]_validations/gems/activerecord-3.2.0/lib/active_record/associations/has_one_association.rb:23:in
block in replace' from /home/peterv/.rvm/gems/[email protected]_validations/gems/activerecord-3.2.0/lib/active_record/connection_adapters/abstract/database_statements.rb:190:intransaction’
from
/home/peterv/.rvm/gems/[email protected]_validations/gems/activerecord-3.2.0/lib/active_record/transactions.rb:208:in
transaction' from /home/peterv/.rvm/gems/[email protected]_validations/gems/activerecord-3.2.0/lib/active_record/associations/has_one_association.rb:11:inreplace’
from
/home/peterv/.rvm/gems/[email protected]_validations/gems/activerecord-3.2.0/lib/active_record/associations/singular_association.rb:17:in
writer' from /home/peterv/.rvm/gems/[email protected]_validations/gems/activerecord-3.2.0/lib/active_record/associations/builder/association.rb:51:inblock in define_writers’

I would have expected this behavior (an exception for p.save!) that is
happening for an explicit save of an associated child, to also occur on
an “auto-save” of an associated child.

Thanks,

Peter

On Wed, Jan 25, 2012 at 1:15 PM, Peter V.
[email protected]wrote:

end
validates :name, :presence => true
end

class Parent < ActiveRecord::Base
has_one :child
end

Sorry, that should have been Child belongs_to :parent …

But, the results are the same. Trying again:

…/app/models$ cat *
class Child < ActiveRecord::Base
belongs_to :parent
validates :name, :presence => true
end
class Parent < ActiveRecord::Base
has_one :child
end

$ rails c
Loading development environment (Rails 3.2.0)
1.9.3-p0 :001 > p = Parent.new
=> #<Parent id: nil, name: nil, created_at: nil, updated_at: nil>
1.9.3-p0 :002 > c1 = p.build_child
(0.2ms) BEGIN
(0.2ms) COMMIT
=> #<Child id: nil, name: nil, parent_id: nil, created_at: nil,
updated_at: nil>
1.9.3-p0 :003 > p.valid?
=> true
1.9.3-p0 :004 > c1.valid?
=> false
1.9.3-p0 :005 > p.save
(0.2ms) BEGIN
SQL (5.7ms) INSERT INTO “parents” (“created_at”, “name”,
“updated_at”)
VALUES ($1, $2, $3) RETURNING “id” [[“created_at”, Wed, 25 Jan 2012
12:18:32 UTC +00:00], [“name”, nil], [“updated_at”, Wed, 25 Jan 2012
12:18:32 UTC +00:00]]
(21.9ms) COMMIT
=> true
1.9.3-p0 :006 > p.child = Child.new
(0.2ms) BEGIN
(0.2ms) ROLLBACK
ActiveRecord::RecordNotSaved: Failed to save the new associated child.
from
/home/peterv/.rvm/gems/[email protected]_validations/gems/activerecord-3.2.0/lib/active_record/associations/has_one_association.rb:23:in
block in replace' from /home/peterv/.rvm/gems/[email protected]_validations/gems/activerecord-3.2.0/lib/active_record/connection_adapters/abstract/database_statements.rb:190:intransaction’
from
/home/peterv/.rvm/gems/[email protected]_validations/gems/activerecord-3.2.0/lib/active_record/transactions.rb:208:in
transaction' from /home/peterv/.rvm/gems/[email protected]_validations/gems/activerecord-3.2.0/lib/active_record/associations/has_one_association.rb:11:inreplace’
from
/home/peterv/.rvm/gems/[email protected]_validations/gems/activerecord-3.2.0/lib/active_record/associations/singular_association.rb:17:in
writer' from /home/peterv/.rvm/gems/[email protected]_validations/gems/activerecord-3.2.0/lib/active_record/associations/builder/association.rb:51:inblock in define_writers’

Thanks,

Peter

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs