Reputation: 6310
Consider the following code
class CreateFoosAndBars < ActiveRecord::Migration
def change
create_table :foos do |t|
t.string :name
t.timestamps
end
create_table :bars do |t|
t.integer :foo_id
t.string :name
t.timestamps
end
end
end
class Foo < ActiveRecord::Base
has_many :bars
end
class Bar < ActiveRecord::Base
belongs_to :foo
validates_presence_of :foo
end
So, foos can have any number of bars and bars cannot exist without a foo.
This code should save, but it doesn't:
irb(main):020:0> foo = Foo.new
=> #<Foo id: nil, name: nil, created_at: nil, updated_at: nil>
irb(main):021:0> foo.bars.build
=> #<Bar id: nil, foo_id: nil, name: nil, created_at: nil, updated_at: nil>
irb(main):022:0> foo.save
(0.2ms) BEGIN
(0.2ms) ROLLBACK
=> false
irb(main):023:0> foo.errors
=> #<ActiveModel::Errors:0x007fb325b35e68 @base=#<Foo id: nil, name: nil, created_at: nil, updated_at: nil>, @messages={:bars=>["er ikke gyldig"]}>
irb(main):024:0>
I am using Rails 4.0.3 for this project.
I can't seem to grasp how to transactionally initialize, validate and save a foo with a number of bars. What am I missing here?
Upvotes: 0
Views: 425
Reputation: 6310
I have discovered the solution.
If I do this
foo = Foo.new
bar = foo.bars.build
bar.foo
=> nil
So build
is lying. It's not building a bar associated with foo as you would expect. build
only works when the foo is persisted.
However, if I add inverse_of: :foo
to my Foo models association, like this:
class Foo < ActiveRecord::Base
has_many :bars, inverse_of: :foo
end
It suddenly oddly works. Calling foo
on the built bar instance suddenly returns an object instead of nil.
irb(main):002:0> bar = foo.bars.build
=> #<Bar id: nil, foo_id: nil, name: nil, created_at: nil, updated_at: nil>
irb(main):003:0> bar.foo
=> #<Foo id: nil, name: nil, created_at: nil, updated_at: nil>
irb(main):005:0> bar.foo.object_id
=> 70208007135620
irb(main):006:0> foo.object_id
=> 70208007135620
I do not know why this is neccessary and not the default behavior.
This issue only applies to Rails versions older than 4.1.0.
Upvotes: 0
Reputation: 4982
Your code should work, I copy pasted your example into a new rails app:
class CreateFoosAndBars < ActiveRecord::Migration
def change
create_table :foos do |t|
t.string :name
t.timestamps
end
create_table :bars do |t|
t.integer :foo_id
t.string :name
t.timestamps
end
end
end
class Foo < ActiveRecord::Base
has_many :bars
end
class Bar < ActiveRecord::Base
belongs_to :foo
validates_presence_of :foo
end
And these were the results:
2.1.1 :001 > Foo.count
(0.1ms) SELECT COUNT(*) FROM "foos"
=> 0
2.1.1 :002 > Bar.count
(0.1ms) SELECT COUNT(*) FROM "bars"
=> 0
2.1.1 :003 > foo = Foo.new
=> #<Foo id: nil, name: nil, created_at: nil, updated_at: nil>
2.1.1 :004 > foo.bars.build
=> #<Bar id: nil, foo_id: nil, name: nil, created_at: nil, updated_at: nil>
2.1.1 :005 > foo.save
(0.3ms) SAVEPOINT active_record_1
SQL (0.5ms) INSERT INTO "foos" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2014-06-21 16:24:15.334535"], ["updated_at", "2014-06-21 16:24:15.334535"]]
SQL (0.2ms) INSERT INTO "bars" ("created_at", "foo_id", "updated_at") VALUES (?, ?, ?) [["created_at", "2014-06-21 16:24:15.342524"], ["foo_id", 1], ["updated_at", "2014-06-21 16:24:15.342524"]]
(0.1ms) RELEASE SAVEPOINT active_record_1
=> true
2.1.1 :006 > foo.errors
=> #<ActiveModel::Errors:0x007ff37b9514a8 @base=#<Foo id: 1, name: nil, created_at: "2014-06-21 16:24:15", updated_at: "2014-06-21 16:24:15">, @messages={}>
2.1.1 :007 > Foo.count
(0.2ms) SELECT COUNT(*) FROM "foos"
=> 1
2.1.1 :008 > Bar.count
(0.3ms) SELECT COUNT(*) FROM "bars"
=> 1
This is using Rails 4.1.0.
In versions of Rails prior to 4.1.0, you would have to specify the following relationship:
class Foo
has_many :bars, inverse_of: :foo
end
However Rails 4.1.0 now heuristically detects inverse associations as documented here.
Upvotes: 3
Reputation: 4306
Try adding the inverse_of option to both of your associations. Maybe Rails is getting confused somewhere and can't match them up.
Upvotes: 0
Reputation: 971
foo = Foo.build
bar = foo.bars.build
bar.save
The reason being you are creating bars without saving foo. Hence foo is invalid. You must save foo before that, so it can get an id.
Update:
You have to use inverse_of in your in your bar model.
belongs_to :foo, inverse_of :bar
Foo model
has_many :bards, inverse_of :foo
Reference: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Upvotes: 0