This demo shows how to write Rails views directly in Ruby using Phlex instead of ERB or Haml. Instead of mixing Ruby into HTML, the view file is a Ruby class that renders HTML.
Rails normally renders .html.erb (ERB), .html.haml (Haml), etc.
Each extension maps to a template handler that turns the file’s source into Ruby code.
Here we register a new handler for .rb files. It takes the raw Ruby source,
wraps it in a tiny Phlex::HTML subclass, and lets Rails render it.
That means a view file like:
# app/views/application/index.rb
h1 { "Hello, world!" }
p { "This is a Phlex view written in plain Ruby." }is equivalent to writing:
class MyView < Views::Base
def view_template
h1 { "Hello, world!" }
p { "This is a Phlex view written in plain Ruby." }
end
end
MyView.new.render_in(view_context)The custom handler (config/initializers/phlexml.rb) looks like this:
# frozen_string_literal: true
module Phlex::Markup
class Handler
def self.call(template, source = nil)
src = source || template.source
<<~RUBY
# Define an anonymous subclass of Phlex::HTML
__phlex_class__ = Class.new(::Views::Base) do
def view_template
#{src}
end
end
# Instantiate and render it
__phlex_class__.new.render_in(self).to_s
RUBY
end
end
end
ActionView::Template.register_template_handler :rb, Phlex::Markup::HandlerWhen Rails compiles the view:
- The handler generates a Ruby string that defines an anonymous subclass
of
Views::Base(aPhlex::HTMLbase class you define for your app). - The view source (
h1 { … }, etc.) becomes the body ofview_template. - The handler instantiates the class and calls
render_in(self)with the Rails view context. - Rails caches the compiled method, so in production there’s no runtime penalty.
- All Ruby, no ERB. No
<%= %>— just method calls. - Full power of Phlex. Components, layouts, helpers — all work as normal.
- Explicit. Each
.rbview is compiled into a real Ruby class.
- In development, Rails recompiles templates when the file changes.
- In production, templates are compiled once and cached.
- Because the handler uses
Class.new, a fresh anonymous class is created each compile. If you want to reuse a stable constant per file, you can constantize with the template path hash. (Not required for production.)
# app/views/posts/show.rb
h1 { @post.title }
p { @post.body }
aside do
a(href: posts_path) { "Back to posts" }
endThis produces:
<h1>My Post Title</h1>
<p>The post body…</p>
<aside>
<a href="/posts">Back to posts</a>
</aside>- Wire
.html.rbinstead of bare.rbfor proper content negotiation. - Explore layouts by having
Views::Baseprovide a wrapperview_template. - Consider constantizing template classes for better debugging.