使用自定义主键时,如何在Rails中设置模型关联?
问题描述
我正在尝试把从Sellix API获取的数据存入Rails应用的数据库,现有products、coupons、orders和feedback四个模型。Sellix的每个对象都有唯一标识uniqid,所以我把它作为模型的自定义主键。
我想给部分模型设置表间关联,比如让订单关联优惠券,明确下单时使用的优惠券。当前两个表的结构如下:
Coupons表结构
create_table "coupons", id: false, force: :cascade do |t| t.string "uniqid", null: false t.string "code" t.decimal "discount" t.integer "used" t.datetime "expire_at" t.integer "created_at" t.integer "updated_at" t.integer "max_uses" t.index ["uniqid"], name: "index_coupons_on_uniqid", unique: true end
Orders表结构
create_table "orders", id: false, force: :cascade do |t| t.string "uniqid", null: false t.string "order_type" t.decimal "total" t.decimal "crypto_exchange_rate" t.string "customer_email" t.string "gateway" t.decimal "crypto_amount" t.decimal "crypto_received" t.string "country" t.decimal "discount" t.integer "created_at" t.integer "updated_at" t.string "coupon_uniqid" t.index ["uniqid"], name: "index_orders_on_uniqid", unique: true end
Orders表中的coupon_uniqid是对应优惠券的关联字段,Sellix API返回的订单对象已经包含这个关联,目前可以直接存储。但展示订单时用Coupon.find_by(uniqid: order.coupon_uniqid)查询优惠券,会触发全表遍历查询:
CACHE Coupon Load (0.0ms) SELECT "coupons".* FROM "coupons" WHERE "coupons"."uniqid" = $1 LIMIT $2 [["uniqid", "62e95dea17de385"], ["LIMIT", 1]]
我希望通过设置模型关联替代手动查询uniqid,优化查询效率,避免全表扫描。
解决方案
1. 配置模型自定义主键与关联关系
首先在Coupon和Order模型中明确指定主键为uniqid,同时定义关联关系:
# app/models/coupon.rb class Coupon < ApplicationRecord self.primary_key = 'uniqid' has_many :orders, foreign_key: 'coupon_uniqid' end # app/models/order.rb class Order < ApplicationRecord self.primary_key = 'uniqid' belongs_to :coupon, foreign_key: 'coupon_uniqid', optional: true end
- 加上
optional: true是因为并非所有订单都会使用优惠券,避免Rails强制验证关联必须存在。
2. 给orders表的coupon_uniqid字段添加索引
当前orders表仅对uniqid建了索引,coupon_uniqid无索引是导致全表扫描的核心原因。生成并执行迁移添加索引:
生成迁移文件:
rails generate migration AddIndexToOrdersCouponUniqid
编辑迁移文件内容:
class AddIndexToOrdersCouponUniqid < ActiveRecord::Migration[7.0] def change add_index :orders, :coupon_uniqid end end
执行迁移:
rails db:migrate
3. 使用关联查询替代手动find_by
之后在代码中直接通过关联调用获取优惠券即可,Rails会自动生成基于索引的高效查询:
# 获取订单对应的优惠券 order.coupon
额外优化:批量预加载避免N+1查询
如果需要批量处理订单(比如订单列表页),使用includes预加载优惠券,减少数据库查询次数:
# 一次性加载所有订单及其关联的优惠券 orders = Order.includes(:coupon).all orders.each do |order| puts order.coupon&.code # 不会触发额外查询 end
内容的提问来源于stack exchange,提问作者jkvithanage
相关产品推荐
相关产品推荐

