简体   繁体   中英

Rails - two foreign keys on one model both refer to same model

I'm fairly new to ActiveRecord associations. I'm sketching out an application that tracks who owes each other money among a set of users. An Expense model and a User model seem like natural choices, I'm just not sure how to define the relationship between the two. For example, I want to track the creditor ("owner") and the debtor of each expense, but that's really just two foreign keys that go back to User. In addition, each user can have multiple expenses (both as creditor and debtor) My best guess for the associations thus far is something like:

class Expense
    # belongs_to or has_one here?
    # Not sure about class => User syntax:
    # need to alias to foreign keys that reference the same model
    belongs_to :creditor, :class => User 
    belongs_to :debtor, :class => User

class User
    # has_many expenses defines a creditor relationship (user owns expense)
    # how to define debtor relationship? (belongs_to...?)
    has_and_belongs_to_many :expenses

I've read the Rails guide on associations but I'm still fairly lost on foreign keys and join tables. Any input is much appreciated!

So this is definately not a has_and_belongs_to_many thats for many-to-many relationships. You just need to use a couple has_many relationships. I think it should end up looking like this:

Edit: oops I fudged that a bit that up sorry let me have another go:

class Expense
  # make sure expense table has 'creditor_id' and 'debtor_id' rows
  belongs_to :creditor, :class_name => "User", :foreign_key => :creditor_id
  belongs_to :debtor, :class_name => "User", :foreign_key => :debtor_id

class User
  has_many :debts, :class_name => "Expense", :foreign_key => :debtor_id
  has_many :credits, :class_name => "Expense", :foreign_key => :creditor_id

The other answers tell you what you need to do, but it can be kind of confusing to people who are new to Rails, as I am, to piece all these things together, so here is a complete solution, including both Migrations and Models.

Also, as a side note: I prefer Loans, Lender and Borrower to Expense, Creditor and Debtor, or Debt, Creditor and Debtor. Mostly because Expense is ambiguous and Debt is too similar to Debtor. But it's not that important; just do what makes sense to you, since you will be maintaing your code.

Migrations

class CreateLoans < ActiveRecord::Migration
  create_table :loans do |t|
    def up
      t.references :lender
      t.references :borrower
    end
  end
end

Here you are specifying that there are two columns in this table that will be referred to as :lender and :borrower and which hold references to another table. Rails will actually create columns called 'lender_id' and 'borrower_id' for you. In our case they will each reference rows in the Users table, but we specify that in the models, not in the migrations.

Models

class Loan < ActiveRecord::Base
  belongs_to :lender, class_name => 'User'
  belongs_to :borrower, class_name => 'User'
end

Here you are creating a property on the Loan model named :lender, then specifying that this property is related to the User class. Rails, seeing the 'belongs_to', will look for a column in the loans table called 'lender_id', which we defined above, and use that to store the foreign key. Then you're doing the exact same thing for the borrower.

This will allow you to access your Lender and Borrower, both instances of the User model, through an instance of the Loan model, like this:

@loan.lender # Returns an instance of the User model
@loan.borrower.first_name # Returns a string, as you would expect

As a side note: the 'belongs_to' nomenclature makes decent sense in this case, but can be kind of confusing elsewhere. Just remember that it is always used on whichever thing contains the foreign key.

class User < ActiveRecord::Base
  has_many :loans_as_lender, :class_name => 'Loan', :foreign_key => 'lender_id'
  has_many :loans_as_borrower, :class_name => 'Loan', :foreign_key => 'borrower_id'
end

Here you are creating a property on the User model named :loans_as_lender, specifying that this property is related to the Loan model, and that the foreign key on the Loan model which relates it to this property is called 'lender_id'. Then you are doing the same thing for :loans_as_borrower.

This allows you to get all the loans where a User is the lender or borrower, like this:

@address.loans_as_lender
@address.loans_as_borrower

Doing either of these will return an array of instances of the Loan model.

If your expense migration looks like this:

create_table :expenses do |t|
  t.integer :creditor_id, :null => false
  t.integer :debtor_id, :null => false
  # other attributes here
end

then your Expense model is sufficient. If you take a look at the documentation for belongs_to , you'll see that it will correctly infer the foreign keys into the user table:

:foreign_key

Specify the foreign key used for the association. By default this is guessed to be the name of the association with an “_id” suffix. So a class that defines a belongs_to :person association will use “person_id” as the default :foreign_key. Similarly, belongs_to :favorite_person, :class_name => "Person" will use a foreign key of “favorite_person_id”.

So you don't need to explicitly specify a foreign key here. If you use other naming conventions for the ids in your expenses model, then you need to explicitly specify them in your associations.

For your User model, you don't have a many_to_many relationship with expenses - an expense always belongs to exactly one debtor and exactly one creditor. So all you need is two has_many associations:

has_many :debts,  :class_name => 'Expense', :foreign_key => :debtor_id
has_many :credits :class_name => 'Expense', :foregin_key => :creditor_id

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM