Skip to content

Instantly share code, notes, and snippets.

@bradgessler
Last active March 19, 2025 21:07
Show Gist options
  • Save bradgessler/2e59b4b8e2fba751299723dfdfd62083 to your computer and use it in GitHub Desktop.
Save bradgessler/2e59b4b8e2fba751299723dfdfd62083 to your computer and use it in GitHub Desktop.
Phlex handler
# config/initializers/phlex_template_handler.rb
require "action_view"
require "phlex"
# Intercept unknown "capitalized" method calls (e.g., PageView(...)) in templates,
# look up a Phlex component class, instantiate it, and render it.
# Crucially, we re-bind the user’s block so that `self` is the component, not the ActionView context.
module PhlexDynamicMethodCalls
def method_missing(name, *args, **kwargs, &block)
# Only intercept method calls starting with an uppercase letter (e.g. "PageView", "MyComponent", etc.)
if name[0] == name[0].upcase && Object.const_defined?(name)
klass = Object.const_get(name)
# If the constant is a Phlex component
if klass < Phlex::HTML
component = klass.new(*args, **kwargs)
# We need to ensure the user's block is evaluated in the context of `component`,
# rather than in the ActionView context. By default, `render_in` will capture
# the block using Rails helpers, which is not what we want.
#
# Instead, we wrap the block in a small "re-bound" block that calls
# `component.instance_exec(*yielded_args, &original_block)`.
# That way, inside the block, `self == component`, so methods like `h1`, `p`, etc. work.
if block
return component.render_in(self) do |*yielded_args|
# Evaluate the original block inside the component's context:
component.instance_exec(*yielded_args, &block)
end
else
# No block given
return component.render_in(self)
end
end
end
# Otherwise, pass it up the chain
super
end
def respond_to_missing?(name, include_private = false)
if name[0] == name[0].upcase &&
Object.const_defined?(name) &&
Object.const_get(name) < Phlex::HTML
true
else
super
end
end
end
class PhlexMethodMissingHandler
def self.call(template, source)
# We return Ruby code that extends the template's context with our method_missing module,
# then runs the original source code from the .html.rb file.
<<~RUBY
extend ::PhlexDynamicMethodCalls
#{source}
RUBY
end
end
# Tell Rails that ".html.rb" templates should use this custom handler:
ActionView::Template.register_template_handler :rb, PhlexMethodMissingHandler
PageView(
title: "This is a page"
){
erb <<~ERB
<h1><%= @title %></h1>
<p>Why would somebody want to put ERB in their Phlex view? Who knows</p>
ERB
div(class: "prose prose-md"){
markdown <<~MD
* This
* could
* be
* cool
MD
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment