Beautiful RubyEssays, tutorials, and deep dives on writing Ruby that feels as good as it reads.

Video Course
7+ hours ยท 45 lessons
I've been running SQLite in production on a few Rails apps. No Postgres process to manage, no connection pool to tune, backups are just file copies. But one thing caught me off guard: how do you replace the database file on a running server?
## The problem
Say you have a content-heavy Rails app. You build the SQLite database locally (import from a CMS, run transforms, generate search indexes) then ship that file to production. With Postgres you'd `pg_restore` into the running database. With SQLite, the database *is* a file. You need to swap it.
The naive approach is `scp` the file over and restart the server. That works, but it means downtime. Your app is offline while Puma restarts, reconnects, and warms up. For a content update that should take milliseconds, you're paying seconds.
You could try `mv new.db production.db` while the app is running, but that's a race condition. ActiveRecord might be mid-query when you yank the file out from under it. You'll get `BusyException` or worse, a corrupted read.
I wanted something like `pg_restore` but for SQLite: push a new database to the running server, validate it, pause requests for a few microseconds while it swaps, then resume.
## What Hotswap does
[Hotswap](https://github.com/rubymonolith/hotswap) adds a Unix socket server to your Rails app. You send it a new SQLite database, it validates the file, then atomically swaps it into place. Incoming requests queue for microseconds during the swap, then resume on the new database.
```ruby
gem "hotswap"
```
The railtie handles everything: it inserts a Rack middleware that wraps requests in a mutex, starts the socket server when Puma boots, and discovers your SQLite databases from `database.yml`.
### Pushing a database
```bash
bin/hotswap cp ./new.sqlite3 db/production.sqlite3
```
Here's what happens:
1. The CLI connects to the socket server inside your running Rails process
2. The database streams to a temp file on the server
3. `PRAGMA integrity_check` verifies the file isn't corrupt
4. Schema is compared against the running database
5. Swap lock acquired, incoming requests queue
6. ActiveRecord disconnects, temp file is atomically renamed, ActiveRecord reconnects
7. Lock releases, queued requests resume on the new database
If the integrity check or schema check fails, the running database is untouched. Nothing gets swapped.
MAR 2026
Replace a SQLite database on a running Rails server without restarting
I've been playing around with adding AI features to Rails apps and ran into a problem: tool calling doesn't scale.
The standard approach is to define discrete functions, the LLM picks which one to call, and I execute it. That works great for fixed actions like "cancel order" or "send refund". But then a customer asks "what's my total spend on shipped orders this year?" and I realize I need a `total_spend_by_status_and_date_range` tool I didn't build.
I could add that tool. Then add another one for "average order value by month". Then another for "orders over $100 in Q3". The tool list grows with every new question, each one is a round-trip, and I'm forever playing catch-up.
## What if the LLM just wrote the code?
One `eval` call replaces dozens of specialized tools:
```ruby
orders().select { |o| o["status"] == "shipped" }.sum { |o| o["total"] }
```
The LLM can reason in code. It fetches data, filters it, computes a result. No back-and-forth. No predefined tool for every possible query.
The problem is obvious: `eval` in your Ruby process is catastrophic. The LLM can do anything your app can do. `User.destroy_all`. `File.read("/etc/passwd")`. `system("curl attacker.com")`. One prompt injection in a ticket body and you're done.
## What Enclave does
[Enclave](https://github.com/rubymonolith/enclave) lets you run LLM-generated Ruby without giving it access to your system. It embeds [MRuby](https://mruby.org), a lightweight Ruby interpreter, as a separate VM inside your process. This VM has no file system, no network, no access to your CRuby runtime.
Here's what a tools class looks like for a customer service agent:
```ruby
class CustomerServiceTools
def initialize(customer)
@customer = customer
end
def customer_info
{ id: @customer.id, name: @customer.name, email: @customer.email,
plan: @customer.plan, created_at: @customer.created_at.to_s }
end
def orders
@customer.orders.order(created_at: :desc).map do |o|
{ id: o.id, total: o.total.to_f, status: o.status, created_at: o.created_at.to_s }
end
end
FEB 2026
An MRuby sandbox for running arbitrary Ruby code from LLMs
Dave Thomas's ["Stop Using Classes"](https://www.youtube.com/watch?v=sjuCiIdMe_4) talk at [SFRuby](https://www.meetup.com/sf-ruby/) has me rethinking how I use modules. His argument is simple: Ruby developers reach for classes by default when modules are often the better tool.
I kept writing these little singleton objects that don't hold state. They're just bags of behavior and data. Every time I reached for a class, I'd end up with an `initialize` that does nothing and a `.new` call that exists only because classes demand it. Dave's talk gave me the push to stop doing that and think harder about modules.
## The problem with `extend self`
The standard module-as-singleton pattern is `extend self`:
```ruby
module Wordpress
extend self
def name = "WordPress"
def match?(doc) = doc.at_css('meta[name="generator"][content^="WordPress"]')
end
Wordpress.name # => "WordPress"
Wordpress.match?(doc) # => true/false
```
It works, but it's boilerplate. Every module needs it. And if you want shared behavior across a family of these singleton modules, you're stuck writing it into each one or reaching for metaprogramming. That's exactly the kind of thing `include` should solve.
## What I actually want
I'm building a framework detector for [OpenGraph+](https://opengraphplus.com) that inspects HTML documents and identifies what platform built them. Each framework is a singleton module that knows its name, its slug, and which CSS selectors identify it.
Dave's talk got me thinking about what the ideal version of this looks like:
1. Each framework is a module with instance-style method definitions
2. Including a shared module auto-wires everything up
3. All frameworks register themselves automatically
4. The parent module provides a `detect` method to find which framework matches
Here's what I landed on:
```ruby
module Framework
@all = []
def self.all = @all
FEB 2026
Using Ruby modules as singleton objects without extend self

Phlex on Rails
This week I've seen a lot of posts characterizing RSpec and factories as inherently slow and inferior to Minitest and fixtures.
[](https://x.com/ryanrhughes/status/2019258699001294911?s=20)
The premise that spec-based development with factories is inherently slower than minitest and fixtures didn't pass my smell test, so I dug in to find out why people are seeing such dramatic speed-ups in these rewrites, and figure out if something could be fixed with RSpec or FactoryBot so we could keep our nice syntax.
## Factories are slow
Ok so turns out, factories are slow. Before I dug into this, I didn't understand exactly why, but I found out that it's way too easy to accidentally create boatloads of records when setting up factories.
The reason? Setting up a single user factory might seem fast at first glance, but it might have to create an associated account record, which might have associated billing records, which might have ... it turns out these graphs can get quite large.
Fixtures are fast because the data is loaded directly into the database for a suite, then each test starts a transaction and rolls it back. So much faster!
## Fixture syntax is tedious
The reason I don't like fixtures isn't because I like factories, it's because I like the syntactic sugar of factories.
I did what any self-respecting Rubyist would do and created a gem with the syntactic sugar of factories and the speed of fixtures called [FixtureBot](https://github.com/rubymonolith/fixturebot).
## FixtureBot
Here's what a FixtureBot looks like for a blog application:
```ruby
FixtureBot.define do
user.email { |fixture| "#{fixture.key}@blog.test" }
user :brad do
name "Brad"
email "brad@blog.test"
end
user :alice do
name "Alice"
# "alice@blog.test" is auotmatically generated
end
user :charlie do
name "Charlie"
FEB 2026
FixtureBot gives you the speed of fixtures with the syntax of factories
After building pricing objects in [last week's post](./superfeature-price), the next challenge was figuring out how to organize features across different pricing tiers. Every SaaS product I've worked on eventually becomes a tangled mess of `if current_user.plan == "pro"` checks scattered throughout the codebase.
I decided to solve this problem in the [Superfeature gem](https://github.com/rubymonolith/superfeature) by creating a `Plan` class that makes defining SaaS pricing tiers declarative and maintainable.
## Defining a plan
Plans are Ruby classes that inherit from `Superfeature::Plan`:
```ruby
class Plans::Website::Free < Superfeature::Plan
def name = "Free"
def tagline = "Get started for free"
def price = Price(0)
end
```
This gives you a clean interface for plan metadata. The `Price()` method is from [Superfeature::Price](./superfeature-price), so pricing and plans work together naturally.
## Plan inheritance
Here's where things get interesting. Plans can inherit from each other:
```ruby
class Plans::Website::Creator < Plans::Website::Free
def name = "Creator"
def tagline = "For content creators"
def price = Price(19)
end
class Plans::Website::Business < Plans::Website::Creator
def name = "Business"
def tagline = "For growing businesses"
def price = Price(99)
end
```
This inheritance isn't just for code reuseโit represents the natural hierarchy of SaaS tiers where higher tiers include everything from lower tiers. I'm still learning if it makes sense to inherit from the previous plan or from a base plan.
## Limits
FEB 2026
A Ruby DSL for defining SaaS pricing plans and features
I've worked with enough SaaS products to know that prices are a bit of a pain in the butt. At first, they seem like numbers, but then you have your first Black Friday sale and begin to understand how tedious it is to display discounts throughout a website.
I decided to end this battle once and for all while working on my latest project, [OpenGraph+](https://og.plus), by directing Claude Code to build a `Price` object that makes working with prices and discounts straightforward in the [Superfeature gem](https://github.com/rubymonolith/superfeature).
## Setting prices
Creating prices is straightforward:
```ruby
price = Superfeature::Price.new(49.99)
price.amount # => BigDecimal("49.99")
price.to_f # => 49.99
price.to_i # => 49
```
There's also a handy `Price()` method for when you want something more concise:
```ruby
price = Price(29)
price.to_formatted_s # => "29.00"
price.to_formatted_s(decimals: 0) # => "29"
```
## Discounts
This is where things get interesting. You can apply discounts in several ways:
```ruby
price = Price(100)
price.discount_fixed(20).amount # => 80.0
price.discount_percent(25).amount # => 75.0
price.discount_to(79).amount # => 79.0
```
Superfeature also parses discount strings naturally. "20%" applies a percentage reduction, "$15" subtracts dollars, and a plain number defaults to dollars:
```ruby
price = Price(100)
price.apply_discount("20%").amount # => 80.0
price.apply_discount("$15").amount # => 85.0
JAN 2026
A Ruby object for working with prices and discounts

Phlex on Rails
One of Superform's strengths is giving developers complete control over how form errors are displayed. Rather than being locked into a specific CSS framework's conventions, you can customize error styling to work with DaisyUI, Tailwind, Bootstrap, or any other framework you prefer.
## Custom Field class with error helpers
The key is extending `Superform::Rails::Form::Field` with your own error helpers. Here's an example that works with DaisyUI:
```ruby
# ./app/components/form.rb
class Components::Form < Superform::Rails::Form
class Field < Superform::Rails::Form::Field
# A single sentence for inclusion below an input.
def error_message
"#{key.to_s.titleize} #{errors.to_sentence}" if errors.any?
end
def input(*, **attributes)
super(*, **mix(
{
class: [
"input input-outline w-full",
("input-error" if field.invalid?)
]
},
attributes)
)
end
def textarea(*, **attributes)
super(*, **mix(
{
class: [
"textarea textarea-outline w-full",
("input-error" if field.invalid?)
]
},
attributes)
)
end
end
end
JAN 2026
Display validation errors with any CSS framework
[Last week I walked through Fizzy](./fizzy), the latest product released by 37 Signals, and completely missed the [`fizzy-saas` repo](https://github.com/basecamp/fizzy-saas). I cracked open that repo and discovered references to the mysterious "Queen Bee" gem.
Watch the video and let's dive in.
## Fizzy SaaS
The first thing I noticed about the Fizzy SaaS repo is the gemspec that has all of these dependencies:
```ruby
spec.add_dependency "rails", ">= 8.1.0.beta1"
spec.add_dependency "queenbee"
spec.add_dependency "rails_structured_logging"
spec.add_dependency "sentry-ruby"
spec.add_dependency "sentry-rails"
spec.add_dependency "yabeda"
spec.add_dependency "yabeda-actioncable"
spec.add_dependency "yabeda-activejob"
spec.add_dependency "yabeda-gc"
spec.add_dependency "yabeda-http_requests"
spec.add_dependency "yabeda-prometheus-mmap"
spec.add_dependency "yabeda-puma-plugin"
spec.add_dependency "yabeda-rails", ">= 0.10"
spec.add_dependency "prometheus-client-mmap", "~> 1.4.0"
spec.add_dependency "console1984"
spec.add_dependency "audits1984"
```
They're all monitoring and auditing gems, which makes sense when deploying a Rails application to production. There wasn't much else to the repo, just a signup flow and something that looks like a signup page.
What I'm still not clear about is why the main `fizzy` repo has a [Gemfile.saas with similar dependencies](https://github.com/basecamp/fizzy/blob/main/Gemfile.saas). Here's why the file is interesting:
```ruby
gem "activeresource", require: "active_resource"
gem "queenbee", bc: "queenbee-plugin" # ๐
gem "fizzy-saas", bc: "fizzy-saas"
```
See the `queenbee` and `activeresource` gems?
## "Queen Bee"
DEC 2025
I walk through the source code of the latest Rails application open-sourced by 37 Signals
37 Signals released [Fizzy](https://www.fizzy.do) today, which is their take on a Kanban board. What surprised everybody was that they also released the source code for it. There's a lot to look at, which I cover in this video.
Let's look at a few things I call out in the video that I need to elaborate on.
## Nested resources
I particularly enjoyed the approach to nesting resources with controller modules. Here's an example from their routes.rb file. We'll be focusing on the `not_now` resource.
```ruby
namespace :columns do
resources :cards do
scope module: :cards do
namespace :drops do
resource :not_now # Maps to Columns::Cards::Drops::NotNowsController
resource :stream
resource :closure
resource :column
end
end
end
end
```
Here's the controller it maps to.
```ruby
class Columns::Cards::Drops::NotNowsController < ApplicationController
include CardScoped
def create
@card.postpone
end
end
```
Simple! I immediately know a `POST /columns/cards/:card_id/drops/not_now` will do something.
### Restomatic
In my projects, I take the same approach, but I've cleaned up the routes a bit with a gem called [Restomatic](https://github.com/rubymonolith/restomatic). Here's how the gem would clean up the Fizzy routes file.
DEC 2025
I walk through the source code of the latest Rails application open-sourced by 37 Signals

Phlex on Rails
NOV 2025
Save money on Ruby books, courses, licenses, & software
NOV 2025
Pre-sold $13,000 of Phlex on Rails video course
OCT 2025
Officially launching a video course for building component-based UIs in Rails

Phlex on Rails
In markdown, an image tag look something like this when you want to drop it into a document.
```markdown

```
For my projects, I don't think of these tags *strictly* as images, but rather blocks of content with an image representation. In practice, that means I want to embed YouTube videos in my content pages like this:
```markdown

```
Out of the box this wouldn't work, but fortunately since I use the [Markdown Rails](https://github.com/sitepress/markdown-rails) gem, I can intercept image tags and process them as blocks of content. Here's what my Markdown parser looks like in my application.
```ruby
class ApplicationMarkdown < MarkdownRails::Renderer::Rails
# Process markdown image blocks
def image(link, title, alt)
url = URI(link)
case url.host
when "youtu.be"
youtube_tag url, alt
else
super
end
end
# ...
private
# This is provided as an example; there's many more YouTube URLs that this wouldn't catch.
def youtube_tag(url, alt)
<<~HTML
<iframe
class="aspect-video w-full rounded"
src="https://www.youtube-nocookie.com/embed#{url.path}"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
OCT 2025
Abuse markdown image tags to easily embed YouTube videos and embedded resources in documents
OCT 2025
An off-the-grid dev setup & recording studio that fits in a backpack
OCT 2025
Videos have been shot & registration is live

Phlex on Rails
Last week [I released Supermail](https://github.com/rubymonolith/supermail), a gem that makes working with emails in Rails much more intuitive by using a much simpler abstraction. Something I love about simpler abstractions is they often lead to pleasant discoveries, in my case creating mailto links with an instance of an email. This week I had the revelation I could use those email objects to generate mailto links.

## Generating support emails
The Beautiful Ruby checkout flow has a "cancel" state where I'm assuming the person trying to buy the [Phlex on Rails video course](/phlex) ran into problems checking out. Instead of dumping them off into a dead-end, I give them an option to email me to get help. Here's what that looks like in Supermail.
```ruby
# ./app/emails/support_email.rb
class SupportEmail < ApplicationEmail
def initialize(checkout_session:)
@checkout_session = checkout_session
end
def to = from
def subject = "Payment issue on Beautiful Ruby"
def body = <<~BODY
Hi,
I had trouble completing my payment.
Here's the checkout session ID: #{@checkout_session.id}
BODY
end
# Then from your views...
<%= link_to SupportEmail.new(checkout_session: @checkout_session).mailto, "Get help with your payment" %>
```
The `@checkout_session` is an instance of a `Stripe::Checkout::Session` object from the [Stripe Gem](https://github.com/stripe/stripe-ruby).
## Generating mailto links
Here's where the power of this simple abstraction lands: I can use it to generate mailto links that launch the users' email clients with an instance of an email.
```erb
OCT 2025
Start more conversations with your users over email
[Supermail](https://github.com/rubymonolith/supermail) `0.2.x` has been released! It's a better way to work with emails in Rails.
Here's what a Supermail email looks like:
```ruby
# $ rails generate supermail:email User::Welcome
# ./app/emails/user/welcome.rb
class User::WelcomeEmail < ApplicationEmail
def initialize(person:)
@person = person
end
def to = @person.email
def subject = "Welcome to Beautiful Ruby"
def body
super do
<<~_
Hi #{@person.name},
You're going to learn a ton at https://beautifulruby.com.
_
end
end
end
```
When you want to use it to send an email, you just initialize the thing like you would any Ruby class and send it.
```ruby
# You might call this from a controller
User::WelcomeEmail.new(person: @person).deliver_now
```
## ActionMailer is frustrating
Action Mailer is one of the most frustrating parts of Rails. The problem I have with it is I can never remember how to initialize it and send an email.
```ruby
# Is it this?
OCT 2025
An easier way to work with emails in Rails.
SEP 2025
Hotwire, Stimulus, Turbo, and Rails World

Phlex on Rails
[Superform](https://github.com/rubymonolith/superform) `0.6.x` has been released, and it's packed with a bunch of quality-of-life improvements.
### Field Kits
The old way of rendering Superforms in Ruby 3.4 looked like this:
```ruby
# The old way of rendering a Superform
render MyForm.new @user do
render it.Field(:name).input
render it.Field(:email).input(type: :email)
end
```
See all those `render` method calls? Nobody wants to type those. With Phlex & Field Kits, you can implicitly render fields with the `#Field` method.
```ruby
# The new way of rendering a Superform
MyForm @user do
it.Field(:name).input
it.Field(:email).input(type: :email)
end
```
Who doesn't like more concise code?
### Better ERB rendering
You might think I built Field Kits to make rendering in Phlex more conciseโwhich is trueโbut I also built it to make rendering in ERB more concise as well. Here's what that looks like.
```erb
<%= render MyForm.new @user do
it.Field(:name).input
it.Field(:email).input(type: :email)
end %>
```
Notice what's missing? Developers don't have to put `<% %>` tags around every single line. This makes Superform the most concise form builder in Rails.
### Phlex 2.0 compatibility
AUG 2025
Field Kits, Concise ERB rendering, Strong Parameters, Phlex 2.0, and HTML5 inputs
It's fun cracking open source code in libraries to see how certain abstractions are made. In the case of [Phlex](https://phlex.fun), it has a file that lists out *all* legal HTML elements in the [standard_elements.rb](https://github.com/yippee-fun/phlex/blob/75fbae36754c63493018afa4bea500e9debaf520/lib/phlex/html/standard_elements.rb) file.
```ruby
# Outputs a `<table>` tag.
#
# [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/table)
# [Spec](https://html.spec.whatwg.org/#the-table-element)
register_element def table(**attributes, &content) = nil
# Outputs a `<tbody>` tag.
#
# [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/tbody)
# [Spec](https://html.spec.whatwg.org/#the-tbody-element)
register_element def tbody(**attributes, &content) = nil
# Outputs a `<td>` tag.
#
# [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/td)
# [Spec](https://html.spec.whatwg.org/#the-td-element)
register_element def td(**attributes, &content) = nil
# Outputs a `<template>` tag.
#
# [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/template)
# [Spec](https://html.spec.whatwg.org/#the-template-element)
register_element def template(**attributes, &content) = nil
# Outputs a `<textarea>` tag.
#
# [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/textarea)
# [Spec](https://html.spec.whatwg.org/#the-textarea-element)
register_element def textarea(**attributes, &content) = nil
```
This is a very concrete approach to abstracting HTML, and its one of the reasons I like Phlex. It's a very well built bridge that connects HTML and Ruby in a way that makes it easy and enjoyable to work with both.
## Language server integraton
It goes beyond that though. If your editor supports language server protocol (LSP), they show a link to the [MDN documentation](https://developer.mozilla.org/en-US/docs) for each element and a [WHATWG](https://html.spec.whatwg.org/) link to the spec.
AUG 2025
How elements are defined in the Phlex library
AUG 2025
Superform updates & videos released, Github talk, and mechanical keyboards

Phlex on Rails
AUG 2025
Existing Rails Apps unit complete and gem updates
While putting together the [Phlex on Rails course](/phlex), I wanted a way to render HTML components from `rails console` so I could see the resulting HTML outside of a browser and have less distrctions for the viewers.
That means I can do this from `rails console`:
```ruby
server(dev)> render Components::PageTitleComponent.new(title: "Hi")
<!-- Before Components::PageTitleComponent -->
<div class="flex flex-col">
<h1 class="font-semibold text-4xl flex flex-row items-center">Hi</h1>
</div>
```
## The code
Create the `./config/initializers/console.rb` file and drop in the following code:
```ruby
# config/initializers/console.rb
Rails.application.configure do
# This block only runs when you're in the Rails console (rails c)
console do
# TOPLEVEL_BINDING.eval('self') gets the main object in the console
# We're extending it with a new module to add custom methods
TOPLEVEL_BINDING.eval('self').extend(
Module.new do
# Add your helper methods in here.
def render(*args, layout: false, **kwargs)
Rails.logger.silence do
html = ApplicationController.renderer.render(*args, layout: layout, **kwargs)
Nokogiri::HTML.fragment(html).to_xhtml(indent: 2)
end
end
end
)
end
end
```
There's no great "official way" to add helpers to Rails console, so a lot of it looks hack-ish up until everything inside the `Module.new` block.
AUG 2025
A handy way to render & debug basic components in isolation
AUG 2025
Finished Existing Rails app unit and published course source code

Phlex on Rails
JUL 2025
One unit complete, six more to go, and the end of summer
I'm a critic of service objects, not because they're bad, but because they're commonly misunderstood by well-meaning developers as a way to encapsulate business logic. The problem is how it's implemented. Often a developer takes a perfectly good method.
```ruby
# Perfectly good method
def hello(name)
puts "Hello, #{name}!"
end
```
Then hides it behind a class, moves the parameters to an initializer, then hides the method invocation behind a `#call` method.
```ruby
# The greeter service object
class Greeter < ServiceObject
def initialize(name)
@name = name
end
def call
puts "Hello, #{@name}!"
end
end
```
Now instead of 3 lines of code, we have 9. ๐ฅณ
## A real service object
Remember how I said service objects are misunderstood? That's because they're actually good when properly used. For the [Phlex on Rails video course](/phlex) I'm building out infrastructure for [HLS video streaming](https://github.com/beautifulruby/hls) on [Tigris](https://www.tigrisdata.com). Tigris is an S3-compatible object storage service that replicates data to servers close to my customers on [Fly.io](https://fly.io) boxes.
Here's how I set up a service object to interact with my Tigris S3 service that will store my video files.
```ruby
Tigris = Aws::S3::Resource.new(
access_key_id: ENV["VIDEO_AWS_ACCESS_KEY_ID"],
secret_access_key: ENV["VIDEO_AWS_SECRET_ACCESS_KEY"],
endpoint: ENV["VIDEO_S3_ENDPOINT_URL"]
)
Bucket = Tigris.bucket(ENV["VIDEO_S3_BUCKET_NAME"])
JUL 2025
Not your average method masquerading as a class behind a #call method
JUL 2025
Video quality, a Rails 8 upgrade, and HTTP Live Streaming

Phlex on Rails
When building out components in Phlex, it's very common to need slight variations on a single component to render different classes. For example, a button component might start like this:
```ruby
class Components::Button < Components::Base
def view_template(&)
div(class: "btn", &)
end
end
```
Then you call it from your views using Phlex Kit like this:
```ruby
Button { "Watch the intro video" }
```
Life is good until your user asks you for a primary & secondary button.
## Class variants without gems
In Ruby, there's a good chance a gem has already solved the problem. That's true for this problem too with gems like [Phlex Variants](https://github.com/stephannv/phlex-variants) and the [Class Variant](https://github.com/avo-hq/class_variants) gems, but you might not need those.
### Subclassing
Since Phlex is just Ruby, we could subclass our button with different classes.
```ruby
class Components::Button < Components::Base
def view_template(&)
div(class: ["btn", classes], &)
end
def classes
nil
end
end
class PrimaryButton < Button
def classes
"btn-primary"
JUL 2025
Create 10's of components with slight variations in their class names
Did you know that [Phlex Kits](https://www.phlex.fun/components/kits.html) work with ERB templates? Assuming you have the latest Phlex Rails gem installed, add this to your controller file to expose components to your ERB view context.
```ruby
class ApplicationController < ActionController::Base
helper Components
# ...
end
```
This registers the components from the `Components` namespace as methods in your view helpers so you can call them directly from your templates without all the "rendering ceremony", like this:
```erb
<%= FloatingLayout do |layout| %>
<% layout.menu do %>
<nav class="breadcrumbs text-sm">
<ul>
<li><%= layout.logo %></li>
</ul>
</nav>
<% end %>
<% # ... %>
<% end %>
```
## The old way
Prior to Phlex Kits, components would be called via the `render` method in the `Components` namespace.
For example, this layout component I'm working on for this website:
```ruby
class Components::FloatingLayout < Components::Base
include Phlex::Rails::Helpers::AssetPath
def view_template
yield
end
def menu_container(*, class: nil, **)
JUL 2025
Write less code to render Phlex components from Erb templates
The [Rails form helpers](https://guides.rubyonrails.org/form_helpers.html) make working with grouped select tags extremely cumbersome because it doesn't expose the `<option>` and `<optgroup>` tags in a way that gives developers the control they need over the HTML. Consider the following example:
```ruby
grouped_options = [
[['United States','US'], 'Canada'],
['Denmark','Germany','France']
]
grouped_options_for_select(grouped_options, nil, divider: '---------')
```
It emits the following HTML.
```html
<optgroup label="---------">
<option value="US">United States</option>
<option value="Canada">Canada</option>
</optgroup>
<optgroup label="---------">
<option value="Denmark">Denmark</option>
<option value="Germany">Germany</option>
<option value="France">France</option>
</optgroup>
```
Things get even more convoluted when working with ActiveRecord and groups
```ruby
class Continent < ActiveRecord::Base
has_many :countries
# attribs: id, name
end
class Country < ActiveRecord::Base
belongs_to :continent
# attribs: id, name, continent_id
end
```
Rails requires calling a completely different method to generate grouped select tags.
JUL 2025
Get precise control over select and option tags with Superform

Phlex on Rails
I've always found [Rails form helpers](https://guides.rubyonrails.org/form_helpers.html) select tags cumbersome to work with, mainly because the abstraction buries control over how the `<option>` tags are rendered. Consider this example in the Rails docs.
```ruby
select(:post, :person_id, Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: true })
```
It emits the following HTML.
```html
<select name="post[person_id]" id="post_person_id">
<option value="">None</option>
<option value="1">David</option>
<option value="2" selected="selected">Eileen</option>
<option value="3">Rafael</option>
</select>
```
What if you want the blank value to appear at the end of the list? How would you add an attribute to an `<option>` tag? You're in for a world of pain as you have to reach deep into the internals of the Rails form helpers.
## Phlex and Superform
I coded Superform after getting angry at how little control I had over Rails helpers and how gross the syntax looks. Here's the equivalent code in Superform, which is a Rails form helper I built from scratch on top of Phlex.
### Blank options
```ruby
render field(:contact).select nil, Person.select(:name, :id)
```
Want that blank to be at the end? Just move the `nil` argument to the end.
```ruby
render field(:contact).select Person.select(:name, :id), nil
```
Want the blank to say "None"? Here's how Rails does it.
```ruby
select(:post, :person_id, Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: "None" })
```
JUL 2025
More control over select and option tags with Superform
JUL 2025
Getting started with videos, workflow, and finalizing scripts
JUL 2025
Rough draft complete, demo apps, and videos are coming soon

Phlex on Rails
One thing I really like about Phlex is how easy it is to group concerns into one place. Consider this Turbo Pagemorphs example. The view has a section in it with a bunch of `turbo_` methods.
```ruby
class View::Post < View::Base
# Includes the turbo_stream_from method
include Phlex::Rails::Helpers::TurboStreamFrom
def initialize(post:)
@post = post
end
# All of our Turbo Stream behavior is configured from one place.
def turbo_refresh_method = "replace"
def turbo_refresh_scroll = "preserve"
def turbo_streams
turbo_stream_from @post.comments
end
def view_template
h1 { @post.title }
main(class: "prose"){
safe render @post.body, type: :md
}
div(id: "comments") do
comments.each do |comment|
div(class: "comment") do
p { comment.body }
end
end
end
end
end
```
The base layout then implements those methods in the `head` and `body` tags.
```ruby
class View::Base < View::Component
# Default to `nil` so we're not morphing all pages
def turbo_refresh_method = nil
JUL 2025
Turbo Layouts in Phlex
When I evaluate code libraries, I like to open it up and get a sense of what it does at its very core. At the heart of Phlex is a string that Phlex appends HTML to for rendering in the browser. That string is named `buffer`, which is defaulted to `+""`.
```ruby
# frozen_string_literal: true
class Phlex::SGML
# ...
def call(buffer = +"", context: {}, fragments: nil, &)
state = Phlex::SGML::State.new(
user_context: context,
output_buffer: buffer,
fragments: fragments&.to_set,
)
internal_call(parent: nil, state:, &)
state.output_buffer << state.buffer
end
# ...
end
```
## What is `+""`?
I never bothered to understand what `+""` meant in Ruby until I looked under the hood of Phlex. You know that annoying `# frozen_string_literal: true` you see at the top of Ruby files? That tells Ruby that you can't modify the string in place with operators like `"fizz".upcase!` or `"fizz".concat("buzz")`.
JUL 2025
Understanding how Phlex handles string concatenation
JUN 2025
More content updates, a toy project, and an upcoming holiday week

Phlex on Rails
When I build out Rails applications these days, I think of ActiveRecord classes purely as something that validates data and controllers as a way to interface HTTP into these data objects.
More often than not, I have to create controllers that do slightly more complicated things than a CRUD interface into the database, so where does the business logic live? I keep it in the `./app/models` directory and use namespaces to help organize it where it makes sense.
Let's have a look at how I deal with license validation for [Terminalwire](https://terminalwire.com).
## Data validation models
Here's what the `License` model looks like at [Terminalwire](https://terminalwire.com). You'll notice it's mostly data validation and dealing with making data easier to work with from Ruby.
```ruby
class License < ApplicationRecord
belongs_to :user, required: true
belongs_to :account, required: true
has_one :distribution
has_many :verifications,
class_name: "License::Verification"
# Only allow https or wss for production deployments.
URL_SCHEMES = %w(https wss)
URL_FORMAT = URI::DEFAULT_PARSER.make_regexp(URL_SCHEMES)
validates :url,
presence: true,
format: {
with: URL_FORMAT,
message: "must be a valid #{URL_SCHEMES.to_sentence(words_connector: " or ", two_words_connector: " or ")} URL"
}
normalizes :url, with: ->(value) { value.chomp }
STATUSES = %w[
pending
payment_required
active
expired
inactive
]
validates :status, presence: true, inclusion: { in: STATUSES }
def status
JUN 2025
Where I put business logic in a Rails application
I've been playing around with an idea I'm calling "PhlexML" in Rails and Sitepress where I can build content pages in Phlex that look like this:
```ruby
PageView(
title: "This is a page"
){
h1 { @title }
erb <<~ERB
<p>
Why would somebody want to put
ERB in their Phlex view? Nobody knows
</p>
ERB
div(class: "prose prose-md"){
markdown <<~MD
* This
* could
* be
* cool
MD
}
}
```
This is perfectly valid Ruby code inspired by Phlex kits. It might look like a lot, so let's take a closer look at how this view works.
## Frontmatter as the initializer
Frontmatter is a way to add metadata to a page made popular by Jekyll. When you create a markdown file, it might look like this:
```md
---
title: This is a page
layout: page
---
* This
* could
* be
* cool
JUN 2025
Build content pages with Phlex
JUN 2025
Packages for companies, scholarships, and a few more sections are drafted

Phlex on Rails
When running `rails new` in a Rails 8 app, you'll notice this innocuous and well-intentioned `allow_browser` line of code in the `ApplicationController`.
```ruby
class ApplicationController < ActionController::Base
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
allow_browser versions: :modern
end
```
One of the first things I do is delete it.
```ruby
class ApplicationController < ActionController::Base
# ๐ฅ Delete "modern" browser support.
end
```
Why? For a lot of reasons.
### Not everybody runs a modern browser
I view browser restrictions as "blaming the user", which I think is an adversarial approach to building products for people. Not everybody has a "modern browser". I'm not talking IE6 of antiquity, I'm talking people who might be stuck on an Intel MacBook that can't upgrade their operating system, but have a perfectly good browser that is still highly capable.
`webp` is a great image format, but I'm perfectly happy serving up `png` and `jpg` images if it means more people can use my web application. Web Push is cool, but most web applications don't need it. I haven't run into problems with importmaps not working and since I use TailwindCSS, nested CSS isn't a concern.
### Breaks OpenGraph integrations
You know those social links that appear on links when you send them over text message or share on a social network? Those won't work because the user agents of social media platforms don't check out as a "modern browser". Good luck tracking that down to the allowance of modern browser.
## Sometimes restrictions are OK
I'm not categorically against browser restrictions, I just think it's a bad default, especially for a framework as widely deployed as Rails. It's important to consider the needs of all users, not just those with the latest and greatest browsers.
JUN 2025
Restricting websites to modern browsers can cause headaches
JUN 2025
Drafting the beginning, ending, a few things in the middle and working on plumbing
Part of putting together a paid courses is giving people an idea of what content they're be getting, but not so much where there's no point in them signing up to support your efforts.
Here's what the paywall looks like so far on my Phlex for Rails course.
[](/phlex/all-in/superview)
Let's take a closer look.
### Markdown redactor
At the core of the paywall is a parser that replaces characters with a unicode placaholder. It parses the output of the markdown HTML and splits it into a preview, which people can see, and redacted content, which has the unicode โ charactors.
```ruby
# ./lib/paywall.rb
class Paywall
REDACTED_CHARACTER = "โ"
attr_reader :preview, :redacted
def initialize(html)
@dom = Nokogiri::HTML.fragment(html)
@preview = slice_nodeset(@dom, 0...5).to_html.html_safe
@redacted = redact(slice_nodeset(@dom, 5..-1)).to_html.html_safe
end
private
def redact(dom)
dom.css("p").each do |element|
element.content = redact_text element.content
end
dom.css(".locked").each do |element|
element.content = redact_text element.content
end
dom
end
def slice_nodeset(dom, range)
JUN 2025
A stack of components and parsers to protect locked content

Phlex on Rails
Apple dropped the [SwiftUI WebKit](https://developer.apple.com/documentation/webkit/webkit-for-swiftui) API at WWDC 2025, which might mean my dream of Turbo SwiftUI could come true.
A few years ago, I made the source code for the [Legible News iOS app available on Github](https://github.com/legiblenews/ios-app/blob/main/Legible%20News/ContentView.swift#L29-L53) as the world's first Turbo SwiftUI app.
The snippet I like the most is the [TabView](https://developer.apple.com/documentation/swiftui/tabview):
```swift
struct ContentView: View {
var body: some View {
TabView {
TurboNavigationStack(initialUrl: URL(string: "https://legiblenews.com/")!)
.tabItem {
Image(systemName: "newspaper")
Text("Today")
}
TurboNavigationStack(initialUrl: URL(string: "https://legiblenews.com/week")!)
.tabItem {
Image(systemName: "calendar")
Text("Week")
}
TurboNavigationStack(initialUrl: URL(string: "https://legiblenews.com/time-machine")!)
.tabItem {
Image(systemName: "clock")
Text("Time Machine")
}
}
}
}
```
Here's what it looks like in the app.
<div class="mockup-phone max-w-fit my-8 mx-auto">
<div class="mockup-phone-display">
<img alt="wallpaper" src="https://bradgessler.s3.amazonaws.com/OPyWbYWv8QOK83jKn27D.png"/>
</div>
</div>
JUN 2025
A dream that might be coming true
If you like highly abstracted view code, you'll appreciate how concise you can express HTML with Phlex. This snippet renders an HTML table of students in an app I built to manage my local school's science fair.
```ruby
class Index < View
# This is rendered for an `<h1>` on this views layout, which
# is not related to the table.
def title = "Students"
def view_template
# Calls the `Table` method with `Student.all` set to `@students`.
# It ultimately renders a `<table>` tag.
Table @students do
# `it` is the table. We call `column` with the argument `"Student"`
# which is set in the `table > thead > th` element.
it.column("Student") { show(it, &:name) }
# The `show` method is a link helper that links to the `Teacher` model,
# which is the same as `url_for(teacher)` in Rails.
it.column("Teacher") { show(it.teacher, &:last_name) }
# The second argument `&:name` calls it.parent.name.
it.column("Parent") { show(it.parent, &:name) }
it.column("Project") { show(it.project, &:title) }
it.column("Registered at") { local_time it.created_at }
# The contents of the link can be changed by returning a value from a block.
it.column("Interview") do |student|
show(student.interview) { interview_name(student.interview) }
end
end
end
end
```
Here's the HTML it renders:
```html
<table class="table">
<thead>
<tr>
<th>Student</th>
<th>Teacher</th>
<th>Parent</th>
JUN 2025
HTML table and hyperlink abstractions built with Phlex, Superview, and Superlink
One of my favorite things about using the [Markdown Rails](https://github.com/sitepress/markdown-rails), [Sitepress Rails](https://github.com/sitepress/sitepress), and [URI Builder](https://github.com/rubymonolith/uri-builder) gems is how easy it is to integrate referral codes into markdown content so I don't have to think about it when I'm writing the article.
All I have to do is modify the `ApplicationMarkdown` class to intercept links the Markdown parses.
```ruby
# ./app/markdown/application_markdown.rb
class ApplicationMarkdown < MarkdownRails::Renderer::Rails
# Code
AMAZON_STORE_ID = "rocketship083-20"
# This is called when a link is encountered in the markdown content
def link(link, title, content)
# The URI is wrapped in a builder, which makes modifications easier.
url = URI.build(link)
case url.uri.host
when "www.amazon.com"
url.query tag: AMAZON_STORE_ID
end
# HTML is rendered and emitted back into the HTML buffer.
content_tag :a, href: url.to_s, title: do
content
end
end
end
```
Then, when I'm writing an article, I can copy and paste whatever links I want into the markdown and never have to remember to include the referral code.
When I paste a link into my markdown like:
```markdown
[SmallRig aluminum tripod stand](https://www.amazon.com/SmallRig-Selection-Lightweight-Aluminum-Adjustable/dp/B09C8F36LC)
```
It automatically renders the HTML with the referral code:
```html
<a href="https://www.amazon.com/SmallRig-Selection-Lightweight-Aluminum-Adjustable/dp/B09C8F36LC?tag=rocketship083-20">
SmallRig aluminum tripod stand
</a>
JUN 2025
Automatically inject referral codes into markdown content

Phlex on Rails
Phlex Kits make writing Ruby view code look almost like Swift.
```ruby
Header {
h1 { "Welcome to Phlex!"}
}
```
Pretty cool?
JUN 2025
A collection of Phlex components and utilities
The `Enumerator` class is a handy way to add multiple Enumerables to a class.
```ruby
class Person
def initialize
@addresses = []
@emails = []
end
# Here it is...
def names
Enumerator.new do
@array.each do |person|
it.yield person.name
end
end
end
def emails
Enumerator.new do
@array.each do |person|
it.yield person.email
end
end
end
end
Person.new.names.each do |name|
puts name
end
Person.new.emails.each do |email|
puts email
end
```
Enumerators are a powerful tool for iterating over collections of elements. They provide a simple and efficient way to iterate over a collection of elements, without having to worry about the underlying implementation details.
JUN 2025
A quick way to create multiple Enumerables