belongs_to belongs_to 仅关联 无 has_many 或 has_one

belongs_to belongs_to association only no has_many or has_one

你能在 Rails 中建立 belongs_to belongs_to 关系吗?

Search results 给了我两个结果。事实上,我找不到关于这个主题的太多信息,这似乎表明不应该这样做或者是不好的做法。

我问 yesterday 关于 has_many 关系,但我想因为我找不到这方面的信息,所以我会提出一个问题,这样人们就可以更容易地在未来。我在转述另一个用户的回答(希望没问题)。

一个鞋可以有很多袜子,但只有一个活跃的袜子。其他袜子处于非活动状态。所有的袜子也都是独一无二的,有着独特的图案。我想确保我没有相同图案的袜子。我想我可以通过三种方式做到这一点

class Sock < ActiveRecord::Base
  belongs_to :shoe
end

要确定袜子是活动的还是不活动的,请为其所有者鞋提供其活动袜子的参考,如下所示:

class Shoe < ActiveRecord::Base
  belongs_to :sock
end

转到它的所有者 Shoe 并检查 Shoe 的活动袜子是否是当前的袜子。例如

def is_active
  owner_shoe.active_sock == self

将它们与外键相关联

class CreateGettingDressed < ActiveRecord::Migration
  def change
    create_table :shoes do |t|
      t.belongs_to :active_sock, foreign_key: "sock_id"
      t.string :size
      t.timestamps null: false
    end

    create_table :socks do |t|
      t.belongs :owner_shoe, foreign_key: "shoe_id"
      t.string :pattern
    end
  end
end

在Rails中,belongs_to表示模型有外键。例如,如果一只袜子 belongs_to 一只鞋,那么你的袜子 table 将有一个 shoe_id 字段。您可以使用 a number of associations,但是让鞋子和袜子都使用 belongs_to 听起来不必要地复杂。

最直接的解决办法是稍微改变一下语言,把每只袜子想象成一只活动鞋。这样一来,如果您将一只袜子换成另一只鞋,那么您就不需要整理鞋子了。这样您的数据更有可能保持一致。

class Shoe < ActiveRecord::Base
  has_one :sock
end

class Sock < ActiveRecord::Base
  belongs_to :shoe
end

您现在可以调用 shoe.sock 获取鞋子的活动袜子,或 sock.shoe 获取袜子的鞋子。


或者,您可能希望每只鞋都有一个专门的袜子池。我们可以扩展我们以前的解决方案来支持它,将我们的关系命名为 active_sock 以使事情更清楚,并使用验证器来确保我们的袜子来自池:

class Shoe < ActiveRecord::Base
  has_many :socks
  has_one :active_sock, class_name: "Sock", foreign_key: "active_shoe_id"

  validates :active_sock_id, inclusion: { in: sock_ids }
end

class Sock < ActiveRecord::Base
  belongs_to :shoe

  def active?
    shoe.active_sock == self
  end
end

现在您可以调用 shoe.socks 获取鞋子的所有可用袜子,调用 shoe.active_sock 获取活动袜子。从袜子这边,然后 sock.shoe returns 哪只鞋 "owns" 这只袜子,以及 sock.active? returns 这只袜子当前是否处于活动状态。

您遇到的问题是您的两项功能存在冲突:

A Shoe can have many Socks, but only one active Sock

您希望将两个模型关联到两个不同的关联上。虽然这很简单,但我觉得你尝试做的方式有点受限。

这是我设置基础关联的方式:

#app/models/sock.rb
class Sock < ActiveRecord::Base
   #columns id | shoe_id | name | active (boolean) | created_at | updated_at
   belongs_to :shoe
end

#app/models/shoe.rb
class Shoe < ActiveRecord::Base
   #columns id | name | etc | created_at | updated_at
   has_many :socks
   scope :active, -> { where(active: true).first }
end

这将使您能够调用:

@shoe = Shoe.find 1
@shoe.socks.active #-> first sock with "active" boolean as true

它也不需要在您的 sock 模型中包含 active? 方法。您可以致电 @shoe.socks.find(2).active? 以获得关于它是否处于活动状态的响应。


现在,应该 可以很好地实现基本功能。

但是,您提到了几个扩展:

if a Sock is active or inactive

I want to make sure I don't have socks with the same pattern

这增加了我将使用 join 模型 (has_many :through) 处理的额外规范:

#app/models/sock.rb
class Sock < ActiveRecord::Base
   has_many :shoe_socks
   has_many :shoes, through: :shoe_socks
end

#app/models/shoe_sock.rb
class ShoeSock < ActiveRecord::Base
   # columns id | shoe_id | sock_id | pattern_id | active | created_at | updated_at
   belongs_to :shoe
   belongs_to :sock
   belongs_to :pattern
end

#app/models/shoe.rb
class Shoe < ActiveRecord::Base
   has_many :shoe_socks
   has_many :socks, through: :shoe_socks, extend: ActiveSock
   scope :active, -> { where(active: true).first }
end

您可以阅读以下代码的更多信息here:

#app/models/concerns/active_sock.rb
module ActiveSock

    #Load
    def load
      captions.each do |caption|
          proxy_association.target << active
      end
    end

    #Private
    private

    #Captions
    def captions
            return_array = []
            through_collection.each_with_index do |through,i|
                    associate = through.send(reflection_name)
                    associate.assign_attributes({active: items[i]})
                    return_array.concat Array.new(1).fill( associate )
            end
            return_array
    end

    #######################
    #      Variables      #
    #######################

    #Association
    def reflection_name
            proxy_association.source_reflection.name
    end

    #Foreign Key
    def through_source_key
            proxy_association.reflection.source_reflection.foreign_key
    end

    #Primary Key
    def through_primary_key
              proxy_association.reflection.through_reflection.active_record_primary_key
    end

    #Through Name
    def through_name
            proxy_association.reflection.through_reflection.name
    end

    #Through
    def through_collection
            proxy_association.owner.send through_name
    end

    #Captions
    def items
            through_collection.map(&:active)
    end

    #Target
    def target_collection
            #load_target
            proxy_association.target
    end

此设置基本上会将所有“逻辑”放入连接模型中。在 IE 浏览器中,您将拥有 socks 数据库、shoes 数据库和一个包含两者配对的连接数据库。

这仍然允许您调用 @shoe.socks.active,但不必降低数据模型中的数据完整性。

我还添加了一些我不久前写的代码——它使您能够从连接模型访问属性。它使用 ActiveRecord 中的 proxy_association 对象,因此不再调用 SQL.

此添加的代码会将 active? 属性附加到任何关联的 Sock 对象。