You are correct (and I learned something). In this many_to_many association
a build and a later save on the newly build object, seems to NOT create
the intermediate association object in the database … (this works as I would
expect in a straight has_many, but seemingly not in a has_many :through …).
Using a ‘create’ resolves that problem, but that means you cannot delay the
saving to database to one consolidated save at the end, as I prefer to do.
Some code:
002:0> Company.create
SQL (11.6ms) INSERT INTO “companies” (“created_at”, “updated_at”) VALUES (?, ?) [[“created_at”, Thu, 12 Jan 2012 23:28:57 UTC +00:00], [“updated_at”, Thu, 12 Jan 2012 23:28:57 UTC +00:00]]
004:0> @company = Company.find(1)
Company Load (0.2ms) SELECT “companies”.* FROM “companies” WHERE “companies”.“id” = ? LIMIT 1 [[“id”, 1]]
=> #<Company id: 1, created_at: “2012-01-12 23:28:57”, updated_at: “2012-01-12 23:28:57”>
006:0> @manager = @company.managers.build()
=> #<Manager id: nil, created_at: nil, updated_at: nil>
007:0> @manager.save!
SQL (0.6ms) INSERT INTO “managers” (“created_at”, “updated_at”) VALUES (?, ?) [[“created_at”, Thu, 12 Jan 2012 23:30:25 UTC +00:00], [“updated_at”, Thu, 12 Jan 2012 23:30:25 UTC +00:00]]
=> true
This does NOT save the intermediate management record
008:0> @manager = @company.managers.create()
SQL (0.5ms) INSERT INTO “managers” (“created_at”, “updated_at”) VALUES (?, ?) [[“created_at”, Thu, 12 Jan 2012 23:30:42 UTC +00:00], [“updated_at”, Thu, 12 Jan 2012 23:30:42 UTC +00:00]]
SQL (0.8ms) INSERT INTO “managements” (“company_id”, “created_at”, “manager_id”, “role”, “updated_at”) VALUES (?, ?, ?, ?, ?) [[“company_id”, 1], [“created_at”, Thu, 12 Jan 2012 23:30:42 UTC +00:00], [“manager_id”, 2], [“role”, nil], [“updated_at”, Thu, 12 Jan 2012 23:30:42 UTC +00:00]]
=> #<Manager id: 2, created_at: “2012-01-12 23:30:42”, updated_at: “2012-01-12 23:30:42”>
but this does save the intermediate management record
009:0> @manager.managements.first
Management Load (0.3ms) SELECT “managements”.* FROM “managements” WHERE “managements”.“manager_id” = 2 LIMIT 1
=> #<Management id: 1, company_id: 1, manager_id: 2, role: nil, created_at: “2012-01-12 23:30:42”, updated_at: “2012-01-12 23:30:42”>
010:0> @manager.managements.first.role = “test role”
Management Load (0.3ms) SELECT “managements”.* FROM “managements” WHERE “managements”.“manager_id” = 2 LIMIT 1
=> “test role”
Huh, Y U Reload ? (this is a different problem)
011:0> @manager.managements.first.save!
Management Load (0.3ms) SELECT “managements”.* FROM “managements” WHERE “managements”.“manager_id” = 2 LIMIT 1
=> true
Huh, Y U Reload again … and now save a pristine management record without my role assigned ?
I could not reproduce this behavior … never seen this before.
013:0> management = @manager.managements.first
Management Load (0.3ms) SELECT “managements”.* FROM “managements” WHERE “managements”.“manager_id” = 2 LIMIT 1
=> #<Management id: 1, company_id: 1, manager_id: 2, role: nil, created_at: “2012-01-12 23:30:42”, updated_at: “2012-01-12 23:30:42”>
014:0> management.role = “test role”
=> “test role”
015:0> management.save!
(0.4ms) UPDATE “managements” SET “role” = ‘test role’, “updated_at” = ‘2012-01-12 23:32:24.704538’ WHERE “managements”.“id” = 1
=> true
forcing management to be the same record
017:0> @manager.managements.first
Management Load (0.3ms) SELECT “managements”.* FROM “managements” WHERE “managements”.“manager_id” = 2 LIMIT 1
=> #<Management id: 1, company_id: 1, manager_id: 2, role: “test role”, created_at: “2012-01-12 23:30:42”, updated_at: “2012-01-12 23:32:24”>
018:0> @manager.managements.first.role
Management Load (0.3ms) SELECT “managements”.* FROM “managements” WHERE “managements”.“manager_id” = 2 LIMIT 1
=> “test role”
And now, reading it back from the database yields the role.
Please not that the use of “first” here is dangerous, unless you make sure there is never
a double association record between the same combination of Company and Manager.
Another problem, I do not see the “role” value anywhere in your params, so probably also
the form needs fixing somewhere ?
HTH,
Peter