【问题标题】:Avoiding duplicate rows with many-to-many rails associations避免具有多对多导轨关联的重复行
【发布时间】:2016-02-23 05:37:57
【问题描述】:

我见过很多多对多关联,但似乎普遍的趋势是它们最终使用 has_many :through 关系。

假设您有以下内容:

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 行并保持查看关系的能力以高效的方式从两端开始,将创建这种关系的空间复杂度降低一半......

抱歉,如果这是一个菜鸟问题,但我是 Rails 的新手,刚刚开始掌握事情的窍门。很容易找到如何设置它们,但我发现很难找到如何以有效的方式实际实施它们

【问题讨论】:

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


    【解决方案1】:

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

    基本上,我有一个 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_toperson_2,那么我可以说person_1.people_i_relate_to 并得到返回person_2。或者,我可以说,person_2.people_that_relate_to_me 并返回 person_1

    我还开发了一个基于 acts_as_relating_to 模块的 acts_as_administering 模块,让我可以执行 person_1.administered_organizationsperson_1.administered_programs 之类的操作。

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

    干杯!

    【讨论】:

    • 非常简洁,不过有点过头了,不熟悉 ruby​​ 的反射 api 以及 class_eval 的作用
    猜你喜欢
    • 1970-01-01
    • 2022-08-03
    • 2020-06-09
    • 1970-01-01
    • 2015-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-27
    相关资源
    最近更新 更多