从子对象引用父对象

Referencing parent objects from children

假设你有一个 User class:

class User
  attr_accessor :widgets
end

和一个Widget:

class Widget
  attr_accessor :owner
end

并且您将一些小部件分配给用户:

user = User.new
widget = Widget.new
widget.owner = user
widget2 = Widget.new
widget2.owner = user
user.widgets = [widget, widget2]

现在你有一个递归 userwidgetsowneruser.inspect 为每个小部件显示一次相同的 user 引用,使输出混乱:

user.widgets.first.owner.widgets.first.owner
=> #<User:0x00000001cac820 @widgets=[#<Widget:0x00000001ca45f8 @owner=#<User:0x00000001cac820 ...>>, #<Widget:0x00000001c87a20 @owner=#<User:0x00000001cac820 ...>>]>                                      

如果我们将这个数据结构简化为散列,我们将有:

{ user:
    { widgets: [ { widget: ... },
                 { widget: ... } ]
    }
}

我们可以传递它而不是分配 widget.owner 并且引用父 user.

会很容易

我想知道是否有一种方法可以通过子对象访问父对象而不必将 owner 分配给所有子对象,一个接口可以这样工作:

user = User.new
widget = Widget.new
user.widgets = [widget]
widget.parent
# => #<User:... @widgets=[#<Widget:...>]>

您要找的是一位定制作家。在 ObjectBaseObject class 上没有 parent 方法或等效方法,因为实现该方法需要对象跟踪碰巧指向它的所有其他对象。但是,当您需要该功能时,自定义编写器可以使实现变得简单易行。

class Widget
    attr_accessor :owner
end

class User
  attr_reader :widgets

  def widgets=(widgets)
    @widgets = widgets
    widgets.each do |widget|
      widget.owner = self
    end
  end
end

user = User.new
widget = Widget.new
user.widgets = [widget]
widget.owner #=> #<User:... @widgets=[#<Widget:...>]>

请注意,此自定义编写器仅涵盖常规作业,例如 user.widgets = [widget]。如果您想执行类似 user.widgets << widget 的操作,则不会为新小部件分配所有者。如果您希望能够做到这一点,您要么必须 monkeypatch Array like this (not recommended), or you'll have to create a WidgetCollection class that likely inherits from Array. That's what ActiveRecord::Associations 做到。说到这一点,如果您碰巧使用 Rails,一定要考虑使用 ActiveRecord 来为您完成所有这些工作。看起来你问的是普通的 ruby 所以我给你一个普通的 ruby 答案。

想分享我的解释。它没有确凿的证据,但可能会有所帮助。

首先,像这样的循环链接对象没有任何问题。如果循环链出现问题,代码将无法正常工作,它会崩溃或显示错误。所以它可能以某种方式处理这些类型的循环引用,但如果你理解变量只是对对象的引用,它真的很有意义。

我的意思是,当您简单地访问一个用户实例 user 时,它不会递归地加载其中的所有内容。它什么都不做,或者只是删除引用。真正设置递归的是 inspect 方法,它递归地检查实例内部的所有实例变量。但它确实处理了深度检查,使用 .....

所以你真正的问题应该只是让检查看起来紧凑。您可以覆盖该方法,这样它就不会递归,并给您一个很好的消息。示例:

class User
  attr_accessor :widgets
  def initialize
    @widgets =[]
  end
  def inspect
    "[User:objid=#{object_id};widgets=#{widgets.size}]"
  end
end

class Widget
  attr_accessor :owner

  def inspect
    "#[Widget:objid=#{object_id}]"
  end
end

界面可以保持不变。

user = User.new
widget = Widget.new
widget.owner = user
widget2 = Widget.new
widget2.owner = user
user.widgets = [widget, widget2]
user.widgets.first.owner.widgets.first.owner
# => #[User:objid=-590412418;widgets=2]