 
                    In this guide we’ll create a real-time chat application using Rails 8.0.1 and Ruby 3.4.2.
Project Setup
source "https://rubygems.org"
ruby "3.4.2"
gem "rails", "~> 8.0.1"
gem "turbo-rails"
Prerequisites
To get started, ensure you have:
- Proper Action Cable configuration in cable.yml
- Working user authentication system
- Turbo Rails properly installed and configured
This implementation provides a robust foundation for a real-time chat application, leveraging Rails 8’s modern features for seamless real-time updates with minimal JavaScript.
Key Technical Aspects
Turbo Streams and Broadcasting
- Turbo Streams: Handles real-time updates through WebSocket connections
- Action Cable: Powers the WebSocket functionality (built into Rails)
- Scoped Broadcasting: Messages only broadcast to specific room subscribers
- Partial Rendering: Keeps code DRY and maintains consistent UI updates
Let’s break down the key broadcasting mechanisms:
Room Broadcasting:
broadcasts_to ->(room) { room }
This establishes the room as a broadcast target, allowing Turbo to track changes to the room itself.
Message Broadcasting:
after_create_commit -> { broadcast_append_to room }
This ensures new messages are automatically broadcast to all room subscribers.
JavaScript Integration
- Stimulus: Manages form behavior and DOM interactions
- Minimal JavaScript: Most real-time functionality handled by Turbo
- Automatic DOM Updates: No manual DOM manipulation required
Models
Room Model
class Room < ApplicationRecord
  has_many :messages, dependent: :destroy
  validates :name, presence: true, uniqueness: true
  broadcasts_to ->(room) { room }
end
Message Model
class Message < ApplicationRecord
  belongs_to :room
  belongs_to :user
  validates :content, presence: true
  after_create_commit -> { broadcast_append_to room }
end
Controllers
Rooms Controller
class RoomsController < ApplicationController
  def index
    @rooms = Room.all
  end
  def show
    @room = Room.find(params[:id])
    @messages = @room.messages.includes(:user)
    @message = Message.new
  end
  def create
    @room = Room.create!(room_params)
    redirect_to @room
  end
  private
  def room_params
    params.require(:room).permit(:name)
  end
end
Messages Controller
class MessagesController < ApplicationController
  def create
    @message = Message.new(message_params)
    @message.user_id = session[:user_id] || create_anonymous_user.id
    @message.save!
    respond_to do |format|
      format.turbo_stream
    end
  end
  private
  def message_params
    params.require(:message).permit(:content, :room_id)
  end
  def create_anonymous_user
    random_id = SecureRandom.hex(4)
    user = User.create!(
      nickname: "Anonymous_#{random_id}",
      email: "new-email-#{random_id}@test.com",
    )
    session[:user_id] = user.id
    user
  end
end
Views
Room Index
  Chat Rooms
  
    <%= form_with(model: Room.new, class: "flex gap-2") do |f| %>
      <%= f.text_field :name, class: "rounded border px-2 py-1" %>
      <%= f.submit "Create Room", class: "bg-blue-500 text-white px-4 py-1 rounded" %>
    <% end %>
  
  <%= turbo_frame_tag "rooms" do %>
    
      <%= render @rooms %>
    
  <% end %>
Room Partial
<%= link_to room_path(room),
    class: "block p-4 border rounded hover:bg-gray-50",
    data: { turbo_frame: "_top" } do %>
  <%= room.name %>
<% end %>
Room Show (Chat Interface)
  <%= @room.name %>
  <%= turbo_stream_from @room %>
  <%= turbo_frame_tag "messages",
      class: "block mb-4 h-96 overflow-y-auto border rounded p-4",
      data: { reset_form_target: "messages" } do %>
    <%= render @messages %>
  <% end %>
  <%= turbo_frame_tag "new_message", target: "_top" do %>
    <%= form_with(model: [@room, @message],
        class: "flex gap-2",
        data: { action: "turbo:submit-end->reset-form#reset" }) do |f| %>
      <%= f.hidden_field :room_id, value: @room.id %>
      <%= f.text_field :content,
          class: "flex-1 rounded border px-2 py-1",
          data: { reset_form_target: "content" } %>
      <%= f.submit "Send", class: "bg-blue-500 text-white px-4 py-1 rounded" %>
    <% end %>
  <% end %>
Message Partial
JavaScript
Reset Form Controller
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
  static targets = ["content", "messages"]
  connect() {
    this.scrollToBottom()
  }
  reset() {
    this.contentTarget.value = ""
    this.scrollToBottom()
  }
  scrollToBottom() {
    this.messagesTarget.scrollTop = this.messagesTarget.scrollHeight
  }
}
Routes
  resources :rooms do
    resources :messages, only: [:create]
  end
  root 'rooms#index'
How It All Works Together
- 
    Room Creation and Listing - Users can view available rooms on the index page - Each room is rendered using the _room.html.erbpartial
- Entering a Chat Room
    - Clicking a room link takes users to the show page
- The show page establishes a Turbo Stream connection
- Existing messages are loaded and displayed
 
- Real-time Message Broadcasting
    - When a message is created:
        - The form submits to MessagesController#create
- Message is saved to the database
- after_create_commit triggers broadcasting
- All room subscribers receive the update
- New message appears instantly for all users
 
 
- When a message is created:
        
- Form Handling
    - The Stimulus controller manages form behavior
- After successful submission, the form is cleared
- The UI remains responsive throughout
 
Code In Action
You should see something like this in your browser:
The Chat room should be like this:
Deploying the application on Heroku
To deployment on Heroku platform is pretty straighfoward. The prerequisites are:
- Heroku CLI installed
- Git repository initialized
After covering all the prerequisites, let’s dive into the steps to the deployment:
- Create a new Heroku application
heroku create your-chat-app-name
- Add the necessary Add-ons
# Add Redis add-on
heroku addons:create heroku-redis:hobby-dev
# Add PostgreSQL add-on
heroku addons:create heroku-postgresql:mini
- Configure RAILS_MASTER_KEY ENV variable
heroku config:set RAILS_MASTER_KEY=$(cat config/master.key)
- Deploy the application
# Push to Heroku
git push heroku main
# Run database migrations
heroku run rails db:migrate
- Request a Web Dyno
heroku ps:scale web=1
- Verify the deployment
Check the logs from the deployment process and open the application:
# Open the application
heroku open
# Monitor logs
heroku logs --tail
Join the discussion on Reddit!
Ps. if you have any questions
Ask here