Given:
class Blat < React::Component::Base
render do
Bar()
Foo::Bar()
end
end
class Bar < React::Component::Base
end
module Foo
class Bar < React::Component::Base
render do
Blat()
Baz()
end
end
class Baz < React::Component::Base
end
end
The problem is that method lookup is different than constant lookup. We can prove it by running this code:
def try_it(test, &block)
puts "trying #{test}"
result = yield
puts "success#{': '+result.to_s if result}"
rescue Exception => e
puts "failed: #{e}"
ensure
puts "---------------------------------"
end
module Boom
Bar = 12
def self.Bar
puts " Boom::Bar says hi"
end
class Baz
def doit
try_it("Bar()") { Bar() }
try_it("Boom::Bar()") {Boom::Bar()}
try_it("Bar") { Bar }
try_it("Boom::Bar") { Boom::Bar }
end
end
end
Boom::Baz.new.doit
which prints:
trying Bar()
failed: Bar: undefined method `Bar' for #<Boom::Baz:0x774>
---------------------------------
trying Boom::Bar()
Boom::Bar says hi
success
---------------------------------
trying Bar
success: 12
---------------------------------
trying Boom::Bar
success: 12
---------------------------------
What we need to do is:
-
when defining a component class
Foo
, also define in the same scope that Foo is being defined a methodself.Foo
that will accept Foo's params and child block, and render it. -
As long as a name is qualified with at least one scope (i.e.
ModName::Foo()
) everything will work out, but if we say justFoo()
then the only way I believe out of this is to handle it via method_missing, and let method_missing do a const_get on the method_name (which will return the class) and then render that component.
To define self.Foo
in the same scope level as the class Foo
, we need code like this:
def register_component_dsl_method(component)
split_name = component.name && component.name.split('::')
return unless split_name && split_name.length > 2
component_name = split_name.last
parent = split_name.inject([Module]) { |nesting, next_const| nesting + [nesting.last.const_get(next_const)] }[-2]
class << parent
define_method component_name do |*args, &block|
React::RenderingContext.render(name, *args, &block)
end
define_method "#{component_name}_as_node" do |*args, &block|
React::Component.deprecation_warning("..._as_node is deprecated. Render component and then use the .node method instead")
send(component_name, *args, &block).node
end
end
end
module React
module Component
def self.included(base)
...
register_component_dsl_method(base.name)
end
end
end
The component's method_missing function will look like this:
def method_missing(name, *args, &block)
if name =~ /_as_node$/
React::Component.deprecation_warning("..._as_node is deprecated. Render component and then use the .node method instead")
method_missing(name.gsub(/_as_node$/,""), *args, &block).node
else
component = const_get name if defined? name
React::RenderingContext.render(nil, component, *args, &block)
end
end
The Kernel#p method conflicts with the
tag. However the p method can be invoked on any object so we are going to go ahead and use it, and deprecate the para method.