Payment 关联的是 payee 还是 User?
结论
Payment 实际关联的是 User 模型,payee 是这条关联在 Payment 中的名称。
具体来说:
User:实际关联的模型类。payee:Payment用来访问收款用户的关联名。received_payments:User用来访问收款记录的反向关联名。payee_id:payments表中的外键,指向users.id。
换句话说,一个 Payment 属于一个作为 payee 的 User,一个 User 可以拥有多条 received_payments。
为什么关联名是 payee
Rails 通常会根据 belongs_to 后面的名称推断模型:
belongs_to :user
这表示关联的模型是 User,外键通常是 user_id。
这里使用 payee,是因为它比笼统的 user 更贴近业务含义。由于关联名和模型名不同,需要用 class_name 指定实际模型:
belongs_to :payee, class_name: "User"
这样调用:
payment.payee
返回的是一个 User 对象。Rails 默认通过 payee_id 查询这个用户,效果类似于:
User.find(payment.payee_id)
payee 不是另一个模型,也不需要定义 Payee 类。它只是一个指向 User 的关联名称。
反向关联如何工作
User 中的定义如下:
class User < ApplicationRecord
has_many :received_payments,
foreign_key: "payee_id",
class_name: "Payment"
end
其中:
received_payments是关联名称。class_name: "Payment"表示实际关联的模型是Payment。foreign_key: "payee_id"表示外键位于payments表中。
因此,调用:
user.received_payments
会查找所有符合以下条件的 Payment:
Payment.where(payee_id: user.id)
对应的数据库关系是:
payments.payee_id → users.id
推荐写法
可以补充 inverse_of,让 Rails 明确识别这组双向关联:
class Payment < ApplicationRecord
belongs_to :payee,
class_name: "User",
inverse_of: :received_payments
end
class User < ApplicationRecord
has_many :received_payments,
class_name: "Payment",
foreign_key: :payee_id,
inverse_of: :payee
end
创建和访问数据时可以这样写:
user = User.find(1)
payment = Payment.create!(
payee: user
)
payment.payee
# => user 对应的 User 实例
user.received_payments
# => payee_id 等于 user.id 的 Payment 集合
也可以从用户一侧创建:
payment = user.received_payments.create!
Rails 会自动将 payment.payee_id 设置为 user.id。
数据库需要具备的字段
payments 表必须包含 payee_id 字段。例如,迁移可以写成:
class AddPayeeToPayments < ActiveRecord::Migration[7.0]
def change
add_reference :payments,
:payee,
foreign_key: { to_table: :users },
index: true
end
end
这里需要注意:
foreign_key: { to_table: :users }
引用列虽然叫 payee_id,但它指向的不是 payees 表,而是 users 表。
迁移版本号应按项目实际使用的 Rails 版本调整。
常见误区
payee 不是数据库表名
下面的关联不要求存在 payees 表:
belongs_to :payee, class_name: "User"
class_name: "User" 已经指定 Rails 查询 users 表。
received_payments 也不是模型名
下面的代码返回 Payment 集合:
user.received_payments
Rails 不会寻找 ReceivedPayment 模型,因为关联中已经指定:
class_name: "Payment"
两侧名称不需要相同
下面两个名称描述的是同一组关系的不同方向:
payment.payee
user.received_payments
前者表示“这笔付款的收款人”,后者表示“这个用户收到的付款”。使用不同的关联名,可以让各自的业务含义更清楚。
备注:内容仅供参考。
