【问题标题】:Active Record Associations: has_and_belongs_to_many, has_many :through or polymorphic association?Active Record 关联:has_and_belongs_to_many,has_many:通过还是多态关联?
【发布时间】:2015-06-19 00:19:35
【问题描述】:

我正在开发的 Ruby on Rails 应用程序允许 users 创建 agendas 并与其他 users 共享。

此外,我们必须能够:

  • 在他的个人资料中为每个user 显示agendas 列表
  • 在议程页面上显示与agenda 关联的users 列表
  • 与其他用户共享议程时,为该用户定义一个role,并在上面提到的列表中显示该用户的角色

我打算在 useragenda 模型之间建立一个 has_and_belongs_to_many 关联,就像这样:

class User < ActiveRecord::Base
  has_and_belongs_to_many :agendas
end

class Agenda < ActiveRecord::Base
  has_and_belongs_to_many :users
end

但后来我想知道这是否能让我在给定用户的给定议程页面上获取并显示roles@user.agenda.user.role 列表。

而且我认为我可能应该使用has_many :through 关联,例如:

class User < ActiveRecord::Base
  has_many :roles
  has_many :agendas, through: :roles
end

class Role < ActiveRecord::Base
  belongs_to :user
  belongs_to :agenda
end

class Agenda < ActiveRecord::Base
  has_many :roles
  has_many :users, through: :roles
end

虽然我对user 有多个roles(每个agenda 一个)的想法很满意,但我不确定agenda 有几个roles(一个每个user?)。

最后,为了增加混乱,我读到了多态关联,并认为它也可能是一个可行的解决方案,例如,如果这样做:

class Role < ActiveRecord::Base
  belongs_to :definition, polymorphic: true
end

class User < ActiveRecord::Base
  has_many :roles, as: :definition
end

class Agenda < ActiveRecord::Base
  has_many :roles, as: :definition
end

上述任何解决方案听起来都适合这种情况吗?

更新:做一些研究,我偶然发现这篇文章(从 2012 年开始)解释说 has_many :through 是比 has_and_belongs_to_many 更“聪明”的选择。就我而言,我仍然不确定agenda 是否会有很多roles

更新 2:正如 @engineersmnkyn 在 cmets 中所建议的,解决此问题的一种方法是使用两个连接表。我尝试实现以下代码:

class User < ActiveRecord::Base
  has_many :agendas, through: :jointable
end

class Agenda < ActiveRecord::Base

end

class Role < ActiveRecord::Base

end

class Jointable < ActiveRecord::Base
  belongs_to :user
  belongs_to :agenda
  has_many :agendaroles through :jointable2
end

class Jointable2 < ActiveRecord::Base
  belongs_to :roles
  belongs_to :useragenda
end

我不确定语法。我在正确的轨道上吗?我应该如何定义AgendaRole 模型?

更新 3:如果我使用类似的东西会怎样:

class User < ActiveRecord::Base
      has_many :roles
      has_many :agendas, through: :roles
    end

    class Role < ActiveRecord::Base
      belongs_to :user
      belongs_to :agenda
    end

    class Agenda < ActiveRecord::Base
      has_many :roles
      has_many :users, through: :roles
    end

然后,在迁移文件中,使用以下内容:

class CreateRoles < ActiveRecord::Migration
  def change
    create_table :roles do |t|
      t.belongs_to :user, index: true 
      t.belongs_to :agenda, index: true
      t.string :privilege
      t.timestamps
    end
  end
end

我能否调用@user.agenda.privilege 来获得给定用户对给定议程的权限(创建者、编辑者或查看者的“角色”)?

相反,我可以调用@agenda.user.privilege 吗?

【问题讨论】:

  • 为什么不通过属于用户和议程的连接表设置用户拥有多个议程,并通过属于用户议程和角色的连接表具有多个议程角色。我会为你写下来,但我现在不在电脑前。
  • 感谢@engineersmnky 这很有意义,因为它解决了议程没有很多角色的问题。如果我正确理解您的建议,代码将类似于:repl.it/tNS 不过我不确定语法。

标签: ruby-on-rails activerecord database-design has-and-belongs-to-many polymorphic-associations


【解决方案1】:

好的,我先说我没有测试过这个,但我认为这两个选择之一应该适合你。

此外,如果这些连接表除了关系之外永远不需要功能,那么has_and_belongs_to_many 会更好,更简洁。

Rails 的基本经验法则:

如果您需要将关系模型作为自己的实体使用,请使用 has_many :through。在处理遗留模式或从不直接处理关系本身时使用 has_and_belongs_to_many。

首先使用您的示例 (http://repl.it/tNS):

class User < ActiveRecord::Base
  has_many :user_agendas
  has_many :agendas, through: :user_agendas
  has_many :user_agenda_roles, through: :user_agendas
  has_many :roles, through: :user_agenda_roles

  def agenda_roles(agenda)
    roles.where(user_agenda_roles:{agenda:agenda})
  end
end

class Agenda < ActiveRecord::Base
    has_many :user_agendas
    has_many :users, through: :user_agendas
    has_many :user_agenda_roles, through: :user_agendas
    has_many :roles, through: :user_agenda_roles

    def user_roles(user)
      roles.where(user_agenda_roles:{user: user})
    end
end

class Role < ActiveRecord::Base
  has_many :user_agenda_roles
end

class UserAgenda < ActiveRecord::Base
  belongs_to :user
  belongs_to :agenda
  has_many   :user_agenda_roles
  has_many   :roles, through: :user_agenda_roles 
end

class UserAgendaRoles < ActiveRecord::Base
  belongs_to :role
  belongs_to :user_agenda
end

这使用一个连接表来保存 User Agenda 的关系,然后使用一个表来加入 UserAgenda => Role。

第二种选择是使用一个连接表来保存用户议程的关系,另一个连接表来处理用户议程角色的关系。从 CRUD 的角度来看,此选项需要进行更多设置,例如验证用户是否是该议程的用户,但允许有一点灵活性。

class User < ActiveRecord::Base
  has_many :user_agendas
  has_many :agendas, through: :user_agendas
  has_many :user_agenda_roles
  has_many :roles, through: :user_agenda_roles

  def agenda_roles(agenda)
      roles.where(user_agenda_roles:{agenda: agenda})
  end

end

class Agenda < ActiveRecord::Base
    has_many :user_agendas
    has_many :users, through: :user_agendas
    has_many :user_agenda_roles
    has_many :roles, through: :user_agenda_roles

    def user_roles(user)
        roles.where(user_agenda_roles:{user: user})
    end
end

class Role < ActiveRecord::Base
  has_many :user_agenda_roles
end

class UserAgenda < ActiveRecord::Base
  belongs_to :user
  belongs_to :agenda
end

class UserAgendaRoles < ActiveRecord::Base
  belongs_to :role
  belongs_to :user
  belongs_to :agenda
end

我知道这是一个很长的答案,但在这种情况下,我想向您展示解决问题的不止一种方法。希望对你有帮助

【讨论】:

  • 非常感谢您抽出宝贵时间帮助@engineersmnky。我将尝试您的两种解决方案。同意您对 has_and_belongs_to_many 的限制,这些限制阻止我们直接处理这种关系。不过有一个问题:您如何看待我在 UPDATE 3 中发布的解决方案?
  • @ThibaudClement 我看到的问题是,您必须为每个新议程或用户多次设置相同的角色。我假设一个角色会是所有者、编辑者等,在这种情况下,您实际上只需要对每个角色的 1 个引用并且它是适用的权限,而不是每次都必须设置它们。如果我误解了此设置中的“角色”角色,那么您的解决方案可能对您有用,因为它是有效的关系。
  • @ThibaudClement 在这种情况下,您的第三次更新会很好,尽管如果您想创建其他角色,我可能会将其创建为多态。我认为 Role 不应该根据表中的字符串来处理所有这些逻辑。这似乎会在模型中创建很多条件逻辑。
  • @ThibaudClement 您可以根据“角色”制作多个视图,但这只是视觉约束而不是功能约束。我认为 Owner Viewer Editor 的单独模型会更有意义。但我对条件句的关注是@user.can_edit?(@agenda)
  • 那么方法必须类似于def can_edit?(agenda); roles.where(agenda: agenda).any?(&amp;:allow_edits?);end,而角色需要def allow_edits?; ['Owner','Editor'].includes?(self.privilege);end,您最终会使用includes?if...else或@得到更多这样的方法987654330@ 语句用于根据字符串确定权限。如果您制作这些模型并将关系变为多态,那么它将更像class Owner;def allow_edits?;true;end;end
猜你喜欢
  • 2012-01-01
  • 2012-10-24
  • 2012-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-25
  • 2014-12-06
相关资源
最近更新 更多