在Rails 5中,如何在两个方向上创建多态的多对多关联

时间:2017-01-27 06:05:04

标签: ruby-on-rails namespaces many-to-many ruby-on-rails-5 polymorphic-associations

我有3个命名空间用户类 - Users::SuperAdminUserUsers::AdminUserUsers::StandardUser以及当前2个命名空间用户组类 - UserGroups::TeamUserGroups::Projects。我想在两个方向上创建多对多的多态关系(例如,管理员用户可以属于多个团队和多个项目,团队可以包含多个标准用户,多个超级管理员用户等)。

我已经尝试了一些方法,例如调整和混合这些解决方案(所有这些方法只在一个方向上是多态的),但我最终迷失在杂草中:

many to many polymorphic association

Rails: has_many through not returning correctly with namespaced models

http://www.informit.com/articles/article.aspx?p=2220311&seqNum=6

https://utkukaynarblog.wordpress.com/2016/04/21/polymorphic-has_many-through-associations-in-ruby-on-rails/

我主要使用了最后一个链接的方法。我试图通过添加一个表示用户类的“可列表”关注来向另一个方向建立关联多态性,作为现有“Memberable”关注点的补充,它表示用户组类(团队和项目,在我的情况下),但是没有用。

目前,我的用户模型如下所示:

module Users
  class SuperAdminUser < ApplicationRecord

    self.table_name = 'users_super_admin_users'

    include UserGroups::Enlistable

    has_many :user_groups_memberships
    has_many :user_groups_teams,    through: :memberships, source: :memberable, source_type: 'UserGroups::Team'
    has_many :user_groups_projects, through: :memberships, source: :memberable, source_type: 'UserGroups::Project'

  end
end

团队/项目类看起来像这样:

module UserGroups
  class Team < ApplicationRecord

    self.table_name = 'user_groups_teams'

    include Memberable

    has_many :user_groups_memberships
    has_many :users_super_admin_users, through: :memberships, source: :enlistable, source_type: 'Users::SuperAdminUser'
    has_many :users_admin_users,       through: :memberships, source: :enlistable, source_type: 'Users::AdminUser'
    has_many :users_standard_users,    through: :memberships, source: :enlistable, source_type: 'Users::StandardUser'

  end
end

会员类,如下所示:

module UserGroups
  class Membership < ApplicationRecord

    self.table_name = 'user_groups_memberships'

    belongs_to :enlistable, polymorphic: true
    belongs_to :memberable, polymorphic: true

  end
end

可登记/可元关注的问题如下:

module UserGroups
  module Enlistable
    extend ActiveSupport::Concern
    included do
      has_many :memberships, as: :enlistable, dependent: :destroy
      has_many :memberables, through: :memberships
    end    
  end
end

以及如下所示的成员资格迁移:

class CreateUserGroupsMembership < ActiveRecord::Migration[5.0]
  def change
    create_table :user_groups_memberships do |t|

      t.string  :enlistable_type
      t.integer :enlistable_id
      t.string  :memberable_type
      t.integer :memberable_id

      t.references :enlistable, polymorphic: true, index: { name: 'index_user_groups_memberships_on_enlistable_type_and_id' }
      t.references :memberable, polymorphic: true, index: { name: 'index_user_groups_memberships_on_memberable_type_and_id' }

      t.timestamps  null: false

    end
  end
end

一切都很好地迁移,我使用每个用户类的多个以及团队和项目类为数据库播种。但我从那里难倒。如果我有一个特定的用户,让我们说Users::SuperAdminUser.first,我想创建并访问它的关联,即将其分配给一个团队并列出他们所属的团队。如何设置它,并且一旦设置,我将如何执行这些操作?其中一个关键是能够在考虑命名空间的情况下完成此任务。

提前感谢您提供的任何指导。

1 个答案:

答案 0 :(得分:0)

看来我有一个解决方案,一个与命名空间一起工作的解决方案,并且足够灵活,您可以在应用程序中的任意两组命名空间类之间创建双向,多对多,多态关联。它会自动生成反向关联,删除模型删除时涉及模型的任何关联,并确保没有重复的关联记录。我不知道它是多么优化,或者它是否能很好地扩展,但它似乎完全符合我的需要。不过,对于任何预见到的问题或潜在的陷阱,我都很感激。

<强>〜/应用/模型/用户/ super_admin_user.rb

module Users
  class SuperAdminUser < ApplicationRecord

    include Polymorpher
    self.table_name = 'users_super_admin_users'

    Dir[Rails.root + 'app/models/user_groups/*.rb'].map { |f| File.path(f).to_s.sub(Rails.root.to_s + '/app/models/', '').chomp('.rb').camelize.constantize }
    associated_user_groups = UserGroups.constants.map { |user_groups_constant| UserGroups.const_get(user_groups_constant).to_s }

    has_many_through_polymorphs(associated_user_groups)

  end
end

我有admin_userstandard_user的其他类,它们与类和表名更改的明显例外相同。为了简洁起见,我省略了它们。

<强>〜/应用/模型/ user_groups / project.rb

module UserGroups
  class Project < ApplicationRecord

    include Polymorpher
    self.table_name = 'user_groups_projects'

    Dir[Rails.root + 'app/models/users/*.rb'].map { |f| File.path(f).to_s.sub(Rails.root.to_s + '/app/models/', '').chomp('.rb').camelize.constantize }
    associated_users = Users.constants.map { |users_constant| Users.const_get(users_constant).to_s }

    has_many_through_polymorphs(associated_users)

  end
end

我再一次为team增加了一个相同的类,同样也有类和表名更改的明显例外。

<强>〜/应用/模型/关系/ relationship.rb

module Relationships
  class Relationship < ApplicationRecord

    self.table_name = 'relationships_relationships'

    after_create { |relationship| create_reverse_relationship(relationship) }

    belongs_to :origination_class, polymorphic: true
    belongs_to :destination_class, polymorphic: true

    validates :origination_class_id, uniqueness: { scope: [:origination_class_type, :destination_class_id, :destination_class_type] }

    private

    def create_reverse_relationship(relationship)
      reverse_origination    = relationship.destination_class_type.constantize.find(relationship.destination_class_id)
      reverse_destination    = relationship.origination_class_type.constantize.find(relationship.origination_class_id)
      association_attribute  = relationship.origination_class_type.underscore.gsub('/','_').pluralize

      if !Relationships::Relationship.exists?(origination_class_type: reverse_origination.class.to_s,
                                              origination_class_id:   reverse_origination.id,
                                              destination_class_type: reverse_destination.class.to_s,
                                              destination_class_id:   reverse_destination.id)
        eval "reverse_origination.#{ association_attribute } << reverse_destination"
      end
    end

  end
end

<强>〜/应用/模型/关切/ polymorpher.rb

module Polymorpher
  extend ActiveSupport::Concern

  included do
    after_destroy { |origin_object| delete_destination_relationships(origin_object.class.to_s, origin_object.id) }

    def delete_destination_relationships(object_class_name, object_id)
      Relationships::Relationship.where(destination_class_type: object_class_name, destination_class_id: object_id).delete_all
    end

  end

  module ClassMethods

    def has_many_through_polymorphs(associated_classes)

      has_many :relationships_relationships,
               class_name: 'Relationships::Relationship',
               as: :origination_class,
               dependent: :destroy

      associated_classes.each do |associated_class|
        has_many associated_class.underscore.gsub('/','_').pluralize.to_sym,
                 through:     :relationships_relationships,
                 source:      :destination_class,
                 source_type: associated_class
      end

    end

  end

end

<强>〜/分贝/迁移/ 20170127022550_create_users_super_admin_user.rb

class CreateUsersSuperAdminUser < ActiveRecord::Migration[5.0]
  def change
    create_table :users_super_admin_users do |t|
      t.string :email
    end
  end
end

UsersUserGroups命名空间类的所有剩余迁移实际上与此相同,但具有UserGroups属性的:name类的迁移除外:email属性。

<强>〜/分贝/迁移/ 20170127036200_create_relationships_relationship.rb

class CreateRelationshipsRelationship < ActiveRecord::Migration[5.0]
  def change

    create_table :relationships_relationships do |t|

      t.references :origination_class, polymorphic: true, index: { name: 'index_relationships_relationships_on_origination_type_and_id' }
      t.references :destination_class, polymorphic: true, index: { name: 'index_relationships_relationships_on_destination_type_and_id' }

      t.timestamps null: false

    end

    add_index :relationships_relationships,
             [:origination_class_type, :origination_class_id, :destination_class_type, :destination_class_id],
               unique: true,
               name: 'index_relationships_relationships_unique_combo_of_all_columns'

  end
end

注意:此解决方案的灵感来自此交换中的解决方案:

https://www.ruby-forum.com/topic/203838

但是,我没有复制它,因为我无法从其他模型继承我的用户模型(例如链接示例中的PolyRecord),因为它们最终将继承自Devise用户模型。