Hotwire、StimulusJS 和 Turbo 已经问世了将近一半的十年,而就在过去的一年里,Turbo 广播和流模式也经历了重大变化。所有这些功能都包含在 Rails 8 中,并且还有其他新特性,例如 SolidQueue、SolidCache 和 SolidCable,使开发变得更加迅速。其中的两个功能使得快速搭建这样的克隆应用变得非常简单!
注意:当我快速制作一些东西时,我会使用Bootstrap。你可以在views/layout/application.html.erb
中添加一个资产标签,或者在运行rails new
命令之前安装yarn,然后在rails --new
命令中添加Bootstrap。此外,我使用--main
标志从Github的主分支安装Rails,以获取尚未发布但已接近完成的Rails 8。
不啰嗦,让我们开始一个新的应用:
rails new blabber --main --css=boostrap
进入全屏模式 退出全屏模式
Rails 会完成以下操作:
安装应用程序。
安装 gems。
处理 Bootstrap 安装。
安装 yarn 包。
Hotwire 和 Turbo 现在已经是 Rails 的一部分了,所以无需安装任何额外的东西!只需运行 bin/dev
并开始编写代码。
现在,让我们创建一个模型来保存克隆推文的数据,在这个名为 Blabber 的推特克隆中。所有基本属性:
rails g model Post username body:text likes_count:integer repost_count:integer
进入全屏模式 退出全屏模式
为了使这与之前的文章保持相似,我们将在Post
模型中添加相同的验证:
class Post < ApplicationRecord
turbo_refreshes_with
validates :body, length: { minimum: 1, maximum: 280 }
end
进入全屏模式 退出全屏模式
一个区别是 broadcasts_refreshes
这一行。这一行是 Turbo 8 的一部分,后端的变更(这里是指模型的变更)会广播为 Turbo 的 refresh
事件,由 turbo.js
前端接收。幕后隐藏的是 Rails 所做的所有工作,以连接浏览器,使其能够接收特定模型实例的更新,以及所有相关的连接部分,如 ActiveJob、ActionCable、SolidQueue 和 SolidCable。
我们将对生成的迁移文件进行一些微小的调整,以添加一些数据库级别的默认值(并允许我们在 Post
创建时保持代码简单)。
class CreatePosts < ActiveRecord::Migration[6.0]
def change
create_table :posts do |t|
t.string :username, default: 'Blabby'
t.text :body
t.integer :likes_count, default: 0
t.integer :repost_count, default: 0
t.timestamps
end
end
end
进入全屏模式 退出全屏模式
好的!现在,我们可以运行这个迁移了!
rails db:migrate
进入全屏模式 退出全屏模式
另一个变化是,在这个 refreshes
和流的过程中,我们的控制器现在可以不了解这些流,不需要根据格式来响应或拥有与应用程序非动态部分使用的相同片段非常相似的模板文件。
class PostsController < ApplicationController
def index
@posts = Post.all.order(created_at: :desc)
@post = Post.new
end
def create
@post = Post.new(post_params)
respond_to do |format|
if @post.save
redirect_to posts_path
else
render :index
end
end
end
def like
Post.find_by(id: params[:post_id]).increment(:likes_count).save
redirect_to posts_path
end
def repost
Post.find_by(id: params[:post_id]).increment(:repost_count).save
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:body)
end
end
进入全屏模式 退出全屏模式
简单的控制器。index
动作返回一个帖子列表给 @post
。create
使用 StrongParameters
创建一个新的帖子,并重定向回 index
模板。like
和 repost
类似,只是它们会分别增加相应的计数列。
让我们为这些控制器动作设置几个路由。是的,这些路由并不完全是 RESTful 的,但 1) 它们可以工作。2) 这是一个 10 分钟的教程。3) 在我的小应用程序中,POST 请求并没有让其他浏览器窗口更新变化。(如果有人知道原因,我很乐意听听!)
Rails.application.routes.draw do
resources :posts, only: %i[index create] do
get 'like'
get 'repost'
end
root to: 'posts#index'
end
进入全屏模式 退出全屏模式
有了模型、控制器和路由,我们现在可以组合一些视图模板。接下来,我们可以向应用程序布局中添加“魔法调料”,以监听 refresh
广播。
<!DOCTYPE html>
<html>
<head>
<title><%= content_for(:title) || "Blabber" %></title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<%= csrf_meta_tags %> <%= csp_meta_tag %> <%= turbo_refreshes_with method:
:replace, scroll: :preserve %> <%= yield :head %> <%# 启用 PWA manifest 以支持可安装的应用程序(确保在 config/routes.rb 中也启用) %> <%#=
tag.link rel: "manifest", href: pwa_manifest_path %>
<link rel="icon" href="/icon.png" type="image/png" />
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/icon.png" />
<%# 包含 app/views/stylesheets 目录下的所有样式表文件 %> <%=
stylesheet_link_tag :app, "data-turbo-track": "reload" %> <%=
javascript_importmap_tags %> <%= stylesheet_link_tag "application",
"data-turbo-track": "reload" %>
</head>
<body>
<%= yield %>
</body>
</html>
进入全屏模式 退出全屏模式
The yield :head
由 rails new
命令生成,并在其上方我们添加了 <%= turbo_refreshes_with method: :replace, scroll: :preserve %>
以告知应用程序监听 refreshes
。
第一个视图模板是 app/views/posts/index.html.erb
:
<div class="container">
<h1>Blabber</h1>
<h4>一个 Rails、Hotwire 演示</h4>
<%= render partial: 'form' %> <%= turbo_stream_from 'posts' %> <%= render
@posts %>
</div>
进入全屏模式 退出全屏模式
视图是一个相对简单的Rails模板。它使用render @posts
方法来渲染@posts
集合,使用的是posts/_post
片段。turbo_stream_from:posts
将视图层与Post模型的广播连接起来。
这个部分是一些标准的单个对象标记,加入了一些 Bootstrap 类来让它看起来还算不错:
app/views/posts/_posts.html.erb
<%= turbo_stream_from post %>
<div class="card mb-2" id="<%= dom_id(post) %>">
<div class="card-body">
<h5 class="card-title text-muted">
<small class="float-right"> 发布于 <%= post.created_at %> </small>
<%= post.username %>
</h5>
<div class="card-text lead mb-2"><%= post.body %></div>
<%= link_to post_repost_path(post), { class: 'card-link', data: {
turbo_prefetch: false }} do %> 转发 (<%= post.repost_count %>) <% end %>
<%= link_to post_like_path(post), { class: 'card-link', data: {
turbo_prefetch: false }} do %> 点赞 (<%= post.likes_count %>) <% end %>
</div>
</div>
进入全屏模式 退出全屏模式
另一个 turbo_stream
命令用于监听广播的更新和删除操作,而 dom_id
辅助函数用于创建传统的 Rails 视图 ID model_resource-id
。在这种情况下,它会看起来像 posts_1
等。
接下来是一个简单的 Rails 表单:
<%= form_with model: @post, id: dom_id(@post), html: {class: 'my-4' } do |f| %>
<% if @post.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(@post.errors.count, "错误") %> 阻止了此帖子的保存:
</h2>
<ul>
<% @post.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<%= f.text_area :body, placeholder: '输入你的吐槽', class: 'form-control',
rows: 3 %>
</div>
<div class="actions"><%= f.submit class: "btn btn-primary" %></div>
<% end %>
进入全屏模式 退出全屏模式
如果你还记得 index.html.erb
,另一个框架包裹了表单的渲染调用。因此,我们包含了 dom_id
以允许控制器的 create
动作启用 Turbo 管理流的标记。
现在我们将回到控制器,深入讨论创建动作。
def 创建
@post = Post.new(post_params)
respond_to do |format|
if @post.save
redirect_to posts_path
else
render :index
end
end
end
进入全屏模式 退出全屏模式
之前,在这一点上,我们会讨论如何在标记中返回流以追加到顶级框架,但现在 Turbo 8 的刷新和广播功能可以处理这一切,无需任何额外的更改。
此时,你应该有一个可以在两个浏览器中打开的应用程序,它应该看起来像这样!
好了!如果一切顺利,这将是迄今为止最快的Twitter克隆教程,而且可能需要的代码行数最少!时间会证明这一切如何在复杂的现实世界Rails应用程序中协同工作和扩展,但就目前而言,开发人员复杂性的降低是一个巨大的胜利!