Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parent/children relationship in ruby?

Tags:

ruby

I want to know which is the best way to create a two-ways father-children relationship in ruby. For example, the following code:

class Bar
  def initialize(name)
    @name = name
   end
end

class Foo
  attr_accessor :children
  def initialize(names = %w(john ben maudy))
    @children = names.map{|n| Bar.new(n)}
  end
end

With this, it is fairy easy to get from a Foo instance to its Bar children. But it doesn't have the other way around. For for example:

# Instanciates a Foo and get his first son
bar = Foo.new.children.first
# bar can't get to his parent. Essentially, I want 
bar.foo

The only idea I had so far is to pass self in the Bar#new method, to keep the reference on the bar objects, but I would like to avoid this. Can you explain me a better way?

EDIT

From google I could find the caller method, which gives me the line number. However I was looking for an object reference, a little more abstract than the line number.

EDIT 2

You can do this in python

EDIT 3

This spinoff of pry seems to be best fit. If anyone can find a way to do this without the gem, I might answer my own question with it.

like image 810
fotanus Avatar asked Jul 09 '26 17:07

fotanus


2 Answers

This is a strong composition, where the component couldn't be built without a reference to the composite.

That means you need to initialized them with that composite (i.e. pass self to the Bar instance). This is the way to do it, as you need a reference to the owner. You can possibly set it after, for instance bar.foo = foo, but it will implement a looser link, an aggregation.

If we don't receive a reference, we need to find it somewhere. We can make Foo a singleton, but it's an anti-pattern as it's like a global variable. Bar instances could request to another object what's the reference of their Foo. If there is multiple instances of Foo, you need to identify them, with an id for instance, but that means Bar instances need that id. Then, why not working directly with the reference of the instance of Foo?

As conclusion, IMHO the only good solution consists in passing a reference of Foo when building a new Bar instance. If the intention is to respect OO paradigm, IMHO it's bad idea to hide that important relation implementation.

Code for the initialization of Bar instance with Foo instance

class Bar # Class of the component
  attr_reader :foo

  # the component must be initialized/built
  # with a ref to the composite foo
  def initialize(name, foo) 
    @name = name
    @foo = foo
   end
end

class Foo # Class of the composite
  attr_reader :children

  def initialize(names = %w(john ben maudy))
    @children = names.map do |n|
      Bar.new(n, self) # pass self to build the component
    end
  end
end

bar = Foo.new.children.first
bar.foo

Update about binding_of_caller

It's an interesting project making accessible the callstack. In the current case, it would be used in Bar::initialized method to find the composite, who is the caller, and ask it to return self.

However, the project needs to extend the Ruby implementation. It's then not pure ruby.

like image 195
toch Avatar answered Jul 11 '26 08:07

toch


I think you should just pass foos into Bar's constructor like:

class Bar
  def initialize(name, parent)
    @name = name
    @parent = parent
   end
end

class Foo
  attr_accessor :children
  def initialize(names = %w(john ben maudy))
    @children = names.map{|n| Bar.new(n, self)}
  end
end

This way each bar will have a reference to the foo that created it.

If you really don't want to pass foo into bar's constructor, you could also do something like the following, but I don't think it gains you much.

class Bar
  attr_accessor :foo
  def initialize(name)
    @name = name
   end
end

class Foo
  attr_accessor :children
  def initialize(names = %w(john ben maudy))
    @children = names.map{|n| Bar.new(n)}
  end
  def get_nth_child(n)
    bar = @children[n]
    bar.foo = self
    bar
  end
end

and then you access your original foo like:

Foo.new.get_nth_child(1).foo
like image 37
Andbdrew Avatar answered Jul 11 '26 07:07

Andbdrew