Skip to content
This repository has been archived by the owner on Oct 19, 2018. It is now read-only.

Latest commit

 

History

History
145 lines (110 loc) · 4.28 KB

component-name-lookup.md

File metadata and controls

145 lines (110 loc) · 4.28 KB

Notes on how component names are looked up

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
---------------------------------

try-it

What we need to do is:

  1. when defining a component class Foo, also define in the same scope that Foo is being defined a method self.Foo that will accept Foo's params and child block, and render it.

  2. As long as a name is qualified with at least one scope (i.e. ModName::Foo()) everything will work out, but if we say just Foo() 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.

details

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

other related issues

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.