I embarked on an experiment with a Rails application using modern alternative tools and libraries. In this post, I'll walk you through the steps I took to set up a Rails template repository with the following technologies:
Configuration with Bun JS runtime for efficient JavaScript management.
Integration of TailwindCSS for sleek UI design.
Utilization of Phlex library to build views entirely in Ruby, eliminating the need for mixed template languages and replacing partials and view helpers with components.
Implementation of HTMX actions for dynamic content swapping without full page refresh.
Setup Steps:
Installed bun js runtime (I used mise, btw)
Added necessary gems and generated some configurations:
rails new sample-02_bun_tw_phlex_htmx -j bun -c tailwind -d sqlite3 --skip-hotwire --skip-jbuilder --skip-bootsnap | |
bin-/bundle add rails-htmx | |
bin/bundle add phlex-rails | |
bin/rails generate phlex:install | |
bin/bundle add tailwindcss-rails | |
bin/rails tailwindcss:install | |
bin/rails g phlex:controller Articles index |
Edited Procfile to remove bun css watch line and use tailwindcss:watch task.
Edited application_layout.rb to remove javascript_importmap_tags line.
This is how it looks:
The Rails bin/dev command starts the three processes defined in the Procfile file. The Rails server, the Bun js runtime, and the TailwindCSS watcher.
Some code highlights:
class ApplicationLayout < ApplicationView | |
include Phlex::Rails::Layout | |
def template(&block) | |
doctype | |
html do | |
head do | |
title { "Rails demo" } | |
meta name: "viewport", content: "width=device-width,initial-scale=1" | |
csp_meta_tag | |
csrf_meta_tags | |
javascript_include_tag "application" | |
stylesheet_link_tag "application" | |
stylesheet_link_tag "tailwind", "inter-font" | |
end | |
body(class: "bg-slate-800", "hx-headers": "{\"X-CSRF-Token\": \"#{helpers.form_authenticity_token}\"}") do | |
header(class: "bg-white px-6 shadow") { | |
div(class: "h-16 flex items-center justify-between") { | |
render HeaderButtonComponent.new(:menu) | |
h1(class: "text-xl font-bold") { "Rails demo" } | |
render HeaderButtonComponent.new(:user) | |
} | |
} | |
main(class: "m-4 p-4 bg-slate-400 rounded", &block) | |
end | |
footer(class: "bg-white px-4 shadow h-12 flex items-center") { | |
p(class: "m-2 text-sm font-normal") { "© Cristian Molina - #{Time.zone.now.year}" } | |
} | |
end | |
end | |
end |
The main application layout, in Ruby, has some header and footer definitions and a call to a Ruby block in the tag.
class ArticlesController < ApplicationController | |
layout -> { ApplicationLayout } | |
def index | |
render Articles::IndexView.new | |
end | |
def create | |
render ArticleComponent.new, layout: false | |
end | |
end |
The controller has an action for listing the Articles and the method to handle the POST to add a new Article.
class Articles::IndexView < ApplicationView | |
HEAD_STYLE = "p-2 font-mono font-bold font-lg font-bold" | |
BUTTON_STYLE = "font-mono rounded border-2 border-black bg-indigo-200 p-1 text-sm transition-colors focus-ring-2 focus:ring-indigo-800 hover:bg-indigo-100" | |
def template | |
div(class: "h-16 flex items-center justify-between") { | |
h1(class: HEAD_STYLE) { "Articles index" } | |
button("hx-post": articles_path, "hx-target": "#article-list", "hx-swap": "afterbegin", | |
class: BUTTON_STYLE) { | |
plain "Add new article" | |
} | |
} | |
section(id: "article-list") { | |
articles.each {|el| render el } | |
} | |
end | |
private | |
def articles | |
3.times.map do | |
ArticleComponent.new | |
end | |
end | |
end |
The Index view has a title, an Add button, and the list of Articles. In a real app these Articles would probably be passed on initialization from a DB query outside this class.
require "faker" | |
class ArticleComponent < ApplicationComponent | |
def template | |
article(class: "mb-2 bg-blue-600 rounded border-solid border-2 border-black item-article") { | |
h3(class: "text-left text-slate-200 pl-2 font-semibold") { | |
i { Faker::Lorem.sentence } | |
} | |
p(class: "bg-slate-200 p-4") { Faker::Lorem.paragraph } | |
} | |
end | |
end |
The Article component just generates some random data. In a real app it would probably be populated from an Active Record object on initialization.
The full source repository is available here.
By following these steps, I was able to leverage a modern UI frontend for a Rails application in a hopefully more maintainable way.
I plan to continue experimenting with these tools on more advanced topics like forms, websockets & SSE updates, filters, pagination, using JS interactivity, and more complex components.
Happy coding!
Top comments (2)
cool stack
Nice