如何为RubyonRails应用程序创建嵌套资源

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

介绍

Ruby on Rails 是一个用 Ruby 编写的 Web 应用程序框架,它为开发人员提供了一种自以为是的应用程序开发方法。 使用 Rails 可为开发人员提供:

  • 处理路由、有状态数据和资产管理等事务的约定。
  • model-view-controller (MCV) 架构模式的坚实基础,它将位于模型中的应用程序逻辑与应用程序信息的表示和路由分开。

当您增加 Rails 应用程序的复杂性时,您可能会使用多个模型,这些模型代表您的应用程序的业务逻辑和与数据库的接口。 添加相关模型意味着在它们之间建立有意义的关系,然后影响信息如何通过应用程序的控制器传递,以及如何捕获信息并通过视图呈现给用户。

在本教程中,您将构建一个现有的 Rails 应用程序,该应用程序向用户提供有关鲨鱼的事实。 此应用程序已经有一个处理鲨鱼数据的模型,但是您将为有关单个鲨鱼的帖子添加一个嵌套资源。 这将允许用户对单个鲨鱼建立更广泛的想法和意见。

先决条件

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

  • 运行 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 中的步骤 1-4。 本教程使用 Ruby 2.5.1、rbenv 1.1.2 和 Rails 5.2.3
  • 按照 How To Build a Ruby on Rails Application 中的说明安装 SQLite,并创建了一个基本的鲨鱼信息应用程序。

第 1 步——搭建嵌套模型的脚手架

我们的应用程序将利用 Active Record associations 来建立 SharkPost 模型之间的关系:帖子将属于特定的鲨鱼,每个鲨鱼可以有多个帖子. 因此,我们的 SharkPost 模型将通过 belongs_tohas_many 关联相关联。

以这种方式构建应用程序的第一步是创建一个 Post 模型和相关资源。 为此,我们可以使用 rails generate scaffold 命令,它会给我们一个模型,一个 数据库迁移 来改变数据库模式,一个控制器,一套完整的视图来管理标准 [ X187X]创建、读取、更新和删除 (CRUD) 操作,以及用于部分、帮助程序和测试的模板。 我们需要修改这些资源,但是使用 scaffold 命令会节省我们一些时间和精力,因为它会生成一个我们可以用作起点的结构。

首先,确保您位于您在先决条件中创建的 Rails 项目的 sharkapp 目录中:

cd sharkapp

使用以下命令创建您的 Post 资源:

rails generate scaffold 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 generate scaffold 命令还为帖子创建路由和视图,就像在 How To Build a Ruby on Rails 的 Step 3 中为我们的鲨鱼资源所做的那样应用。

这是一个有用的开始,但我们需要配置一些额外的路由并巩固 Shark 模型的 Active Record 关联,以便我们的模型和路由之间的关系按预期工作。

第 2 步 — 为父模型指定嵌套路由和关联

Rails 已经在我们的 Post 模型中设置了 belongs_to 关联,这要归功于 rails generate scaffold 命令中的 :references 关键字,但是为了使这种关系发挥作用正确地,我们还需要在我们的 Shark 模型中指定一个 has_many 关联。 我们还需要更改 Rails 提供给我们的默认路由,以使 post 资源成为 Shark 资源的子资源。

要将 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 来执行此操作。

接下来,打开你的 config/routes.rb 文件来修改你的资源路由之间的关系:

nano config/routes.rb

目前,该文件如下所示:

~/sharkapp/config/routes.rb

Rails.application.routes.draw do
  resources :posts 
  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

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

完成这些更改后,您可以继续更新 posts 控制器。

第 3 步 — 更新 Posts 控制器

我们的模型之间的关联为我们提供了可以用来创建与特定鲨鱼相关联的新帖子实例的方法。 要使用这些方法,我们需要将它们添加到我们的帖子控制器中。

打开帖子控制器文件:

nano app/controllers/posts_controller.rb

目前,该文件如下所示:

~/sharkapp/controllers/posts_controller.rb

class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update, :destroy]

  # GET /posts
  # GET /posts.json
  def index
    @posts = Post.all
  end

  # GET /posts/1
  # GET /posts/1.json
  def show
  end

  # GET /posts/new
  def new
    @post = Post.new
  end

  # GET /posts/1/edit
  def edit
  end

  # POST /posts
  # POST /posts.json
  def create
    @post = Post.new(post_params)

    respond_to do |format|
      if @post.save
        format.html { redirect_to @post, notice: 'Post was successfully created.' }
        format.json { render :show, status: :created, location: @post }
      else
        format.html { render :new }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /posts/1
  # PATCH/PUT /posts/1.json
  def update
    respond_to do |format|
      if @post.update(post_params)
        format.html { redirect_to @post, notice: 'Post was successfully updated.' }
        format.json { render :show, status: :ok, location: @post }
      else
        format.html { render :edit }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /posts/1
  # DELETE /posts/1.json
  def destroy
    @post.destroy
    respond_to do |format|
      format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_post
      @post = Post.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def post_params
      params.require(:post).permit(:body, :shark_id)
    end
end

与我们的鲨鱼控制器一样,此控制器的方法与关联的 Post 类的实例一起使用。 例如,new 方法创建了 Post 类的新实例,index 方法抓取了该类的所有实例,set_post 方法使用findparams 通过 id 选择特定帖子。 但是,如果我们希望我们的 post 实例与特定的鲨鱼实例相关联,那么我们将需要修改此代码,因为 Post 类当前作为独立实体运行。

我们的修改将利用两件事:

  • 当我们将 belongs_tohas_many 关联添加到我们的模型时,我们可以使用这些方法。 具体来说,由于我们在 Shark 模型中定义的 has_many 关联,我们现在可以访问 构建方法 。 此方法将允许我们使用 posts 数据库中存在的 shark_id 外键创建与特定鲨鱼对象关联的帖子对象的集合。
  • 当我们创建嵌套的 posts 路由时可用的路由和路由助手。 有关在资源之间创建嵌套关系时可用的示例路由的完整列表,请参阅 Rails 文档。 现在,我们只要知道对于每条特定的鲨鱼——比如 sharks/1——都会有一条与该鲨鱼相关的帖子的关联路线:sharks/1/posts。 还会有路由助手,如 shark_posts_path(@shark)edit_sharks_posts_path(@shark) 引用这些嵌套路由。

在该文件中,我们将首先编写一个方法 get_shark,它将在控制器中的每个操作之前运行。 此方法将通过 shark_id 查找鲨鱼实例来创建本地 @shark 实例变量。 有了文件中的这个变量,我们就可以在其他方法中将帖子与特定的鲨鱼相关联。

在文件底部的其他 private 方法之上,添加以下方法:

~/sharkapp/controllers/posts_controller.rb

. . . 
private
  def get_shark
    @shark = Shark.find(params[:shark_id])
  end
  # Use callbacks to share common setup or constraints between actions.
. . . 

接下来,将相应的过滤器添加到文件的 top 中,在现有过滤器之前:

~/sharkapp/controllers/posts_controller.rb

class PostsController < ApplicationController
  before_action :get_shark

这将确保 get_shark 在文件中定义的每个操作之前运行。

接下来,您可以使用这个 @shark 实例来重写 index 方法。 我们希望此方法返回与特定鲨鱼实例关联的所有 post 实例,而不是获取 Post 类的所有实例。

index 方法修改为如下所示:

~/sharkapp/controllers/posts_controller.rb

. . .
  def index
    @posts = @shark.posts
  end
. . .

new 方法需要类似的修改,因为我们希望一个新的帖子实例与特定的鲨鱼相关联。 为此,我们可以使用 build 方法以及本地 @shark 实例变量。

new 方法更改为如下所示:

~/sharkapp/controllers/posts_controller.rb

. . . 
  def new
    @post = @shark.posts.build
  end
. . . 

此方法创建一个 post 对象,该对象与 get_shark 方法中的特定鲨鱼实例相关联。

接下来,我们将讨论与 new 最密切相关的方法:createcreate 方法做了两件事:它使用用户在 new 表单中输入的参数构建一个新的帖子实例,如果没有错误,它会保存该实例并使用路由助手将用户重定向到他们可以看到新帖子的地方。 如果出现错误,它会再次呈现 new 模板。

create 方法更新为如下所示:

~/sharkapp/controllers/posts_controller.rb

  def create
    @post = @shark.posts.build(post_params)

        respond_to do |format|
         if @post.save  
            format.html { redirect_to shark_posts_path(@shark), notice: 'Post was successfully created.' }
            format.json { render :show, status: :created, location: @post }
         else
            format.html { render :new }
            format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

接下来看一下update方法。 此方法使用 @post 实例变量,该变量未在方法本身中显式设置。 这个变量从何而来?

查看文件顶部的过滤器。 第二个自动生成的 before_action 过滤器提供了答案:

~/sharkapp/controllers/posts_controller.rb

class PostsController < ApplicationController
  before_action :get_shark
  before_action :set_post, only: [:show, :edit, :update, :destroy]
  . . .

update 方法(如 showeditdestroy)从 set_post 方法中获取 @post 变量. 该方法与我们的其他 private 方法一起列在 get_shark 方法下,目前如下所示:

~/sharkapp/controllers/posts_controller.rb

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

为了与我们在文件中其他地方使用的方法保持一致,我们需要修改此方法,以便 @post 引用与特定帖子相关联的帖子的 集合 中的特定实例鲨鱼。 请牢记 build 方法 - 由于我们的模型之间的关联,以及通过这些关联对我们可用的方法(如 build),我们的每个帖子实例都是与特定鲨鱼相关联的对象集合的一部分。 因此,在查询特定帖子时,我们将查询与特定鲨鱼关联的帖子集合是有道理的。

set_post 更新为如下所示:

~/sharkapp/controllers/posts_controller.rb

. . . 
private
. . . 
  def set_post
    @post = @shark.posts.find(params[:id])
  end
. . .

我们不是通过 id 查找整个 Post 类的特定实例,而是在与特定鲨鱼相关的帖子集合中搜索匹配的 id

更新该方法后,我们可以查看 updatedestroy 方法。

update 方法利用 set_post 中的 @post 实例变量,并与用户在 [ 中输入的 post_params 一起使用X150X] 形式。 在成功的情况下,我们希望 Rails 将用户返回到与特定鲨鱼相关的帖子的 index 视图。 如果出现错误,Rails 将再次渲染 edit 模板。

在这种情况下,我们需要做的唯一更改是对 redirect_to 语句进行处理,以处理成功的更新。 更新它以重定向到 shark_post_path(@shark),这将重定向到所选鲨鱼帖子的 index 视图:

~/sharkapp/controllers/posts_controller.rb

. . . 
  def update
    respond_to do |format|
      if @post.update(post_params)
        format.html { redirect_to shark_post_path(@shark), notice: 'Post was successfully updated.' }
        format.json { render :show, status: :ok, location: @post }
      else
        format.html { render :edit }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end
. . .

接下来,我们将对 destroy 方法进行类似的更改。 更新 redirect_to 方法,在成功的情况下将请求重定向到 shark_posts_path(@shark)

~/sharkapp/controllers/posts_controller.rb

. . . 
  def destroy
    @post.destroy
     respond_to do |format|
      format.html { redirect_to shark_posts_path(@shark), notice: 'Post was successfully destroyed.' }
      format.json { head :no_content }
    end
  end
. . .

这是我们要做的最后一个改变。 您现在有一个如下所示的帖子控制器文件:

~/sharkapp/controllers/posts_controller.rb

class PostsController < ApplicationController
  before_action :get_shark
  before_action :set_post, only: [:show, :edit, :update, :destroy]

  # GET /posts
  # GET /posts.json
  def index
    @posts = @shark.posts
  end

  # GET /posts/1
  # GET /posts/1.json
  def show
  end

  # GET /posts/new
  def new
    @post = @shark.posts.build
  end

  # GET /posts/1/edit
  def edit
  end

  # POST /posts
  # POST /posts.json
  def create
    @post = @shark.posts.build(post_params)

        respond_to do |format|
         if @post.save  
            format.html { redirect_to shark_posts_path(@shark), notice: 'Post was successfully created.' }
            format.json { render :show, status: :created, location: @post }
         else
            format.html { render :new }
            format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /posts/1
  # PATCH/PUT /posts/1.json
  def update
    respond_to do |format|
      if @post.update(post_params)
        format.html { redirect_to shark_post_path(@shark), notice: 'Post was successfully updated.' }
        format.json { render :show, status: :ok, location: @post }
      else
        format.html { render :edit }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /posts/1
  # DELETE /posts/1.json
  def destroy
    @post.destroy
    respond_to do |format|
      format.html { redirect_to shark_posts_path(@shark), notice: 'Post was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private

   def get_shark
     @shark = Shark.find(params[:shark_id])
   end
    # Use callbacks to share common setup or constraints between actions.
    def set_post
      @post = @shark.posts.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def post_params
      params.require(:post).permit(:body, :shark_id)
    end
end

控制器管理信息如何从视图模板传递到数据库,反之亦然。 我们的控制器现在反映了我们的 SharkPost 模型之间的关系,其中帖子与特定的鲨鱼相关联。 我们可以继续自己修改视图模板,用户将在其中传入并修改有关特定鲨鱼的帖子信息。

第四步——修改视图

我们的视图模板修订将涉及更改与帖子相关的模板,并修改我们的鲨鱼 show 视图,因为我们希望用户看到与特定鲨鱼相关的帖子。

让我们从帖子的基础模板开始:跨多个帖子模板重用的 form 部分。 现在打开该表格:

nano app/views/posts/_form.html.erb

我们将传递 sharkpost 模型,而不是仅将 post 模型传递给 form_with 表单助手,以及 post设置为子资源。

把文件的第一行改成这样,体现了我们的shark和post资源的关系:

~/sharkapp/views/posts/_form.html.erb

<%= form_with(model: [@shark, post], local: true) do |form| %>
. . . 

接下来,删除列出相关鲨鱼的shark_id的部分,因为这不是视图中的重要信息。

完成的表单,包括我们对第一行的编辑,没有删除 shark_id 部分,将如下所示:

~/sharkapp/views/posts/_form.html.erb

<%= form_with(model: [@shark, post], local: true) do |form| %>
  <% if post.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:</h2>

      <ul>
      <% post.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :body %>
    <%= form.text_area :body %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

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

接下来,打开 index 视图,它将显示与特定鲨鱼相关的帖子:

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

多亏了 rails generate scaffold 命令,Rails 生成了模板的大部分内容,并附有一个表格,其中显示了每个帖子的 body 字段及其关联的 shark

然而,就像我们已经修改过的其他代码一样,这个模板将帖子视为独立的实体,当我们想利用我们的模型与这些关联给我们的集合和辅助方法之间的关联时。

在表的正文中,进行以下更新:

首先,将 post.shark 更新为 post.shark.name,以便该表将包含关联鲨鱼的名称字段,而不是标识有关鲨鱼对象本身的信息:

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

. . . 
  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.body %></td>
        <td><%= post.shark.name %></td>
. . . 

接下来,更改 Show 重定向以将用户定向到关联鲨鱼的 show 视图,因为他们很可能想要一种导航回原始鲨鱼的方法。 我们可以使用在控制器中设置的 @shark 实例变量,因为 Rails 使控制器中创建的实例变量可用于所有视图。 我们还将链接的文本从 Show 更改为 Show Shark,以便用户更好地了解其功能。

将此行更新为以下内容:

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

. . . 
  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.body %></td>
        <td><%= post.shark.name %></td>
        <td><%= link_to 'Show Shark', [@shark] %></td>

在下一行中,我们希望确保用户在编辑帖子时被路由到正确的嵌套路径。 这意味着用户不会被定向到 posts/post_id/edit,而是被定向到 sharks/shark_id/posts/post_id/edit。 为此,我们将使用 shark_post_path 路由助手和我们的模型,Rails 会将其视为 URL。 我们还将更新链接文本以使其功能更清晰。

Edit 行更新为如下所示:

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

. . . 
  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.body %></td>
        <td><%= post.shark.name %></td>
        <td><%= link_to 'Show Shark', [@shark] %></td>
        <td><%= link_to 'Edit Post', edit_shark_post_path(@shark, post) %></td>

接下来,让我们为 Destroy 链接添加类似的更改,更新其字符串中的函数,并添加我们的 sharkpost 资源:

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

. . . 
  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.body %></td>
        <td><%= post.shark.name %></td>
        <td><%= link_to 'Show Shark', [@shark] %></td>
        <td><%= link_to 'Edit Post', edit_shark_post_path(@shark, post) %></td>
        <td><%= link_to 'Destroy Post', [@shark, post], method: :delete, data: { confirm: 'Are you sure?' } %></td>

最后,在表单的底部,我们要更新 New Post 路径,以便在用户想要创建新帖子时将其带到适当的嵌套路径。 更新文件的最后一行以使用 new_shark_post_path(@shark) 路由助手:

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

. . . 
<%= link_to 'New Post', new_shark_post_path(@shark) %>

完成的文件将如下所示:

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

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

<h1>Posts</h1>

<table>
  <thead>
    <tr>
      <th>Body</th>
      <th>Shark</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.body %></td>
        <td><%= post.shark.name %></td>
        <td><%= link_to 'Show Shark', [@shark] %></td>
        <td><%= link_to 'Edit Post', edit_shark_post_path(@shark, post) %></td>
        <td><%= link_to 'Destroy Post', [@shark, post], method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Post', new_shark_post_path(@shark) %>

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

我们将对发布视图进行的其他编辑不会那么多,因为我们的其他视图使用我们已经编辑过的 form 部分。 但是,我们将希望更新其他帖子模板中的 link_to 引用,以反映我们对 form 部分所做的更改。

打开app/views/posts/new.html.erb

nano app/views/posts/new.html.erb

更新文件底部的 link_to 引用以使用 shark_posts_path(@shark) 帮助器:

~/sharkapp/app/views/posts/new.html.erb

. . . 
<%= link_to 'Back', shark_posts_path(@shark) %>

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

接下来,打开edit模板:

nano app/views/posts/edit.html.erb

除了 Back 路径之外,我们还将更新 Show 以反映我们的嵌套资源。 将文件的最后两行更改为如下所示:

~/sharkapp/app/views/posts/edit.html.erb

. . . 
<%= link_to 'Show', [@shark, @post] %> |
<%= link_to 'Back', shark_posts_path(@shark) %>

保存并关闭文件。

接下来,打开show模板:

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

对文件底部的 EditBack 路径进行以下编辑:

~/sharkapp/app/views/posts/edit.html.erb

. . .
<%= link_to 'Edit', edit_shark_post_path(@shark, @post) %> |
<%= link_to 'Back', shark_posts_path(@shark) %>

完成后保存并关闭文件。

作为最后一步,我们将要更新鲨鱼的 show 视图,以便单个鲨鱼可以看到帖子。 现在打开该文件:

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

我们在此处的编辑将包括在表单中添加 Posts 部分,并在文件底部添加 Add Post 链接。

在给定鲨鱼的 Facts 下方,我们将添加一个新部分,该部分迭代与该鲨鱼关联的帖子集合中的每个实例,输出每个帖子的 body

在表单的 Facts 部分下方以及文件底部的重定向上方添加以下代码:

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

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

<h2>Posts</h2>
<% for post in @shark.posts %>
    <ul>
      <li><%= post.body %></li>
  </ul>
<% end %>

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

接下来,添加一个新的重定向以允许用户为这个特定的鲨鱼添加一个新帖子:

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

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

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

您现在已经对应用程序的模型、控制器和视图进行了更改,以确保帖子始终与特定的鲨鱼相关联。 作为最后一步,我们可以向我们的 Post 模型添加一些验证,以保证保存到数据库中的数据的一致性。

第 5 步 - 添加验证和测试应用程序

How To Build a Ruby on Rails ApplicationStep 5 中,您向 Shark 模型添加了验证,以确保保存到sharks 数据库。 我们现在将采取类似的步骤来确保 posts 数据库的保证。

打开定义 Post 模型的文件:

nano app/models/post.rb

在这里,我们要确保帖子不是空白的,并且不会重复其他用户可能发布的内容。 为此,将以下行添加到文件中:

~/sharkapp/app/models/post.rb

class Post < ApplicationRecord
  belongs_to :shark
  validates :body, presence: true, uniqueness: true
end

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

完成最后一项更改后,您就可以运行迁移并测试应用程序了。

首先,运行你的迁移:

rails db:migrate

接下来,启动您的服务器。 如果你在本地工作,你可以通过运行:

rails s

如果您在开发服务器上工作,请运行以下命令:

rails s --binding=your_server_ip

http://localhost:3000http://your_server_ip:3000 导航到应用程序的根目录。

必备的 Rails 项目教程指导您添加和编辑 Great White 鲨鱼条目。 如果您没有添加任何更多鲨鱼,应用程序登录页面将如下所示:

单击 Great White 名称旁边的 Show。 这将带您进入这条鲨鱼的 show 视图。 您将看到鲨鱼的名称及其事实,以及没有内容的 Posts 标头。 让我们添加一个帖子来填充表单的这一部分。

单击 Posts 标题下方的 Add Post。 这将带您进入帖子 index 视图,您将有机会选择 New Post

由于您在 How To Build a Ruby on Rails ApplicationStep 6 中设置的身份验证机制,可能会要求您使用在该步骤中创建的用户名和密码进行身份验证,取决于您是否创建了新会话。

点击 New Post,这将带您进入您的帖子 new 模板:

Body 字段中,输入“这些鲨鱼真可怕!”

单击创建帖子。 您将被重定向到属于此鲨鱼的所有帖子的 index 视图:

随着我们的帖子资源的工作,我们现在可以测试我们的数据验证,以确保只有所需的数据被保存到数据库中。

index 视图中,单击 New Post。 在新表单的 Body 字段中,尝试输入“这些鲨鱼真可怕!” 再次:

单击创建帖子。 您将看到以下错误:

点击 Back 返回主帖子页面。

要测试我们的其他验证,请再次单击 New Post。 将帖子留空,然后单击创建帖子。 您将看到以下错误:

随着您的嵌套资源和验证正常工作,您现在拥有一个工作的 Rails 应用程序,您可以将其用作进一步开发的起点。

结论

有了 Rails 应用程序,您现在可以处理样式和开发其他前端组件之类的事情。 如果您想了解更多关于路由和嵌套资源的信息,Rails 文档 是一个很好的起点。

要了解有关将前端框架与您的应用程序集成的更多信息,请查看 如何使用 React 前端设置 Ruby on Rails 项目