Turbo 8

Episode #436 by Teacher's Avatar David Kimura

Summary

With Turbo 8 comes a lot of new features and in this episode, we'll explore an upgrading a blog post with comments to add in the new functionality.
rails turbo hotwire 26:16

Chapters

  • Introduction (0:00)
  • Setting up the base code (3:41)
  • Updating turbo-rails and turbo (14:30)
  • Fixing Comments#create (16:31)
  • Broadcasting comments (18:49)
  • Broadcasting refreshes (20:53)
  • Fixing refreshed new comments (23:21)
  • Broadcasting post changes (24:25)
  • Final thoughts (25:26)

Resources

Episode Source - https://github.com/driftingruby/436-turbo-8

This episode is sponsored by Honeybadger


Download Source Code

Summary

# Terminal
rails action_text:install
rails g scaffold posts title
rails g model comment post:belongs_to session
rails g controller comments

# app/models/post.rb
class Post < ApplicationRecord
  has_many :comments, dependent: :destroy

  has_rich_text :content
  has_one_attached :poster
  broadcasts_refreshes
end

# app/views/posts/_form.html.erb
  <div class="mb-3">
    <%= form.label :poster, class: 'form-label' %>
    <%= form.file_field :poster, class: 'form-control' %>
  </div>

  <div class="mb-3">
    <%= form.label :content, class: 'form-label' %>
    <%= form.rich_text_area :content, class: 'form-control' %>
  </div>

# app/controllers/posts_controller.rb
def post_params
  params.require(:post).permit(:title, :poster, :content)
end

# app/views/posts/show.html.erb
<%= turbo_stream_from @post %>

<%= render @post %>

<h2>Comments</h2>

<div id="comments">
  <%= render partial: "comments/comment", collection: @post.comments, locals: { post: @post } %>
</div>

<div data-turbo-permanent>
  <%= render partial: "comments/new", locals: { post: @post, comment: @post.comments.new } %>
</div>

# app/views/posts/_post.html.erb
<div id="<%= dom_id post %>" class="scaffold_record position-relative">
  <% if post.poster.attached? %>
    <%= image_tag post.poster.variant(resize_to_fill: [1600, 300]), class: "img-fluid" %>
  <% end %>

  <h1 class="position-absolute top-50 start-50 translate-middle text-white text-center">
    <%= post.title %>
  </h1>
</div>

<%= post.content %>

# config/routes.rb
resources :posts do
  resources :comments, only: [:create, :destroy]
end

# app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  before_action :set_post

  def create
    @comment = @post.comments.new(comment_params)
    @comment.session = session.id
    @comment.save
    # redirect_to @post
    # render turbo_stream: [
    #   turbo_stream.replace(
    #     "new_comment",
    #     partial: "comments/new",
    #     locals: { post: @post, comment: Comment.new }
    #   ),
    #   turbo_stream.append(
    #     "comments",
    #     partial: "comments/comment",
    #     locals: { post: @post, comment: @comment }
    #   )
    # ]
  end

  def destroy
    @comment = @post.comments.find(params[:id])
    @comment.destroy
    redirect_to @post
  end

  private

  def set_post
    @post = Post.find(params[:post_id])
  end

  def comment_params
    params.require(:comment).permit(:content)
  end
end

# app/views/comments/_new.html.erb
<%= form_with model: [post, comment], id: :new_comment do |f| %>
  <%= f.rich_text_area :content %>
  <%= f.submit class: "mt-3 btn btn-primary" %>
<% end %>

# app/views/comments/_comment.html.erb
<%= content_tag :div, id: dom_id(comment), class: "card mb-3" do %>
  <div class="card-body">
    <p class="card-text"><%= comment.content %></p>
    <p class="card-text">
      <small class="text-muted">
        Posted <%= time_ago_in_words comment.created_at %> ago |
        Posted by <%= comment.session %>
        <% if session.id.to_s == comment.session %>
          <%= link_to "Delete", [post, comment], "data-turbo-method": :delete %>
        <% end %>
      </small>
    </p>
  </div>
<% end %>

# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :post
  has_rich_text :content

  # broadcasts_to :post
  broadcasts_refreshes_to :post
end

# Gemfile
gem "turbo-rails", "2.0.0-beta.2"

# package.json
"@hotwired/turbo-rails": "^8.0.0-beta.2",

# app/views/layouts/application.html.erb
<%# turbo_refreshes_with method: :replace, scroll: :reset %>
<%= turbo_refreshes_with method: :morph, scroll: :preserve %>
<%= yield :head %>

# app/views/comments/create.turbo_stream.erb
<%= turbo_stream.replace(
        "new_comment",
        partial: "comments/new",
        locals: { post: @post, comment: Comment.new }) %>

<%= turbo_stream.append(
        "comments",
        partial: "comments/comment",
        locals: { post: @post, comment: @comment }) %>