Rails 5.1外键命名与创建指南:Expense外键关联失败排查
Hey there! Let's break down your two questions, starting with the immediate issue that's causing your expense records to have null foreign keys—since that's blocking your progress right now.
问题2:为什么seeds.rb生成的Expense外键为null?
You've got two key issues here, let's fix them step by step:
1. 你传递的是对象而非ID值
In your seeds.rb, you're passing Vendor.first directly to vendor_id, but vendor_id is an integer column that expects the ID number of the vendor, not the entire Vendor object. Same goes for expensetype_id.
Fix your seeds code to use .id:
Expense.create!( amount: 500, date: Date.today, vendor_id: Vendor.first.id, # Add .id here expensetype_id: ExpenseType.second.id, # And here note: "This is the first expense!" )
2. 外键命名不符合Rails约定
Looking at your schema and model:
- Your model has
belongs_to :expensetype, but your class is namedExpenseType(camel case). Rails expects the association name to be snake case (expense_type) and the foreign key to beexpense_type_id(with an underscore), but your schema usesexpensetype_id(no underscore). This mismatch means Rails can't automatically map the association to the correct column.
You have two options to fix this:
Option A: Adjust the model to match your existing column (quick fix)
Update your expense.rb to explicitly specify the foreign key:
class Expense < ApplicationRecord belongs_to :vendor # This works because vendor_id matches the convention belongs_to :expensetype, foreign_key: 'expensetype_id' # Add foreign_key here end
Option B: Rename the column to follow Rails conventions (recommended for long-term)
This will make future development smoother since you'll stick to Rails' defaults.
- Generate a migration to rename the column:
rails generate migration RenameExpensetypeIdToExpenseTypeIdOnExpenses - Open the generated migration file and add:
def change rename_column :expenses, :expensetype_id, :expense_type_id end - Run the migration:
rails db:migrate - Update your
expense.rbto use the correct association name:class Expense < ApplicationRecord belongs_to :vendor belongs_to :expense_type # Snake case to match ExpenseType class end - Don't forget to update your seeds.rb to use the new column name:
Expense.create!( amount: 500, date: Date.today, vendor_id: Vendor.first.id, expense_type_id: ExpenseType.second.id, # Updated column name note: "This is the first expense!" )
Side note: Why has_one didn't work
Switching to has_one was incorrect here—belongs_to is the right choice because an Expense belongs to a single Vendor and a single ExpenseType. The inverse would be adding has_many :expenses to your Vendor and ExpenseType models (if you want to access all expenses for a vendor/type later).
问题1:Rails 5.1中如何命名与创建外键?
Rails relies heavily on conventions to reduce boilerplate—here's how it works for foreign keys:
Naming Rules
- For a
belongs_to :model_nameassociation, the default foreign key ismodel_name_id(snake_case of the model class name).- Example:
belongs_to :vendor→ foreign keyvendor_id - Example:
belongs_to :expense_type→ foreign keyexpense_type_id
- Example:
- If you need to use a non-standard foreign key (like your original
expensetype_id), specify it with theforeign_keyoption in the association (as shown in Option A above).
Creating Foreign Keys
There are a few ways to set up foreign keys in Rails 5.1:
1. Generate a model with associations (easiest)
When creating a new model, specify associations using the :references type, and Rails will handle the rest:
rails generate model Expense amount:integer date:date vendor:references expense_type:references note:string
This generates a migration that:
- Adds
vendor_idandexpense_type_idinteger columns to theexpensestable - Adds indexes for these columns
- Adds database-level foreign key constraints (if your database supports it—like PostgreSQL; SQLite may require extra configuration)
2. Add a foreign key to an existing model
If you need to add an association to an existing model, generate a migration with the :references type:
rails generate migration AddVendorIdToExpenses vendor:references
Run the migration with rails db:migrate to add the column, index, and foreign key constraint.
3. Manually add a database foreign key constraint
If you already have the foreign key column but want to add a database-level constraint (to enforce referential integrity), generate a blank migration:
rails generate migration AddForeignKeyConstraintsToExpenses
Then add the constraints in the migration:
def change add_foreign_key :expenses, :vendors add_foreign_key :expenses, :expense_types end
Run rails db:migrate to apply the changes.
内容的提问来源于stack exchange,提问作者Jay Wharton

