【问题标题】:Multi-target has_many through:多目标 has_man 通过:
【发布时间】:2018-10-12 20:37:48
【问题描述】:

我有三个模型:

User(name: string)
Course(name: string)
Assoc(user_id: integer, ta_id: integer, teach_id: integer, student_id: integer)

我希望能够将用户与课程相关联,如助教、教师或学生。我不确定这是否符合 ActiveRecord 中的 :polymorphic 想法。这是我到目前为止所拥有的,但它并不完全有效:

class User < ApplicationRecord
  has_many :tas, class_name: "Assoc", foreign_key: :ta_id
  has_many :teaches, class_name: "Assoc", foreign_key: :teach_id
  has_many :takes, class_name: "Assoc", foreign_key: :student_id

  has_many :ta_courses, through: :tas
  has_many :taken_courses, through: :tas
  has_many :taught_courses, through: :tas
end

它不工作:

irb(main):056:0> User.find(1).ta_courses
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
Traceback (most recent call last):
        1: from (irb):56
ActiveRecord::HasManyThroughSourceAssociationNotFoundError (Could not find the source association(s) "ta_course" or :ta_courses in model Assoc. Try 'has_many :ta_courses, :through => :tas, :source => <name>'. Is it one of ?)
irb(main):057:0>

任何指针将不胜感激!

【问题讨论】:

  • 我会考虑将所有内容捆绑到单个连接模型/表中是否真的是一个好主意。你可以用几个离散的模型/表来完成同样的事情,避免创建一个上帝类——更重要的是有更好的索引。而且,您所做的不是多态性 - ActiveRecord 中的多态性是您使用 id 和 type 列动态连接表的地方。
  • 你心目中的神级是哪一个?用户?我看到的两个替代方案似乎都更复杂:在用户周围具有实际的多态性,三种用户。或者,拥有三个独立的连接表。两者都更容易实现,确实如此,但似乎不必要地复杂。
  • Assoc 是神级,因为它将所有东西连接在一起并且没有单一的职责。
  • 单表继承并不是真正的多态。它只是使用相同的机制——类型列。多态性是您创建指向多个表的单个关联的地方。多态性没有实际的外键,而是在 Ruby 中动态解析表之间的链接。
  • @max 也许... 通常我将术语“神级”与具有很多功能的模型联系起来。这个几乎没有。但也许吧。

标签: ruby-on-rails database activerecord


【解决方案1】:

您似乎尚未建立从 Assoc 模型到 Course 模型的关系。你的 Assoc 模型应该有一个 course_id,所以它看起来像:

Assoc(user_id: integer, ta_id: integer, teach_id: integer, student_id: integer, course_id: integer)

那么你需要在 Assoc 模型上建立一个 belongs_to 关系:

class Assoc < ApplicationRecord
  belongs_to :course
end

最后,您在 User 模型中的 has_many through 关系没有正确构建(所有都通过 tas)。您的错误消息为您提供了关于您需要做的最后一件事的线索,即使用源识别别名关系:

class User < ApplicationRecord
  has_many :tas, class_name: "Assoc", foreign_key: :ta_id
  has_many :teaches, class_name: "Assoc", foreign_key: :teach_id
  has_many :takes, class_name: "Assoc", foreign_key: :student_id

  has_many :ta_courses, through: :tas, source: :course
  has_many :taught_courses, through: :teaches, source: :course
  has_many :taken_courses, through: :takes, source: :course
end

【讨论】:

  • 剪切/粘贴关于 :ta_id 重复的错字。
【解决方案2】:

如果我真的必须能够查询所有用户 -> 课程关系作为单个表,我会这样设置:

# rails g model assoc user:belongs_to course:belongs_to
class Assoc < ApplicationRecord
  enum role: [:student, :teacher, :assistent]
  belongs_to :user
  belongs_to :course
end

然后我们要在用户和课程中为每个角色设置范围关联:

has_many :student_assocs, -> { where(role: :student) }
  class_name: 'Assoc'

由于我们希望在两者中拥有完全相同的关联,因此可以使用模块保持其 DRY:

app/models/concerns/associated.rb
# dynamically creates assocations for each role in Assoc.roles enum
module Associated
  extend ActiveSupport::Concern
  included do
    # the plain base assocation
    has_many :assocs
    # this creates the assocations student_assocs, teacher_assocs, etc
    Assoc.roles.keys.each do |role|
      # We need to use eval for the second argument as we are creating the lambda dynamically
      has_many :"#{role}_assocs", eval( "->{ where(role: #{Assoc.roles[role]})}" ),
        class_name: 'Assoc'
    end
  end
end

Assoc.roles 给出了我们在 Assoc 中设置的枚举映射的哈希值。

然后我们可以在课程和用户中包含我们的模块并设置间接关联:

class Course < ApplicationRecord
  include Associated
  has_many :users, through: :assocs
  # this creates the assocations students, teachers, etc
  Assoc.roles.keys.each do |role|
    has_many role.pluralize.to_sym,
      through: "#{role}_assocs".to_sym,
      source: :user
  end
end

class User < ApplicationRecord
  include Associated
  has_many :courses, through: :assocs
   # this creates the assocations courses_as_student, courses_as_teacher, etc
  Assoc.roles.keys.each do |role|
    has_many "course_as_#{role}".to_sym,
      through: "#{role}_assocs".to_sym,
      source: :course
  end
end

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多