避免具有多对多轨道关联的重复行

时间:2016-02-23 05:37:58

标签: sql ruby-on-rails ruby activerecord rails-activerecord

我见过许多多对多关联,但似乎共同的趋势是他们最终使用has_many:通过关系。

假设您有以下内容:

class User < ActiveRecord::Base
    has_many :user_relationships
    has_many :relations, :through => :user_relationships
end

class UserRelationship < ActiveRecord::Base
    belongs_to :user
    belongs_to :related_user, class_name: "User"
end

鉴于此类关系,您最终将关系设置如下:

user1.relations << user2
user2.relations << user1

或者这个:

user1.user_relationships.build(related_user_id: 2)
user2.user_relationships.build(related_user_id: 1)

导致连接表中的行如下所示:

user_id | related_user_id
1       | 2
2       | 1

因此,在设置上述关系时,您可以看到可以完成以下任务

user1.relations.include? user2 = true
user2.relations.include? user1 = true

我的问题是:在Rails中是否有办法完成上述任务,或至少与上述内容类似,不必为每个单向双向关系创建2行,并保持查看关系的能力从两端以有效的方式,将创建这种关系的空间复杂性减少一半......

道歉,如果这是一个noobie问题,但我是Rails的新手,只是开始了解事情。很容易找到如何设置它们,但我发现更难以找到如何以有效的方式实际实现它们

1 个答案:

答案 0 :(得分:0)

这是(大致)我所做的。我将跳过大量细节,但如果它有用,我很乐意分享更多细节。这可能很令人发指,所以我很好奇别人的想法。

基本上,我有Relationship模型,如:

module ActsAsRelatingTo
  class Relationship < ActiveRecord::Base

    validates :owner_id,                  presence: true
    validates :owner_type,                presence: true
    validates :in_relation_to_id,         presence: true
    validates :in_relation_to_type,       presence: true

    belongs_to  :owner,                   polymorphic: true
    belongs_to  :in_relation_to,          polymorphic: true

    acts_as_taggable
    acts_as_taggable_on :roles

  end
end

然后,我创建了一个acts_as_relating_to模块(再次,掩盖了细节),其中包括:

module ActsAsRelatingTo
  def acts_as_relating_to(*classes_array)

    # re-opens the class at run time so I can do things like add 
    # new instance methods on-the-fly
    class_eval do 

      before_destroy :tell_to_unrelate

      has_many :owned_relationships,
        as: :owner,
        class_name: "ActsAsRelatingTo::Relationship",
        dependent: :destroy

      has_many :referencing_relationships,
        as: :in_relation_to,
        class_name: "ActsAsRelatingTo::Relationship",
        dependent: :destroy

      # iterates through arguments passed in 'acts_as_relating_to' call
      # in the Person model, below.
      classes_array.each do |class_sym| 

        # This is a method created on-the-fly. So, when I call 
        # (in the Person model, below) 'acts_as_relating_to :people', 
        # then I get a method on each instance of 'Person' called 
        # 'people_that_relate_to_me. You can also create class methods
        # using `define_singleton_method`.
        #
        # The reason for doing all of this via the define_method 
        # is that it lets me create any number of 'things_i_relate_to'
        # methods on any class descending from ActiveRecord::Base 
        # (more on this in a bit). Which, if I understand it 
        # correctly, is how a lot of the ActiveRecord functionality gets
        # into a model in the first place. 
        define_method(class_sym.to_s + "_that_relate_to_me") do |options={}|
          ... some stuff
        end

        # Same as above, but know I'm defining a method called 
        # 'people_i_relate_to'
        define_method(class_sym.to_s+"_i_relate_to") do |options={}|
          ... some more stuff
        end

        # I can also create static methods and incorporate them in the 
        # 'Person' class. I just define them in modules (such as 
        # ActsAsRelatingTo::InstanceMethods and ActsAsRelatingTo::ClassMethods) 
        # and then either 'include' (for instance methods) or 'extend'
        # (for class methods) them.
        include InstanceMethods
        extend ClassMethods

      end
    end
  end
end

# Here, I'm telling ActiveRecord::Base to 'extend' this module. 
# That makes the 'acts_as_relating_to' method available in 
# any class that descends from ActiveRecord::Base.
ActiveRecord::Base.extend ActsAsRelatingTo

然后,我可以做类似的事情:

class Person < ActiveRecord::Base
  # Here, I am calling the method that I defined above, passing in
  # :people, :organizations, and :programs. This is exactly the 
  # sort of thing you do all the time when you say something like
  # 'has_one :foo', or 'belongs_to :bar'. 
  acts_as_relating_to :people, :organizations, :programs

  # Here, I am calling a method I have that builds on acts_as_relating_to,
  # but which I did not show, that creates administrative methods on 
  # the person so that I can say stuff like 'person.administrate organization'.
  # Or, 'organization.administrators'. 
  acts_as_administering :organizations, :programs

  ... 

end

所以,如果我有person_1person_2并且我有一个关系记录ownerperson_1in_relation_to是{{1}然后我可以说person_2然后回到person_1.people_i_relate_to。或者,我可以说,person_2并返回person_2.people_that_relate_to_me

我还推出了基于person_1模块构建的acts_as_administering模块,让我可以执行acts_as_relating_toperson_1.administered_organizations等内容。

我可能已经喋喋不休了太长时间。无论如何,如果它很有趣,我可以说更多。

干杯!