Ruby on Rails Thursday, December 1, 2011

On Thu, Dec 1, 2011 at 10:59 PM, Erwin <yves_dufour@mac.com> wrote:
Is it wrong to use a beings_to on both side of a one-to-one
association ?

User
belongs_to :account          so I have an account_id field

Account
belongs_to :owner, :class_name => 'User', :foreign_key => 'user_id'

I can get   user.account     and   account.owner
It runs, but I wonder about any collateral effect...

I don't like it (if I understand correctly that you have both 
in users table account_id
in accounts table user_id)

1) This code expresses the 1-on-1 link between
a user and his associated account 2 times
(user.account_id points to account.id and
 account.user_id point to user.id).

So, there is a chance that the 2 links go out of sync.

2) You will always need 3 database writes to save
 a pair of a user and an account.

If you did:

class User
  belongs_to :account
end

class Account
  has_one :user
end

you could write

  account = Account.new(params[:account])
  user = account.build_user(params[:user])
  account.save # handle the return value

and this will do all that is required (with 2 database writes,
first the account, which will render the account.id and then
user will will be saved with user.account_id = account.id).

But with the original code (with 2 belongs_to associations),
I think you will need to do 3 writes:
* account.save (#=> account.id)
* user.save (with user.account_id = account.id)
* a second account.save (for updating account.user_id = user.id)

I fail to see the advantage over a symmetric belongs_to ,  has_one
relationship.

Also check out the advantages of the inverse_of

class User
  belongs_to :account, :inverse_of => :user
end

class Account
  has_one :user, :inverse_of => :account
end

This will make sure the user.account is known before the save.

With the :inverse_of, this is the case:

$ rails c
Loading development environment (Rails 3.1.1)
001:0> u = User.new(:name => "peter")
=> #<User id: nil, name: "peter", account_id: nil, created_at: nil, updated_at: nil>
002:0> a = u.build_account(:number => "123")
=> #<Account id: nil, number: "123", created_at: nil, updated_at: nil>
003:0> a.user
=> #<User id: nil, name: "peter", account_id: nil, created_at: nil, updated_at: nil>
004:0> u.save
   (0.4ms)  BEGIN
  SQL (50.1ms)  INSERT INTO "accounts" ("created_at", "number", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["created_at", Thu, 01 Dec 2011 22:24:16 UTC +00:00], ["number", "123"], ["updated_at", Thu, 01 Dec 2011 22:24:16 UTC +00:00]]
  SQL (1.6ms)  INSERT INTO "users" ("account_id", "created_at", "name", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["account_id", 1], ["created_at", Thu, 01 Dec 2011 22:24:16 UTC +00:00], ["name", "peter"], ["updated_at", Thu, 01 Dec 2011 22:24:16 UTC +00:00]]
   (0.9ms)  COMMIT
=> true

Without the inverse_of relations, this is the result:

$ rails c
Loading development environment (Rails 3.1.1)
001:0> u = User.new(:name => "peter")
=> #<User id: nil, name: "peter", account_id: nil, created_at: nil, updated_at: nil>
002:0> a = u.build_account(:number => "123")
=> #<Account id: nil, number: "123", created_at: nil, updated_at: nil>
003:0> a.user
=> nil
004:0> # the "back link is only known AFTER the save to db and a reload :-/"
005:0* ^C
005:0> u.save
   (0.4ms)  BEGIN
  SQL (16.1ms)  INSERT INTO "accounts" ("created_at", "number", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["created_at", Thu, 01 Dec 2011 22:28:35 UTC +00:00], ["number", "123"], ["updated_at", Thu, 01 Dec 2011 22:28:35 UTC +00:00]]
  SQL (1.3ms)  INSERT INTO "users" ("account_id", "created_at", "name", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["account_id", 2], ["created_at", Thu, 01 Dec 2011 22:28:35 UTC +00:00], ["name", "peter"], ["updated_at", Thu, 01 Dec 2011 22:28:35 UTC +00:00]]
   (0.8ms)  COMMIT
=> true
006:0> a.user
=> nil
007:0> a.reload
  Account Load (1.4ms)  SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT 1  [["id", 2]]
=> #<Account id: 2, number: "123", created_at: "2011-12-01 22:28:35", updated_at: "2011-12-01 22:28:35">
008:0> a.user
  User Load (1.0ms)  SELECT "users".* FROM "users" WHERE "users"."account_id" = 2 LIMIT 1
=> #<User id: 2, name: "peter", account_id: 2, created_at: "2011-12-01 22:28:35", updated_at: "2011-12-01 22:28:35">
009:0> # work-around (when inverse_of is not available)
010:0* ^C
010:0> u3 = User.new(:name => "peter")
=> #<User id: nil, name: "peter", account_id: nil, created_at: nil, updated_at: nil>
011:0> a3 = u3.build_account(:number => '456', :user => u3)
   (0.4ms)  BEGIN
   (0.3ms)  COMMIT
=> #<Account id: nil, number: "456", created_at: nil, updated_at: nil>
012:0> a3.user
=> #<User id: nil, name: "peter", account_id: nil, created_at: nil, updated_at: nil>
013:0>


HTH,

Peter

--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
To post to this group, send email to rubyonrails-talk@googlegroups.com.
To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.

No comments:

Post a Comment