Ruby on Railsアプリケーション用のネストされたリソースを作成する方法

はじめに

Ruby on Railsは、Rubyで書かれたWebアプリケーションフレームワークであり、開発者にアプリケーション開発に対する意見を持ったアプローチを提供します。Railsを使用することで、開発者は次のようなものを得ます:

  • ルーティング、状態管理、アセット管理などの処理に関する規約。
  • A firm grounding in the model-view-controller (MCV) architectural pattern, which separates an application’s logic, located in models, from the presentation and routing of application information.

Railsアプリケーションに複雑さを加えるにつれて、おそらく複数のモデルを扱うことになります。これらのモデルは、アプリケーションのビジネスロジックを表し、データベースとのインターフェースを提供します。関連するモデルを追加すると、それらの間に意味のある関係を確立することになります。これにより、情報がアプリケーションのコントローラを介して中継される方法や、情報がユーザーにビューを通じてキャプチャされ、提示される方法が変わります。

このチュートリアルでは、ユーザーにサメに関する事実を提供する既存のRailsアプリケーションをベースに構築します。このアプリケーションにはすでにサメのデータを処理するためのモデルがありますが、個々のサメに関する投稿のネストされたリソースを追加します。これにより、ユーザーは個々のサメに関する幅広い考えや意見を展開することができます。

前提条件

このチュートリアルに従うには、以下が必要です:

  • A local machine or development server running Ubuntu 18.04. Your development machine should have a non-root user with administrative privileges and a firewall configured with ufw. For instructions on how to set this up, see our Initial Server Setup with Ubuntu 18.04 tutorial.
  • Node.jsとnpmがローカルマシンまたは開発サーバーにインストールされています。このチュートリアルでは、Node.jsバージョン10.16.3とnpmバージョン6.9.0を使用しています。Ubuntu 18.04にNode.jsとnpmをインストールする手順については、「PPAを使用したインストール」セクションの手順に従ってください。Ubuntu 18.04にNode.jsをインストールする方法に関する指示に従ってください。
  • ローカルマシンまたは開発サーバーにRuby、rbenv、およびRailsがインストールされています。Ubuntu 18.04にRuby on Railsをrbenvでインストールする手順は、1〜4のステップに従ってください。このチュートリアルでは、Rubyバージョン2.5.1、rbenvバージョン1.1.2、およびRailsバージョン5.2.3を使用しています。
  • SQLiteがインストールされ、基本的なシャーク情報アプリケーションが作成されています。Ruby on Railsアプリケーションを構築する方法指示に従ってください。

ステップ1 — ネストされたモデルのスキャフォールディング

アプリケーションは、Active Recordの関連付けを活用して、SharkモデルとPostモデルの関係を構築します:投稿は特定のサメに属し、各サメは複数の投稿を持つことができます。したがって、SharkモデルとPostモデルは、belongs_toおよびhas_manyの関連付けを介して関連付けられます。

このようにアプリケーションを構築するための最初のステップは、Postモデルと関連するリソースを作成することです。これを行うには、rails generate scaffoldコマンドを使用します。このコマンドにより、モデル、データベースのスキーマを変更するためのデータベースマイグレーション、コントローラ、標準の作成、読み取り、更新、削除(CRUD)操作を管理するための完全なビューのセット、パーシャル、ヘルパー、テストのテンプレートが生成されます。これらのリソースを変更する必要がありますが、scaffoldコマンドを使用すると、出発点として使用できる構造が生成されるため、時間とエネルギーを節約できます。

まず、前提条件で作成したRailsプロジェクトのsharkappディレクトリにいることを確認してください:

  1. cd sharkapp

次のコマンドを使用してPostリソースを作成します:

rails generate scaffold Post body:text shark:references

body:textを使用して、Railsにpostsデータベーステーブル内にbodyフィールドを含めるように指示しています。このテーブルはPostモデルにマップされます。また、:referencesキーワードを含めています。これにより、SharkモデルとPostモデルの間に関連を設定します。具体的には、sharksデータベース内の各サメエントリを表す外部キーpostsデータベースに追加されます。

コマンドを実行した後、Railsがアプリケーションに生成したリソースを確認する出力が表示されます。次に進む前に、モデルとデータベーステーブルの間に存在する関係を確認するために、データベースマイグレーションファイルを確認できます。以下のコマンドを使用してファイルの内容を確認します。ここで示されているタイムスタンプを、自分のマイグレーションファイルのものに置き換えてください:

  1. cat db/migrate/20190805132506_create_posts.rb

次の出力が表示されます:

Output
class 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
Output
class Post < ApplicationRecord belongs_to :shark end

belongs_to関連付けは、宣言されたモデルの単一のインスタンスが指定されたモデルの単一のインスタンスに属する関係を設定します。私たちのアプリケーションの場合、これは単一の投稿が単一のサメに属することを意味します。

この関係を設定するだけでなく、rails generate scaffoldコマンドは、How To Build a Ruby on Rails Applicationステップ3で行ったように、ポストのためのルートとビューも作成しました。

これは便利な始まりですが、モデルとルートの間の関係が望ましいように機能するようにするには、いくつかの追加のルーティングを設定し、SharkモデルのActive Record関連付けを強固にする必要があります。

ステップ2 — 親モデルのネストされたルートと関連付けの指定

Railsは、rails generate scaffoldコマンドの:referencesキーワードのおかげで、Postモデルにbelongs_to関連付けをすでに設定していますが、この関係が正しく機能するようにするには、Sharkモデルにhas_many関連付けを指定する必要があります。また、Railsが与えたデフォルトのルーティングを変更して、ポストリソースをシャークリソースの子にします。

has_many アソシエーションを Shark モデルに追加するには、nano またはお好きなエディターで app/models/shark.rb を開いてください。

  1. 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+XYENTER を押します。

次に、リソースフルなルート間の関係を変更するために config/routes.rb ファイルを開きます:

  1. 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'
  # このファイル内で利用可能なDSLの詳細については、http://guides.rubyonrails.org/routing.html を参照してください
end

編集が完了したら、ファイルを保存して閉じてください。

これらの変更を行ったら、postsコントローラを更新してください。

ステップ3 — Postsコントローラの更新

モデル間の関連付けにより、特定のsharksに関連付けられた新しい投稿インスタンスを作成するために使用できるメソッドが提供されます。 これらのメソッドを使用するには、postsコントローラにそれらを追加する必要があります。

postsコントローラファイルを開いてください:

  1. 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
    # 共通のセットアップや制約をアクション間で共有するためにコールバックを使用します。
    def set_post
      @post = Post.find(params[:id])
    end

    # 怖いインターネットからのパラメータを信頼しないでください、ホワイトリストだけを許可します。
    def post_params
      params.require(:post).permit(:body, :shark_id)
    end
end

このコントローラーのメソッドは、関連するPostクラスのインスタンスと一緒に動作します。たとえば、newメソッドはPostクラスの新しいインスタンスを作成し、indexメソッドはクラスのすべてのインスタンスを取得し、set_postメソッドはfindparamsを使用して特定の投稿をidで選択します。 ただし、投稿インスタンスを特定のサメインスタンスに関連付けたい場合は、このコードを変更する必要があります。現在、Postクラスは独立したエンティティとして機能しています。

私たちの変更は、2つのものを使用します:

  • belongs_toおよびhas_manyの関連付けをモデルに追加したときに利用可能になったメソッド。具体的には、Sharkモデルで定義したhas_many関連付けによってbuildメソッドへのアクセスが可能になりました。このメソッドを使用すると、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
  # コールバックを使用して、アクション間で共通のセットアップや制約を共有します。
. . . 

次に、ファイルの先頭に、既存のフィルターの前に対応するフィルターを追加してください:

~/sharkapp/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :get_shark

これにより、get_sharkがファイルで定義されている各アクションの前に実行されるようになります。

次に、この@sharkインスタンスを使用してindexメソッドを書き換えることができます。 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
. . . 

このメソッドは、get_sharkメソッドから特定のシャークインスタンスに関連付けられた投稿オブジェクトを作成します。

次に、newに最も密接に関連するメソッドであるcreateメソッドに取り組みます。 createメソッドは、ユーザーが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インスタンス変数を使用しています。この変数はどこから来るのでしょうか?

ファイルの上部にあるフィルターを見てみましょう。自動生成された2番目のbefore_actionフィルターは、答えを提供しています:

~/sharkapp/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :get_shark
  before_action :set_post, only: [:show, :edit, :update, :destroy]
  . . .

updateメソッド(showedit、およびdestroyと同様に)は、set_postメソッドから@post変数を取得します。このメソッドは、他のprivateメソッドとともにget_sharkメソッドの下にリストされています。現在のこのメソッドは次のようになっています:

~/sharkapp/controllers/posts_controller.rb
. . . 
private
. . . 
  def set_post
    @post = Post.find(params[:id])
  end
. . .

ファイルの他の箇所で使用しているメソッドに合わせて、このメソッドを変更する必要があります。ここではbuildメソッドを考慮してください。モデル間の関連とそれらの関連によって利用可能になるメソッド(buildなど)のおかげで、各ポストインスタンスは特定のサメに関連付けられたオブジェクトのコレクションの一部です。したがって、特定のポストをクエリする際には、特定のサメに関連付けられたポストのコレクションをクエリするのが理にかなっています。

set_postを以下のように更新してください:

~/sharkapp/controllers/posts_controller.rb
. . . 
private
. . . 
  def set_post
    @post = @shark.posts.find(params[:id])
  end
. . .

Postクラスの特定のインスタンスをidで検索する代わりに、特定のサメに関連付けられたポストのコレクションで一致するidを検索します。

その方法が更新されたので、updatedestroyメソッドを見てみることができます。

updateメソッドは、set_postから@postインスタンス変数を利用し、ユーザーがeditフォームに入力したpost_paramsと共に使用します。成功した場合は、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

コントローラーは、情報がビューテンプレートからデータベースへ、またはその逆にどのように渡されるかを管理します。当社のコントローラーは現在、投稿が特定のサメに関連付けられているSharkおよびPostモデルの関係を反映しています。ユーザーが特定のサメに関する投稿情報を入力および変更する場所であるビューテンプレートを変更できます。

ステップ4 — ビューの変更

ビューテンプレートの変更には、投稿に関連するテンプレートを変更するだけでなく、サメのshowビューも含まれます。特定のサメに関連付けられた投稿をユーザーが表示できるようにしたいからです。

まず、投稿の基本的なテンプレートであるformパーシャルを変更します。それを開きましょう。

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

form_with フォームヘルパーに post モデルだけでなく、shark モデルも渡すようにします。ただし、post を子リソースとして設定します。

ファイルの最初の行を以下のように変更して、shark と post リソースの関係を反映します:

~/sharkapp/views/posts/_form.html.erb
<%= form_with(model: [@shark, post], local: true) do |form| %>
. . . 

次に、ビュー内で関連する shark の 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 %>

編集が完了したら、ファイルを保存して閉じます。

次に、特定の shark に関連する投稿を表示する index ビューを開きます。

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

rails generate scaffold コマンドのおかげで、Rails はほとんどのテンプレートを生成しました。これには、各投稿の body フィールドとその関連する shark を表示するテーブルが含まれています。

ただし、このテンプレートも他の修正したコードと同様に、投稿を独立したエンティティとして扱っています。代わりに、モデル間の関連性やこれらの関連性によって提供されるコレクションやヘルパーメソッドを活用したいと考えています。

テーブルの本文で、以下の更新を行います:

まず、post.sharkpost.shark.name に更新し、テーブルに関連する shark の名前フィールドを含めます。これにより、shark オブジェクト自体の識別情報ではなく、関連する shark の名前フィールドが表示されます。

~/sharkapp/app/views/posts/index.html.erb
. . . 
  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.body %></td>
        <td><%= post.shark.name %></td>
. . . 

次に、Show リダイレクトを変更して、ユーザーが元のサメに戻る方法を求める可能性が高いため、関連するサメの show ビューに直接ユーザーをリダイレクトします。Railsはコントローラで設定したインスタンス変数 @shark をすべてのビューで利用できるようにするので、ここでそれを利用できます。リンクのテキストも 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を開いてください。

  1. 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テンプレートを開いてください。

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

Backパスに加えて、ネストされたリソースを反映するようにShowを更新します。ファイルの最後の2行を次のように変更してください。

~/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ビューを更新します。今すぐそのファイルを開いてください。

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

ここでの編集内容は、フォームにPostsセクションを追加し、ファイルの一番下にAdd Postリンクを追加することです。

以下は、特定のサメに関連付けられた投稿コレクション内の各インスタンスを反復処理し、各投稿の本文を出力する新しいセクションをFactsセクションの下に追加します。

次に、フォームの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 — バリデーションの追加とアプリケーションのテスト

ステップ5では、Ruby on Railsアプリケーションの構築方法において、Sharkモデルにバリデーションを追加し、sharksデータベースに保存されるデータの一貫性と整合性を確保しました。同様の手順を実行して、postsデータベースにも保証を確実にします。

Postモデルが定義されているファイルを開いてください:

  1. nano app/models/post.rb

ここでは、投稿が空白でないこと、および他のユーザーが投稿した可能性のあるコンテンツと重複しないことを確認したいと思います。これを実現するために、以下の行をファイルに追加してください:

~/sharkapp/app/models/post.rb
class Post < ApplicationRecord
  belongs_to :shark
  validates :body, presence: true, uniqueness: true
end

編集が完了したら、ファイルを保存して閉じてください。

この最後の変更を行ったことで、マイグレーションを実行してアプリケーションをテストする準備が整いました。

まず、マイグレーションを実行します:

  1. rails db:migrate

次に、サーバーを起動します。ローカルで作業している場合は、次のコマンドを実行してください:

  1. rails s

開発サーバーで作業している場合は、代わりに次のコマンドを実行してください:

  1. rails s --binding=your_server_ip

アプリケーションのルートに移動し、http://localhost:3000またはhttp://your_server_ip:3000にアクセスしてください。

前提としてのRailsプロジェクトチュートリアルでは、Great Whiteサメのエントリの追加と編集を進めました。それ以上のサメを追加していない場合、アプリケーションのランディングページは次のようになります:

表示をクリックしてください。次に、グレートホワイトの名前の横に表示されます。これにより、このサメのshowビューに移動します。サメの名前とその事実、そして内容のない投稿ヘッダーが表示されます。このフォームのこの部分を埋めるために投稿を追加しましょう。

投稿を追加をクリックしてください。これにより、投稿ヘッダーの下に新規投稿を選択できる投稿indexビューに移動します。

ステップ6Ruby on Railsアプリケーションの構築方法で設定した認証メカニズムのおかげで、新しいセッションを作成したかどうかに応じて、そのステップで作成したユーザー名とパスワードで認証を求められる場合があります。

新規投稿をクリックして、投稿のnewテンプレートに移動します。

本文フィールドに、「これらのサメは怖いです!」と入力してください。

投稿を作成をクリックします。これにより、このサメに属するすべての投稿のindexビューにリダイレクトされます。

投稿リソースが機能するようになったので、データ検証をテストして、データベースに保存されるのは必要なデータだけであることを確認できます。

indexビューから新規投稿をクリックします。新しいフォームの本文フィールドに再度「これらのサメは怖いです!」と入力してみてください。

投稿を作成をクリックしてください。以下のエラーが表示されます:

戻るをクリックして、メインの投稿ページに戻ります。

他の検証をテストするには、新規投稿を再度クリックしてください。投稿を空白のままにして投稿を作成をクリックします。以下のエラーが表示されます:

ネストされたリソースと検証が正常に機能するようになったので、これで追加の開発の出発点として使用できる動作するRailsアプリケーションが完成しました。

結論

Railsアプリケーションが準備できたので、スタイリングや他のフロントエンドコンポーネントの開発などに取り組むことができます。ルーティングやネストされたリソースについて詳しく学びたい場合は、Railsドキュメントが良い出発点です。

アプリケーションにフロントエンドフレームワークを統合する方法について詳しく学びたい場合は、Ruby on RailsプロジェクトにReactフロントエンドを設定する方法をご覧ください。

Source:
https://www.digitalocean.com/community/tutorials/how-to-create-nested-resources-for-a-ruby-on-rails-application