Hello Group! Newbie here. I was playing around trying to create a MVC like framework but I’m having difficulties trying to get this work. Can someone explain what’s wrong here?
annotation Route
end
macro setup_handlers
{% for m in @type.methods %}
{% if a = m.annotation(Route) %}
if path == {{a.args[0]}}
puts "executing {{m.name}} on {{@type.name}}"
{{m.name}}()
return
end
{% end %}
{% end %}
{% debug %}
end
abstract class Controller
def call(path)
setup_handlers
end
end
class Foo < Controller
@[Route("/info")]
def info
puts "in B::info"
end
end
class Bar < Foo
@[Route("/info")]
def info
puts "in C::info"
end
end
Bar.new.call("/info")
This works as expected but if I remove the info() from Bar I’d expect it to find “/info” in the parent class, Foo but it doesn’t! Any idea what I’m doing wrong here?
Also, how do I ensure that the most derived methods show up first in the search (in setup_handlers) I see the @type.methods don’t take any arguments!
Hi! #methods doesn’t include methods from parent types.
You can do this:
macro setup_handlers
{% for type in [@type] + @type.ancestors %}
{% for m in type.methods %}
{% if a = m.annotation(Route) %}
if path == {{a.args[0]}}
puts "executing {{m.name}} on {{type.name}}"
{{m.name}}()
return
end
{% end %}
{% end %}
{% end %}
{% debug %}
end
It would probably be nice to have a simpler way to list all methods, including those from ancestors. Maybe in the future? :-)
Works like a charm! Thanks a bunch @asterite! On a different note, is there any reason why a macro would work when embedded inline but not work when defined as a macro by itself?
Sure, here it is. This as is, refuses to work, ie, no handlers are attached for some reason (won’t print any “found x…” as well), debug comes up empty.
macro setup_handlers
{% for type in [@type] + @type.ancestors %}
{% for m in type.methods %}
{% if a = m.annotation(Route) %}
if path == {{a.args[0]}}
{{puts "found #{m.name} on #{type.name}"}}
{{m.name}}(env)
return
end
{% end %}
{% end %}
{% end %}
{% debug %}
end
abstract class Controller
include HTTP::Handler
def call(env)
setup_handlers
call_next(env)
end
end
but if I paste the whole macro body inline like so, it seems to work!
abstract class Controller
include HTTP::Handler
def call(env)
{% for type in [@type] + @type.ancestors %}
{% for m in type.methods %}
{% if a = m.annotation(Route) %}
if path == {{a.args[0]}}
{{puts "executing #{m.name} on #{type.name}"}}
puts "executing {{m.name}} on {{type.name}}"
{{m.name}}(env)
return
end
{% end %}
{% end %}
{% end %}
call_next(env)
end
end
macro setup_handlers
{% verbatim do %}
{% for type in [@type] + @type.ancestors %}
{% for m in type.methods %}
{% if a = m.annotation(Route) %}
if path == {{a.args[0]}}
{{puts "found #{m.name} on #{type.name}"}}
{{m.name}}(env)
return
end
{% end %}
{% end %}
{% end %}
{% debug %}
{% end %}
end
verbatim do escapes the macro code so that i’ll evaluate in the scope that the macro is used, i.e. in your call method. Currently its being evaluated within the macro itself, which doesn’t have any methods/parent types.
Right, when you call setup_handlers manually, the @type is just the top-level type, not the type where the macro is called.
There’s a discussion about whether that’s okay or not, but we can’t change it right now because it’s a breaking change. There should be a way to get the type in the scope and the type where the macro is declared.
Thanks for the background – I think it feels more natural to have the macro expand in the lexical scope as is, I think that’s what LISP does if I’m not mistaken – it’s nice to know there’s on-going discussion about this!