The questions have been placed at the end, as they might not be clear without some context.
The purpose
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.
Example
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'
Explanation
Bardefines a dynamic class methodfoothat creates a child class ofFoo(let's call itFooChild) whereFooChild.idis initialized with a reference to theBar.idclass method.- While
FooandBarare classes that do not belong to the same inheritance chain,Bazis a child class ofBarand redefines the class methodid.
The problem
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.
The solution I am trying to avoid
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.
Questions
- Is there a way to provide a reference to
:idfromBartoFooChild(when the methodfoois generated) that can be redefined from the child classBaz? (without redefiningfooin the child class). - After skimming through some
Railsposts 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
selfin theembedcall onBar.Since that self happens to be on the class level of
Bar, it is frozen to beBarthe moment the line is evaluated.If you call that self inside the
foomethod instead, it is evaluated at runtime and will be whatever class the method is called on.Everything else unchanged.