The questions have been placed at the end, as they might not be clear without some context.
The idea is that we can dynamically inherit from Foo
and redefine some of its class dependencies throughout the inheritance chain of a client class (Bar
> Baz
). Below a try to reflect this idea:
Foo
▼
Bar.foo --> FooChild <id: Bar.id)
▼ ↕
Baz.foo --> FooChild <id: Baz.id)
This approach aims to reuse the class Foo
by changing its class dependency id
. So depending on what client class uses Foo
(or children thereof), Foo.id
will refer to a different String
(as reconfigured by Bar
and Baz
).
Just for clarity, see below a seemingly more specific example, where the analogy is as follows: Collection
is to Foo
as Room
is to Bar
and Library
to Baz
. If to be at ease we referred to Foo.id
, in this case we refer to Collection.item_class
; the class variable of Collection
that will help to retrieve the Class of the items of a Collection
instance object.
Collection
▼
Room.things --> CollectionChild <item_class: Thing)
▼ ↕
Library.things --> CollectionChild <item_class: Book)
We want to define the things
method (foo
) only once in the parent class Room
(Bar
). Rather than redefining the things
(foo
) method in the Library
class (Baz
), the purpose is to redefine item_class
dynamically.
The code below synthesizes somehow the context of the problem:
class Foo
class << self
attr_accessor :id
def resolved_id
id_class, id_method = id.first
id_class.send(id_method)
end
end
def identify
puts "Referring to '#{self.class.resolved_id}'"
end
end
class Bar
class << self
def embed(method, klass:, data:)
define_method "#{method}" do
Class.new(klass) {|child| child.id = data}.new
end
end
def id; "bar"; end
end
embed :foo, klass: Foo, data: {self => :id}
end
class Baz < Bar
def self.id; "baz"; end
end
Baz.new.foo.identify
The output is:
Referring to 'bar'
I would rather prefer to achieve:
Referring to 'baz'
Bar
defines a dynamic class method foo
that creates a child class of Foo
(let's call it FooChild
) where FooChild.id
is initialized with a reference to the Bar.id
class method.Foo
and Bar
are classes that do not belong to the same inheritance chain, Baz
is a child class of Bar
and redefines the class method id
.While resolving dependencies from within the same inheritance chain gets easy (i.e. redefining methods such as Bar.id
in Baz.id
). To resolve dependencies from a non parented class requires to pass the reference to the current class (see data: {self => :id}
), not just the sym method (:id
), because the method may not exist or may be unrelated in the class that consumes this dependency.
It may be that the entire approach is incorrect and adds unnecessary complexity, when compared to just explicitly redefining things, rather than trying to make everything configurable and reusable. May be configurable classes can be seen as both: a time savior when used in moderation and an aberration and an anti-pattern when used in excess.
What is difficult is to see when you are crossing the line.
In overall, it seems unlikely that this can be resolved without redefining foo
in the child class Baz
like this:
class Baz < Bar
def self.id; "baz"; end
embed :foo, klass: Foo, data: {self => :id}
end
Bar.new.foo.identify
Baz.new.foo.identify
The output:
Referring to 'bar'
Referring to 'baz'
However, if that was the case, that line would look exactly the same as in the parent class Bar
; just that self
would refer to a different class.
:id
from Bar
to FooChild
(when the method foo
is generated) that can be redefined from the child class Baz
? (without redefining foo
in the child class).Rails
posts around constant lookup and resolution, I am keen to hear some "simpler" yet effective as, alternative methods.Thanks in advance.
You seem to be overcomplicating it a bit. The issue stems from the reference to self
in the embed
call on Bar
.
Since that self happens to be on the class level of Bar
, it is frozen to be Bar
the moment the line is evaluated.
If you call that self inside the foo
method instead, it is evaluated at runtime and will be whatever class the method is called on.
class Bar
class << self
def id; "bar"; end
end
def foo
klass = self.class
Class.new(Foo) {|child| child.id = {klass => :id}}.new
end
end
Everything else unchanged.
Baz.new.foo.identify #=> Referring to 'baz'