如何向RubyonRails应用程序添加刺激

来自菜鸟教程
跳转至:导航、​搜索

介绍

如果您正在使用 Ruby on Rails 项目,您的需求可能包括与 视图模板 生成的 HTML 的一些交互性。 如果是这样,对于如何实现这种交互性,您有几个选择。

例如,您可以实现 JavaScript 框架,如 ReactEmber。 如果您的要求包括在客户端处理状态,或者如果您担心与对服务器的频繁查询相关的性能问题,那么选择这些框架之一可能是有意义的。 许多单页应用程序 (SPA) 采用这种方法。

但是,在实现管理客户端状态和频繁更新的框架时,需要牢记几个注意事项:

  1. 加载和转换要求(例如解析 JavaScript,以及获取 JSON 并将其转换为 HTML)可能会限制性能。
  2. 对框架的承诺可能涉及编写比您的特定用例所需的更多代码,特别是如果您正在寻找小规模的 JavaScript 增强功能。
  3. 在客户端和服务器端管理的状态会导致重复工作,并增加错误的表面积。

作为替代方案,Basecamp 的团队(编写 Rails 的同一团队)创建了 Stimulus.js,他们将其描述为“用于您已有的 HTML 的适度 JavaScript 框架。 ” Stimulus 旨在通过使用服务器端生成的 HTML 来增强现代 Rails 应用程序。 状态存在于 文档对象模型 (DOM) 中,该框架提供了与 DOM 中的元素和事件交互的标准方式。 它与 Turbolinks(默认包含在 Rails 5+ 中)一起工作,以通过限制和范围明确定义的目的的代码来提高性能和加载时间。

在本教程中,您将安装和使用 Stimulus 来构建现有的 Rails 应用程序,该应用程序为读者提供有关鲨鱼的信息。 该应用程序已经有一个处理鲨鱼数据的模型,但是您将为有关单个鲨鱼的帖子添加一个嵌套资源,允许用户构建关于鲨鱼的一系列想法和意见。 这篇文章大致平行于 如何为 Ruby on Rails 应用程序创建嵌套资源 ,除了我们将使用 JavaScript 来操作页面上帖子的位置和外观。 我们还将采用稍微不同的方法来构建后期模型本身。

先决条件

要遵循本教程,您将需要:

  • 运行 Ubuntu 18.04 的本地机器或开发服务器。 您的开发机器应该有一个具有管理权限的非 root 用户和一个配置了 ufw 的防火墙。 有关如何设置的说明,请参阅我们的 Initial Server Setup with Ubuntu 18.04 教程。
  • Node.jsnpm 安装在本地机器或开发服务器上。 本教程使用 Node.js 版本 10.16.3 和 npm 版本 6.9.0。 有关在 Ubuntu 18.04 上安装 Node.js 和 npm 的指导,请按照 如何在 Ubuntu 18.04 上安装 Node.js 的 “使用 PPA 安装” 部分中的说明进行操作。
  • Ruby、rbenv 和 Rails 安装在本地计算机或开发服务器上,遵循 如何在 Ubuntu 18.04 上使用 rbenv 安装 Ruby on Rails 中的 Steps 1-4 . 本教程使用 Ruby 2.5.1、rbenv 1.1.2 和 Rails 5.2.3
  • 按照 How To Build a Ruby on Rails Application 中的说明安装 SQLite,并创建了一个基本的鲨鱼信息应用程序。

第 1 步 - 创建嵌套模型

我们的第一步是创建一个嵌套的 Post model,我们将把它与我们现有的 Shark 模型相关联。 我们将通过在我们的模型之间创建 Active Record associations 来做到这一点:帖子将属于特定的鲨鱼,每个鲨鱼可以有多个帖子。

首先,导航到您在先决条件中为 Rails 项目创建的 sharkapp 目录:

cd sharkapp

为了创建我们的 Post 模型,我们将使用 rails generate 命令和 model 生成器。 键入以下命令以创建模型:

rails generate model Post body:text shark:references

对于 body:text,我们告诉 Rails 在 posts 数据库表中包含一个 body 字段——该表映射到 Post 模型。 我们还包括 :references 关键字,它在 SharkPost 模型之间建立关联。 具体来说,这将确保将表示 sharks 数据库中每个鲨鱼条目的 外键 添加到 posts 数据库中。

运行命令后,您将看到确认 Rails 为应用程序生成的资源的输出。 在继续之前,您可以检查数据库迁移文件以查看模型和数据库表之间现在存在的关系。 使用以下命令查看文件的内容,确保将您自己的迁移文件上的时间戳替换为此处显示的内容:

cat db/migrate/20190805132506_create_posts.rb

您将看到以下输出:

Outputclass CreatePosts < ActiveRecord::Migration[5.2]
  def change
    create_table :posts do |t|
      t.text :body
      t.references :shark, foreign_key: true

      t.timestamps
    end
  end
end

如您所见,该表包含一个鲨鱼外键列。 该密钥将采用 model_name_id 的形式——在我们的例子中是 shark_id

Rails 也在其他地方建立了模型之间的关系。 使用以下命令查看新生成的 Post 模型:

cat app/models/post.rb
Outputclass Post < ApplicationRecord
  belongs_to :shark
end

belongs_to 关联建立模型之间的关系,其中声明模型的单个实例属于命名模型的单个实例。 在我们的应用程序中,这意味着单个帖子属于单个鲨鱼。

尽管 Rails 已经在我们的 Post 模型中设置了 belongs_to 关联,但我们还需要在 Shark 模型中指定 has_many 关联,以便这种关系才能正常运作。

要将 has_many 关联添加到 Shark 模型,请使用 nano 或您喜欢的编辑器打开 app/models/shark.rb

nano app/models/shark.rb

将以下行添加到文件中以建立鲨鱼和帖子之间的关系:

~/sharkapp/app/models/shark.rb

class Shark < ApplicationRecord
  has_many :posts
  validates :name, presence: true, uniqueness: true
  validates :facts, presence: true
end

这里值得考虑的一件事是,一旦删除了特定的鲨鱼,帖子会发生什么。 我们可能不希望与已删除鲨鱼相关的帖子保留在数据库中。 为了确保在删除该鲨鱼时删除与给定鲨鱼相关的任何帖子,我们可以在关联中包含 dependent 选项。

将以下代码添加到文件中,以确保对给定鲨鱼的 destroy 操作删除任何关联的帖子:

~/sharkapp/app/models/shark.rb

class Shark < ApplicationRecord
  has_many :posts, dependent: :destroy
  validates :name, presence: true, uniqueness: true
  validates :facts, presence: true
end

完成这些更改后,保存并关闭文件。 如果您正在使用 nano,请按 CTRL+XY,然后按 ENTER

您现在已经为您的帖子生成了一个模型,但您还需要一个 控制器 来协调数据库中的数据和生成并呈现给用户的 HTML。

第 2 步 — 为嵌套资源创建控制器

创建 post 控制器将涉及在应用程序的主路由文件中设置嵌套资源路由,并创建控制器文件本身以指定我们希望与特定操作关联的方法。

首先,打开您的 config/routes.rb 文件以建立资源路由之间的关系:

nano config/routes.rb

目前,该文件如下所示:

~/sharkapp/config/routes.rb

Rails.application.routes.draw do
  resources :sharks

  root 'sharks#index'
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

我们想在鲨鱼和帖子资源之间创建一个依赖关系关系。 为此,请更新您的路由声明以使 :sharks 成为 :posts 的父级。 将文件中的代码更新为如下所示:

~/sharkapp/config/routes.rb

Rails.application.routes.draw do
  resources :sharks do
    resources :posts
  end
  root 'sharks#index'
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

完成编辑后保存并关闭文件。

接下来,为控制器创建一个名为 app/controllers/posts_controller.rb 的新文件:

nano app/controllers/posts_controller.rb

在这个文件中,我们将定义用于创建和销毁单个帖子的方法。 但是,因为这是一个嵌套模型,我们还需要创建一个局部实例变量 @shark,我们可以使用它来将特定帖子与特定鲨鱼相关联。

首先,我们可以创建 PostsController 类本身,以及两个 private 方法:get_shark,这将允许我们引用特定的鲨鱼,以及 post_params ,这使我们能够通过 params 方法 访问用户提交的信息。

将以下代码添加到文件中:

~/sharkapp/app/controllers/posts_controller.rb

class PostsController < ApplicationController
  before_action :get_shark 

  private

  def get_shark
    @shark = Shark.find(params[:shark_id])
  end

  def post_params
    params.require(:post).permit(:body, :shark_id)
  end
end

现在,您可以使用 :shark_id 键和用户为创建帖子而输入的数据来获取与您的帖子相关联的特定鲨鱼实例的方法。 这两个对象现在都可用于您定义的用于处理创建和销毁帖子的方法。

接下来,在 private 方法上方,将以下代码添加到文件中以定义 createdestroy 方法:

~/sharkapp/app/controllers/posts_controller.rb

. . .
  def create
    @post = @shark.posts.create(post_params)
  end
      
  def destroy
    @post = @shark.posts.find(params[:id])
    @post.destroy   
  end
. . .

这些方法将 @post 实例与特定的 @shark 实例相关联,并使用当我们在鲨鱼和鲨鱼之间创建 has_many 关联时可用的 收集方法 帖子。 findcreate 等方法允许我们定位与特定鲨鱼相关的帖子集合。

完成的文件将如下所示:

~/sharkapp/app/controllers/posts_controller.rb

class PostsController < ApplicationController
  before_action :get_shark 

  def create
    @post = @shark.posts.create(post_params)
  end
      
  def destroy
    @post = @shark.posts.find(params[:id])
    @post.destroy   
  end

  private

  def get_shark
    @shark = Shark.find(params[:shark_id])
  end

  def post_params
    params.require(:post).permit(:body, :shark_id)
  end
end

完成编辑后保存并关闭文件。

有了控制器和模型,您就可以开始考虑视图模板以及如何组织应用程序生成的 HTML。

第 3 步 — 使用部分重新组织视图

您已经创建了一个 Post 模型和控制器,所以从 Rails 角度考虑的最后一件事是呈现并允许用户输入有关鲨鱼的信息的视图。 视图也是您有机会与 Stimulus 建立交互性的地方。

在这一步中,您将绘制出您的视图和局部视图,这将是您使用 Stimulus 的起点。

sharks/show 视图将作为帖子和与帖子相关的所有部分的基础的视图。

打开文件:

nano app/views/sharks/show.html.erb

目前,该文件如下所示:

~/sharkapp/app/views/sharks/show.html.erb

<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @shark.name %>
</p>

<p>
  <strong>Facts:</strong>
  <%= @shark.facts %>
</p>

<%= link_to 'Edit', edit_shark_path(@shark) %> |
<%= link_to 'Back', sharks_path %>

当我们创建我们的 Post 模型时,我们选择不为我们的帖子生成视图,因为我们将通过我们的 sharks/show 视图来处理它们。 所以在这个视图中,我们首先要解决的是我们将如何接受用户对新帖子的输入,以及我们将如何将帖子呈现给用户。

注意:有关此方法的替代方法,请参阅如何为Ruby on Rails应用程序创建嵌套资源,它使用创建的全范围设置帖子视图,读取、更新、删除 (CRUD) 方法在帖子控制器中定义。 有关这些方法及其工作原理的讨论,请参阅 如何构建 Ruby on Rails 应用程序Step 3


我们不会将我们所有的功能都构建到这个视图中,而是使用部分——服务于特定功能的可重用模板。 我们将为新帖子创建一个部分,另一个来控制帖子如何显示给用户。 在整个过程中,我们将思考如何以及在何处使用 Stimulus 来操纵页面上帖子的外观,因为我们的目标是使用 JavaScript 控制帖子的呈现。

首先,在鲨鱼事实下方,为帖子添加一个 <h2> 标题和一行来渲染名为 sharks/posts 的部分:

~/sharkapp/app/views/sharks/show.html.erb

. . . 
<p>
  <strong>Facts:</strong>
  <%= @shark.facts %>
</p>

<h2>Posts</h2>
<%= render 'sharks/posts' %>
. . . 

这将使用表单构建器为新的帖子对象呈现部分。

接下来,在 EditBack 链接下方,我们将添加一个部分来控制页面上旧帖子的显示。 将以下行添加到文件中以渲染名为 sharks/all 的局部:

~/sharkapp/app/views/sharks/show.html.erb

<%= link_to 'Edit', edit_shark_path(@shark) %> |
<%= link_to 'Back', sharks_path %>

<div>
  <%= render 'sharks/all' %>
</div>

当我们开始将 Stimulus 集成到此文件中时,<div> 元素将很有用。

完成这些编辑后,保存并关闭文件。 通过您在 Rails 方面所做的更改,您现在可以继续安装 Stimulus 并将其集成到您的应用程序中。

第 4 步 — 安装刺激

使用 Stimulus 的第一步是安装和配置我们的应用程序以使用它。 这将包括确保我们拥有正确的依赖关系,包括 Yarn 包管理器和 Webpacker,这是允许我们使用 JavaScript 预处理器和捆绑器 的 gem webpack。 有了这些依赖项,我们将能够安装 Stimulus 并使用 JavaScript 来操作 DOM 中的事件和元素。

让我们从安装 Yarn 开始。 首先,更新您的软件包列表:

sudo apt update

接下来,为 Debian Yarn 存储库添加 GPG 密钥:

curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -

将存储库添加到您的 APT 源:

echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list

使用新添加的 Yarn 包更新包数据库:

sudo apt update

最后,安装 Yarn:

sudo apt install yarn

安装 yarn 后,您可以继续将 webpacker gem 添加到您的项目中。

打开项目的 Gemfile,其中列出了项目的 gem 依赖项:

nano Gemfile

在文件中,您将看到默认启用 Turbolinks:

~/sharkapp/Gemfile

. . . 
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
. . . 

Turbolinks 旨在通过优化页面加载来提高性能:Turbolinks 不是让链接点击导航到新页面,而是拦截这些点击事件并使用 异步 JavaScript 和 HTML (AJAX) 发出页面请求。 然后它替换当前页面的正文并合并 <head> 部分的内容,而 JavaScript windowdocument 对象和 <html> 元素仍然存在渲染之间。 这解决了页面加载速度慢的主要原因之一:重新加载 CSS 和 JavaScript 资源。

我们在 Gemfile 中默认获取 Turbolinks,但我们需要添加 webpacker gem 以便我们可以安装和使用 Stimulus。 在 turbolinks gem 下面,添加 webpacker

~/sharkapp/Gemfile

. . . 
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
gem 'webpacker', '~> 4.x'
. . . 

完成后保存并关闭文件。

接下来,使用 bundle 命令将 gem 添加到项目的包中:

bundle

这将生成一个新的 Gemfile.lock 文件 — 项目的 gem 和版本的最终记录。

接下来,使用以下 bundle exec 命令在捆绑包的上下文中安装 gem:

bundle exec rails webpacker:install

安装完成后,我们需要对应用程序的内容安全文件进行一些小调整。 这是因为我们正在使用 Rails 5.2+,这是一个 内容安全策略 (CSP) 受限环境,这意味着应用程序中允许的唯一脚本必须来自受信任的来源。

打开 config/initializers/content_security_policy.rb,这是 Rails 为我们提供的用于定义应用程序范围的安全策略的默认文件:

nano config/initializers/content_security_policy.rb

将以下行添加到文件的底部,以允许 webpack-dev-server(为我们的应用程序的 webpack 包提供服务的服务器)作为允许的来源:

~/sharkapp/config/initializers/content_security_policy.rb

. . . 
Rails.application.config.content_security_policy do |policy|
  policy.connect_src :self, :https, 'http://localhost:3035', 'ws://localhost:3035' if Rails.env.development?
end

这将确保 webpacker-dev-server 被识别为受信任的资产来源。

完成此更改后,保存并关闭文件。

通过安装 webpacker,您在项目的 app 目录(主应用程序代码所在的目录)中创建了两个新目录。 新的父目录 app/javascript 将是您项目的 JavaScript 代码所在的位置,其结构如下:

Output├── javascript
│   ├── controllers
│   │   ├── hello_controller.js
│   │   └── index.js
│   └── packs
│       └── application.js

app/javascript 目录将包含两个子目录:app/javascript/packs,它将包含您的 webpack 入口点,以及 app/javascript/controllers,您将在其中定义您的刺激 控制器 . 我们刚刚使用的 bundle exec 命令将创建 app/javascript/packs 目录,但我们需要安装 Stimulus 才能自动生成 app/javascript/controllers 目录。

安装 webpacker 后,我们现在可以使用以下命令安装 Stimulus:

bundle exec rails webpacker:install:stimulus

你会看到如下输出,说明安装成功:

Output. . . 
success Saved lockfile.
success Saved 5 new dependencies.
info Direct dependencies
└─ stimulus@1.1.1
info All dependencies
├─ @stimulus/core@1.1.1
├─ @stimulus/multimap@1.1.1
├─ @stimulus/mutation-observers@1.1.1
├─ @stimulus/webpack-helpers@1.1.1
└─ stimulus@1.1.1
Done in 8.30s.
Webpacker now supports Stimulus.js 🎉

我们现在已经安装了 Stimulus,并且我们需要使用它的主要目录。 在继续编写任何代码之前,我们需要进行一些应用程序级别的调整以完成安装过程。

首先,我们需要对 app/views/layouts/application.html.erb 进行调整,以确保我们的 JavaScript 代码可用并且在我们的主 webpacker 入口点 app/javascript/packs/application.js 中定义的代码运行每次加载页面时。

打开那个文件:

nano app/views/layouts/application.html.erb

将以下 javascript_include_tag 标签更改为 javascript_pack_tag 以加载 app/javascript/packs/application.js

~/sharkapp/app/views/layouts/application.html.erb

. . .
    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
. . . 

进行此更改后,保存并关闭文件。

接下来,打开app/javascript/packs/application.js

nano app/javascript/packs/application.js

最初,该文件将如下所示:

~/sharkapp/app/javascript/packs/application.js

. . . 
console.log('Hello World from Webpacker')

import "controllers"

删除那里的样板代码,并添加以下代码以加载您的 Stimulus 控制器文件并启动应用程序实例:

~/sharkapp/app/javascript/packs/application.js

. . . 
import { Application } from "stimulus"
import { definitionsFromContext } from "stimulus/webpack-helpers"

const application = Application.start()
const context = require.context("../controllers", true, /\.js$/)
application.load(definitionsFromContext(context))

此代码使用 webpack 辅助方法来要求 app/javascript/controllers 目录中的控制器并加载此上下文以在您的应用程序中使用。

完成编辑后保存并关闭文件。

您现在已安装 Stimulus 并准备在您的应用程序中使用。 接下来,我们将构建我们在鲨鱼 show 视图中引用的部分——sharks/postssharks/all——使用 Stimulus controllers目标动作

第 5 步 — 在 Rails Partials 中使用 Stimulus

我们的 sharks/posts 部分将使用 form_with 表单助手 创建一个新的帖子对象。 它还将利用 Stimulus 的三个核心概念:控制器、目标和动作。 这些概念的作用如下:

  • 控制器是在 JavaScript 模块中定义并作为模块的默认对象导出的 JavaScript 类。 通过控制器,您可以访问特定的 HTML 元素和 app/javascript/packs/application.js 中定义的刺激应用程序实例。
  • 目标允许您按名称引用特定的 HTML 元素,并与特定的控制器相关联。
  • 动作控制控制器如何处理 DOM 事件,并且还与特定控制器相关联。 它们在与控制器关联的 HTML 元素、控制器中定义的方法和 DOM 事件侦听器之间创建连接。

在我们的部分中,我们首先要像通常使用 Rails 一样构建一个表单。 然后,我们将向表单添加一个 Stimulus 控制器、操作和目标,以便使用 JavaScript 来控制如何将新帖子添加到页面中。

首先,为部分创建一个新文件:

nano app/views/sharks/_posts.html.erb

在文件中,添加以下代码以使用 form_with 帮助器创建新的帖子对象:

~/sharkapp/app/views/sharks/_posts.html.erb

        <%= form_with model: [@shark, @shark.posts.build] do |form| %>
                <%= form.text_area :body, placeholder: "Your post here" %>
                <br>
                <%= form.submit %>
        <% end %>

到目前为止,这个表单的行为就像一个典型的 Rails 表单,使用 form_with 帮助器来构建一个带有为 Post 模型定义的字段的帖子对象。 因此,表单有一个用于帖子 :body 的字段,我们在其中添加了一个 placeholder 并提示填写帖子。

此外,该表单的范围还可以利用 SharkPost 模型之间的关联所带来的收集方法。 在这种情况下,根据用户提交的数据创建的新帖子对象将属于与我们当前正在查看的鲨鱼相关的帖子集合。

我们现在的目标是添加一些 Stimulus 控制器、事件和操作来控制发布数据在页面上的显示方式。 由于 Stimulus 操作,用户最终将提交发布数据并看到它发布到页面。

首先,我们将在 <div> 元素中添加一个名为 posts 的控制器:

~/sharkapp/app/views/sharks/_posts.html.erb

<div data-controller="posts">
        <%= form_with model: [@shark, @shark.posts.build] do |form| %>
                 <%= form.text_area :body, placeholder: "Your post here" %>
                 <br>
                 <%= form.submit %>
        <% end %>
</div>

确保添加关闭

标签正确地确定控制器的范围。 接下来,我们将向表单附加一个操作,该操作将由表单提交事件触发。 此操作将控制用户输入在页面上的显示方式。 它将引用我们将在 Posts Stimulus 控制器中定义的 addPost 方法:

~/sharkapp/app/views/sharks/_posts.html.erb

<div data-controller="posts">
        <%= form_with model: [@shark, @shark.posts.build], data: { action: "posts#addBody" } do |form| %>
        . . . 
                 <%= form.submit %>
        <% end %>
</div>

我们使用 :data 选项和 form_with 来提交刺激动作作为附加的 HTML 数据属性。 动作本身有一个称为 动作描述符 的值,由以下内容组成:

  • 要监听的DOM事件。 在这里,我们使用与表单元素关联的默认事件,提交,因此我们不需要在描述符本身中指定事件。 有关常见元素/事件对的更多信息,请参阅 Stimulus 文档
  • 控制器标识符,在我们的例子中是posts
  • 事件应该调用的方法。 在我们的例子中,这是我们将在控制器中定义的 addBody 方法。

接下来,我们将数据目标附加到 :body <textarea> 元素中定义的用户输入,因为我们将在 addBody 方法中使用这个输入值。

将以下 :data 选项添加到 :body <textarea> 元素:

~/sharkapp/app/views/sharks/_posts.html.erb

<div data-controller="posts">
        <%= form_with model: [@shark, @shark.posts.build], data: { action: "posts#addBody" } do |form| %>
                <%= form.text_area :body, placeholder: "Your post here", data: { target: "posts.body" } %>
. . .

与动作描述符非常相似,Stimulus 目标具有 目标描述符 ,其中包括控制器标识符和目标名称。 在这种情况下,posts 是我们的控制器,body 是目标本身。

作为最后一步,我们将为输入的 body 值添加数据目标,以便用户在提交帖子后立即看到他们的帖子。

在表单下方和结束 <div> 上方添加以下带有 add 目标的 <ul> 元素:

~/sharkapp/app/views/sharks/_posts.html.erb

. . .
        <% end %>
  <ul data-target="posts.add">
  </ul>

</div>

body 目标一样,我们的目标描述符包括控制器和目标的名称——在本例中为 add

完成的部分将如下所示:

~/sharkapp/app/views/sharks/_posts.html.erb

<div data-controller="posts">
        <%= form_with model: [@shark, @shark.posts.build], data: { action: "posts#addBody"} do |form| %>
                <%= form.text_area :body, placeholder: "Your post here", data: { target: "posts.body" } %>
                <br>
                <%= form.submit %>
        <% end %>
  <ul data-target="posts.add">
  </ul>

</div>

完成这些更改后,您可以保存并关闭文件。

您现在已经创建了添加到 sharks/show 视图模板的两个局部之一。 接下来,您将创建第二个 sharks/all,它将显示数据库中的所有旧帖子。

app/views/sharks/ 目录下新建一个名为 _all.html.erb 的文件:

nano app/views/sharks/_all.html.erb

将以下代码添加到文件中以遍历与所选鲨鱼关联的帖子集合:

~/sharkapp/app/views/sharks/_all.html.erb

<% for post in @shark.posts  %>
    <ul>

        <li class="post">
            <%= post.body %>
        </li>

    </ul>
    <% end %>

此代码使用 for 循环遍历与特定鲨鱼关联的帖子对象集合中的每个帖子实例。

我们现在可以向这个局部添加一些刺激动作来控制页面上帖子的外观。 具体来说,我们将添加控制赞成票以及帖子是否在页面上可见的操作

然而,在我们这样做之前,我们需要向我们的项目添加一个 gem,以便我们可以使用 Font Awesome 图标,我们将使用它来注册赞成票。 打开第二个终端窗口,并导航到您的 sharkapp 项目目录。

打开你的 Gemfile:

nano Gemfile

在您的 webpacker gem 下方,添加以下行以将 font-awesome-rails gem 包含在项目中:

~/sharkapp/Gemfile

. . . 
gem 'webpacker', '~> 4.x'
gem 'font-awesome-rails', '~>4.x'
. . . 

保存并关闭文件。

接下来,安装gem:

bundle install

最后,打开应用程序的主样式表 app/assets/stylesheets/application.css

nano app/assets/stylesheets/application.css

添加以下行以在您的项目中包含 Font Awesome 的样式:

~/sharkapp/app/assets/stylesheets/application.css

. . . 
*
 *= require_tree .
 *= require_self
 *= require font-awesome
 */

保存并关闭文件。 您现在可以关闭第二个终端窗口。

回到您的 app/views/sharks/_all.html.erb 部分,您现在可以添加两个 button_tags 与相关的刺激动作,这将在点击事件时触发。 一个按钮将为用户提供对帖子进行投票的选项,而另一个按钮将为他们提供将其从页面视图中删除的选项。

将以下代码添加到 app/views/sharks/_all.html.erb

~/sharkapp/app/views/sharks/_all.html.erb

<% for post in @shark.posts  %>
    <ul>

        <li class="post">
            <%= post.body %>
            <%= button_tag "Remove Post", data: { controller: "posts", action: "posts#remove" } %>
            <%= button_tag "Upvote Post", data: { controller: "posts", action: "posts#upvote" } %>
        </li>

    </ul>
    <% end %>

按钮标签也采用 :data 选项,因此我们添加了我们的帖子刺激控制器和两个操作:removeupvote。 再次,在动作描述符中,我们只需要定义我们的控制器和方法,因为与按钮元素关联的默认事件是单击。 单击每个按钮将触发我们控制器中定义的相应 removeupvote 方法。

完成编辑后保存并关闭文件。

在继续定义控制器之前,我们要做的最后一个更改是设置数据目标和操作,以控制 sharks/all 部分的显示方式和时间。

再次打开 show 模板,其中当前定义了对渲染 sharks/all 的初始调用:

nano app/views/sharks/show.html.erb

在文件的底部,我们有一个 <div> 元素,目前看起来像这样:

~/sharkapp/app/views/sharks/show.html.erb

. . . 
<div>
  <%= render 'sharks/all' %>
</div>

首先,将控制器添加到此 <div> 元素以限定动作和目标:

~/sharkapp/app/views/sharks/show.html.erb

. . . 
<div data-controller="posts">
  <%= render 'sharks/all' %>
</div>

接下来,添加一个按钮来控制页面上局部的外观。 这个按钮会在我们的 post 控制器中触发一个 showAll 方法。

<div> 元素下方和 render 语句上方添加按钮:

~/sharkapp/app/views/sharks/show.html.erb

. . . 
<div data-controller="posts">

<button data-action="posts#showAll">Show Older Posts</button>

  <%= render 'sharks/all' %>

同样,我们只需要在这里识别我们的 posts 控制器和 showAll 方法 - 操作将由点击事件触发。

接下来,我们将添加一个数据目标。 设置此目标的目的是控制部分在页面上的外观。 最终,我们希望用户仅在通过单击 Show Older Posts 按钮选择这样做时才能看到较旧的帖子。

因此,我们将一个名为 show 的数据目标附加到 sharks/all 部分,并将其默认样式设置为 visibility:hidden。 这将隐藏部分,除非用户通过单击按钮选择查看它。

在按钮下方和部分渲染语句上方添加以下 <div> 元素和 show 目标和 style 定义:

~/sharkapp/app/views/sharks/show.html.erb

. . . 
<div data-controller="posts">

<button data-action="posts#showAll">Show Older Posts</button>

<div data-target="posts.show" style="visibility:hidden">
  <%= render 'sharks/all' %>
</div>

请务必添加结束 </div> 标记。

完成的 show 模板将如下所示:

~/sharkapp/app/views/sharks/show.html.erb

<p id="notice"><%= notice %></p>

<p>
  <strong>Name:</strong>
  <%= @shark.name %>
</p>

<p>
  <strong>Facts:</strong>
  <%= @shark.facts %>
</p>

<h2>Posts</h2>

<%= render 'sharks/posts' %>

<%= link_to 'Edit', edit_shark_path(@shark) %> |
<%= link_to 'Back', sharks_path %>

<div data-controller="posts">

<button data-action="posts#showAll">Show Older Posts</button>

<div data-target="posts.show" style="visibility:hidden">
  <%= render 'sharks/all' %>
</div>
</div>

完成编辑后保存并关闭文件。

完成此模板及其相关部分后,您可以继续使用您在这些文件中引用的方法创建控制器。

第 6 步 - 创建刺激控制器

安装 Stimulus 创建了 app/javascript/controllers 目录,这是 webpack 加载应用程序上下文的地方,所以我们将在这个目录中创建我们的帖子控制器。 该控制器将包含我们在上一步中引用的每个方法:

  • addBody(),添加新帖子。
  • showAll(),显示较早的帖子。
  • remove(),从当前视图中删除帖子。
  • upvote(),在帖子上附加一个赞成图标。

app/javascript/controllers 目录下创建一个名为 posts_controller.js 的文件:

nano app/javascript/controllers/posts_controller.js

首先,在文件顶部,扩展 Stimulus 内置的 Controller 类:

~/sharkapp/app/javascript/controllers/posts_controller.js

import { Controller } from "stimulus"

export default class extends Controller {
}

接下来,将以下目标定义添加到文件中:

~/sharkapp/app/javascript/controllers/posts_controller.js

. . .
export default class extends Controller {
    static targets = ["body", "add", "show"]
}

以这种方式定义目标将允许我们在方法中使用 this.target-nameTarget 属性访问它们,这为我们提供了第一个匹配的目标元素。 因此,例如,为了匹配我们的目标数组中定义的 body 数据目标,我们将使用 this.bodyTarget。 这个属性允许我们操作诸如输入值或 CSS 样式之类的东西。

接下来,我们可以定义 addBody 方法,它将控制页面上新帖子的外观。 在目标定义下方添加以下代码来定义此方法:

~/sharkapp/app/javascript/controllers/posts_controller.js

. . .
export default class extends Controller {
    static targets = [ "body", "add", "show"]

    addBody() {
        let content = this.bodyTarget.value;
        this.addTarget.insertAdjacentHTML('beforebegin', "<li>" + content + "</li>");
    }
}

此方法使用 let 关键字 定义一个 content 变量,并将其设置为用户在帖子表单中输入的帖子输入字符串。 它通过我们在表单中附加到 <textarea> 元素的 body 数据目标来做到这一点。 使用 this.bodyTarget 来匹配该元素,然后我们可以使用与该元素关联的 value 属性content 的值设置为用户输入的帖子输入。

接下来,该方法将此帖子输入添加到我们添加到 sharks/posts 部分中表单构建器下方的 <ul> 元素的 add 目标中。 它使用 Element.insertAdjacentHTML() 方法 执行此操作,该方法将在 add 目标元素之前插入在 content 变量中设置的新帖子的内容。 我们还将新帖子包含在 <li> 元素中,以便新帖子显示为项目符号列表项。

接下来,在 addBody 方法下方,我们可以添加 showAll 方法,它将控制页面上旧帖子的外观:

~/sharkapp/app/javascript/controllers/posts_controller.js

. . . 
export default class extends Controller {
. . .
    addBody() {
        let content = this.bodyTarget.value;
        this.addTarget.insertAdjacentHTML('beforebegin', "<li>" + content + "</li>");
    }

    showAll() {
        this.showTarget.style.visibility = "visible";
    }

}

在这里,我们再次使用 this.target-nameTarget 属性来匹配我们的 show 目标,该目标附加到带有 sharks/all 部分的 <div> 元素。 我们给了它一个默认样式,"visibility:hidden",所以在这个方法中,我们只需将样式更改为"visible"。 这将向选择查看旧帖子的用户显示部分内容。

showAll 下方,我们接下来将添加一个 upvote 方法,以允许用户通过附加 free Font Awesome check-circle 在页面上“投票”帖子] 图标到特定的帖子。

添加以下代码来定义此方法:

~/sharkapp/app/javascript/controllers/posts_controller.js

. . . 
export default class extends Controller {
. . . 

    showAll() {
        this.showTarget.style.visibility = "visible";
    }

    upvote() {
        let post = event.target.closest(".post");
        post.insertAdjacentHTML('beforeend', '<i class="fa fa-check-circle"></i>');
    }

}

在这里,我们正在创建一个 post 变量,它将以最接近的 <li> 元素为目标,其类 post — 我们附加到每个 <li> 元素的类我们在 sharks/all 中的循环迭代。 这将针对最近的帖子,并在 <li> 元素的最后一个子元素之后添加 check-circle 图标。

接下来,我们将使用类似的方法来隐藏页面上的帖子。 在 upvote 方法下面添加以下代码来定义一个 remove 方法:

~/sharkapp/app/javascript/controllers/posts_controller.js

. . . 
export default class extends Controller {
. . . 

    upvote() {
        let post = event.target.closest(".post");
        post.insertAdjacentHTML('beforeend', '<i class="fa fa-check-circle"></i>');
    }

    remove() {
        let post = event.target.closest(".post");
        post.style.visibility = "hidden";
    }
}

再一次,我们的 post 变量将定位最接近的 <li> 元素,其类为 post。 然后它将可见性属性设置为 "hidden" 以隐藏页面上的帖子。

完成的控制器文件现在看起来像这样:

~/sharkapp/app/javascript/controllers/posts_controller.js

import { Controller } from "stimulus"

export default class extends Controller {

    static targets = ["body", "add", "show"]

    addBody() {
        let content = this.bodyTarget.value;
        this.addTarget.insertAdjacentHTML('beforebegin', "<li>" + content + "</li>");
    }

    showAll() {
        this.showTarget.style.visibility = "visible";
    }

    upvote() {
        let post = event.target.closest(".post");
        post.insertAdjacentHTML('beforeend', '<i class="fa fa-check-circle"></i>');
    }

    remove() {
        let post = event.target.closest(".post");
        post.style.visibility = "hidden";
    }
} 

完成编辑后保存并关闭文件。

使用 Stimulus 控制器后,您可以继续对 index 视图进行一些最终更改并测试您的应用程序。

第 7 步 — 修改索引视图并测试应用程序

通过对鲨鱼 index 视图的最后一次更改,您将准备好测试您的应用程序。 index 视图是应用程序的根,您在 如何构建 Ruby on Rails 应用程序Step 4 中设置。

打开文件:

nano app/views/sharks/index.html.erb

我们将使用 button_to 助手代替自动生成的用于展示和销毁鲨鱼的 link_to 助手。 这将帮助我们使用生成的 HTML 代码而不是默认的 Rails JavaScript 资产,当我们在 [ 中将 javascript_include_tag 更改为 javascript_pack_tag 时,我们在步骤 1 中指定我们将不再使用它。 X195X]。

将文件中现有的 link_to 助手替换为以下 button_to 助手:

~/sharkapp/app/views/sharks/index.html.erb

. . . 
  <tbody>
    <% @sharks.each do |shark| %>
      <tr>
        <td><%= shark.name %></td>
        <td><%= shark.facts %></td>
        <td><%= button_to 'Show', shark_path(:id => shark.id), :method => :get %></td>
        <td><%= button_to 'Edit', edit_shark_path(:id => shark.id), :method => :get %></td>
        <td><%= button_to 'Destroy', shark_path(:id => shark.id), :method => :delete %></td>
      </tr>
    <% end %>
  </tbody>
. . . 

这些助手完成的事情与 link_to 对应的助手大致相同,但 Destroy 助手现在依赖于生成的 HTML,而不是 Rails 的默认 JavaScript。

完成编辑后保存并关闭文件。

您现在已准备好测试您的应用程序。

首先,运行数据库迁移:

rails db:migrate

接下来,启动您的服务器。 如果您在本地工作,则可以使用以下命令执行此操作:

rails s

如果您在开发服务器上工作,您可以使用以下命令启动应用程序:

rails s --binding=your_server_ip

在浏览器中导航到应用程序登录页面。 如果您在本地工作,则为 localhost:3000,如果您在服务器上工作,则为 http://your_server_ip:3000

您将看到以下登录页面:

单击 Show 将带您进入此鲨鱼的 show 视图。 在这里,您将看到一个用于填写帖子的表格:

在帖子表单中,输入“这些鲨鱼真可怕!”:

单击创建帖子。 您现在将在页面上看到新帖子:

![新帖子添加到页面](https://assets.digitalocean.com/articles/stimulus/stimulus_show_post.png .png)

如果您愿意,可以添加另一个新帖子。 这一次,输入“这些鲨鱼在电影中经常被歪曲”,然后点击创建帖子

为了测试 Show Older Posts 功能的功能,我们需要离开这个页面,因为我们的 Great White 目前没有任何比我们刚刚添加的更旧的帖子。

点击Back回到主页面,然后点击Show回到大白登陆页面:

点击 Show Older Posts 现在将显示您创建的帖子:

您现在可以通过单击 Upvote Post 按钮来为帖子投票:

同样,点击 Remove Post 将隐藏帖子:

您现在已经确认您有一个可用的 Rails 应用程序,该应用程序使用 Stimulus 来控制嵌套帖子资源在各个鲨鱼页面上的显示方式。 您可以将此作为未来开发和试验 Stimulus 的起点。

结论

Stimulus 代表了使用 rails-ujsJQuery 以及 React 和 Vue 等框架的可能替代方案。

正如介绍中所讨论的,当您需要直接使用服务器生成的 HTML 时,Stimulus 最有意义。 它是轻量级的,旨在使代码——尤其是 HTML——尽可能地自我解释。 如果您不需要在客户端管理状态,那么 Stimulus 可能是一个不错的选择。

如果您对如何在没有 Stimulus 集成的情况下创建嵌套资源感兴趣,可以参考 如何为 Ruby on Rails 应用程序创建嵌套资源

有关如何将 React 与 Rails 应用程序集成的更多信息,请参阅 如何使用 React 前端设置 Ruby on Rails 项目