Trying to extend functionality to ease usage/integration of IceCube
in a Rails
context, ended up using Refinements.
After encountering some limitations and reading some documentation, I am facing a last challenge that I have not seen documented anywhere. How comes the example below does not work.
The target class to extend functionality with some generic Utils
is Foo
:
class Foo; end
module Utils
def bar; "bar"; end
end
This is done with a refinement called RefFoo
, where some other refinements are meant to use Utils
(i.e. call_bar
):
module RefFoo
refine Foo.singleton_class do
include Utils
def call_bar; bar; end
end
end
- Refining the class methods as explained in this post.
Let's say we are using
the refinement within the context of a module called RefinedFoo
(in the real case, a Rails
service class):
module RefinedFoo
using RefFoo
class << self
def bar; Foo.bar; end
def call_bar; Foo.call_bar; end
end
end
Although the refinement is active, as shown by the result of the class method Foo::bar
(via RefinedFoo::bar
), invoking Foo::call_bar
(via RefinedFoo::call_bar
) throws a NameError
:
RefinedFoo.bar
#=> "bar"
RefinedFoo.call_bar
#=> NameError (undefined local variable or method `bar' for Foo:Class)
Foo::call_bar
exists, however, forFoo::call_bar
the methodFoo::bar
does NOT exist, regardlessinclude
was called.
There seems not to be a way around it, if we rewrite RefFoo
to include an already mixed module, the error persists:
module RefFoo
module Mixin
include Utils
def call_bar; bar; end
end
refine Foo.singleton_class do
include Mixin
end
end
# define RefinedFoo the same way
RefinedFoo.call_bar
#=> NameError (undefined local variable or method `bar' for Foo:Class)
And yet, if you extend
RefFoo
in a module, the error won't show up:
def Bar
extend RefFoo::Mixin
end
Bar.call_bar
#=> "bar"
Obviously, I could just create a new class called MyFoo
that inherits and extends Foo
(i.e. MyIceCube
) and work from there. Before I do, it would be great if I could get a bit more of context. Among other readings, went through these:
- Why is nobody using Refinements? Where it largely refers to the lexical scope of the
using
statement. Can't see how this applies to this example, though. - Cannot refine String with CONSTANT This led to a bug report on ruby master where the team referred to constants and class variables as belonging to outer class, being prudent to do not touch this due to backwards incompatible changes to constant scoping rules.
- How do I use an ActiveSupport Concern to add a refinement to a class (I think that this one has a similar problem?)
Still unsure how the code above fits in any of the posts and articles. Are refinements just a method to implement very small and well localized (self-contained) patches? (i.e. can't include
libraries of helpers to extend classes) and are NOT meant to be used for extending classes just in specific scopes of your app? Or I have just landed to this problem because I am trying to refine at class methods level?