Rails 如何实现两个 Model 之间多个 has_many 关系?

pengedy · 2016年08月22日 · 最后由 pengedy 回复于 2016年08月23日 · 2718 次阅读

正常的has_many关系很简单。然而我遇到了一种情况:

  1. 用一个 User Model 表示多个类型的基本实体,比如“商户”和“顾客”,用某一个字段加以区分(比如 user_type)。
  2. 商户和顾客可以相互发起预约(Appointment),系统需要在 Appointment 中标明每个预约是哪位商户哪位顾客参与。
  3. 该情况与 Twitter 中用户相互 Follow 的情况不同,后者没有角色的概念,仅需要根据执行调用的 Object 来处理 following 和 followed 关系。
  4. 想达到的效果是,比如某个store是“商户”,那么store.appointments能返回所有该商户参与的 Appointment;同理如果某用户user_a.appointments,能得到该用户参与的 Appointment。

原本我写的是:

class User < ActiveRecord::Base
  has_many :appointments
  has_many :appointments, foreign_key: 'store_id'
end

class Appointment < ActiveRecord::Base
  # id  :integer
  # user_id  :integer
  # store_id  :integer
  belongs_to :user
  belongs_to :store, class_name: 'User'
end

但这种写法是错误的,其中第一行的has_many :appointments可以被认为是无效的语句。

所以,求助该怎么写?

has_many是一个类方法。你调用这个方法的时候,它定义了一系列新方法来实现其功能。简单的说,这个方法类似于这样:

class ActiveRecord::Base
  def has_many(name, options = {})
    define_method name do
       # return the association
       ...
     end
   end
end

因此,你以相同的名字两次调用has_many时,第二次调用时定义的方法就把之前定义的方法覆盖掉了。 也许你只能用不同的名字,比如:

class User < ActiveRecord::Base
  has_many :user_appointments
  has_many :store_appointments, foreign_key: 'store_id'
end
2 楼 已删除

试试这样:

class User < ActiveRecord::Base
  has_many :appointments
  has_many :stored_appointments, foreign_key: 'store_id', class_name: 'Appointment'
end

class Appointment < ActiveRecord::Base
  belongs_to :user, inverse_of: :appointments
  belongs_to :store, class_name: 'User', foreign_key: 'store_id', inverse_of: :stored_appointments
end

但不建议你这么搞!

@libuchao 你没搞清楚 Store 角色是即有作为用户的 appointments,又有作为商户的 appointments,还是只会有一个 appointments 关联。

4 楼 已删除

我觉得也可以用单表继承 ( single table inheritance ): https://ihower.tw/rails4/activerecord-others.html

Model

用户系统:可以用单表继承,Store(商户) 和 Customer(顾客) model 都继承 User model;
订单系统:Order(预约) model,关键字段有 creator_id (下单者) 和 target_id(被预约者)

Relation

User model 指定与订单的关系:
has_many :own_orders, dependent: :destroy, foreign_key: :creator_id, class_name: "Order" has_many :appointed_orders, dependent: :destroy, foreign_key: :target_id, class_name: "Order"
Order model 不需写 belong_to ,因为我们还不知道 creator 是 Store 还是 Customer(下文会提到如何找 creator 和 target)
Store 和 Customer model 继承了 User,所以也继承了与订单的关系

Usage

订单记录创建

商户预约顾客: order = Order.create(creator_id: store.id, target_id: customer.id)
顾客预约商户:
order = Order.create(creator_id: customer.id, target_id: store.id)

查询

通过订单记录查预约者和被预约者:
通过 order 记录可查询预约者和被预约者:实现你所列的第二点 (标明每个预约是哪位商户、哪位顾客参与),具体如下:

# 查询预约者:  
user = User.find(order.creator_id)
(user.type == "store")? (creator = Store.find(user.id)) : (creator = Customer.find(user.id))
# 查询被预约者同理

用户 (商户或者顾客) 查询自己参与的所有订单:
通过 store 或者 customer 记录可查询参与的所有预约:实现你所列的第四点 (返回该用户参与的所有 Appointment),具体如下:

owner_order = user.own_orders
appointed_orders = user.appointed_orders

写到一半还想到了另外一种方案: 用户系统不变,订单 model 不变,另外加一个 user_order 中间表记录 order_id, owner_id 和 target_id 注 以上思路均没实践过,可能有纰漏。不过用 Rails 写个 demo 也很快 demo

#5 楼 @liukun_lk 没错,后来我尝试使用了这种方法,感觉还不错。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号