Tech Hotoke Blog

IT観音とは私のことです。

Ruby on Railsに入門してみた【CRUDアプリケーション作成】

f:id:TechHotoke:20220207124417p:plain

目的

Ruby on Railsに触れる機会が出てきそうなので、その予習として行なったことをまとめること。

環境

前提

  • Homebrew3系がインストールされていること(2系はM1Macでは動かないです)
  • Rubyがインストールされていること(Macには最初から入ってる)
  • yarnがインストールされていること
  • Rialsに近しいMVCフレームワークを用いたweb開発経験がある方向け(説明を省いている箇所が多いです)

実装

プロジェクト作成

  • sudo npm install -g yarnを実行
  • 前回の記事で作成したプロジェクトのルートディレクトリ上でrails webpacker:installを実行

Webpackerとは Webpackerは、汎用的なwebpackビルドシステムのRailsラッパーであり、標準的なwebpackの設定と合理的なデフォルト設定を提供します。

参考:

https://railsguides.jp/webpacker.html#:~:text=Webpacker%E3%81%AF%E3%80%81%E6%B1%8E%E7%94%A8%E7%9A%84%E3%81%AA,%E8%A8%AD%E5%AE%9A%E3%82%92%E6%8F%90%E4%BE%9B%E3%81%97%E3%81%BE%E3%81%99%E3%80%82

Webpackを使用してRuby on Rails上でJavaScript開発をするために必要な一連のまとまりを、標準で実装することができるgemパッケージみたいです。

  • ここで下記のようなエラーが発生したため、対処
rails webpacker:install      
rails aborted!
Don't know how to build task 'webpacker:install'

一応下記のようなissueが上がっているようですが、今回のインストール方法とは異なるため、一度初めから別のプロジェクトを作成してみます。 参考:

rails new --webpack fails with "Don't know how to build task 'webpacker:install'" · Issue #61 · rails/webpacker · GitHub

  • rails -vでバージョン確認し、
  • rails new sample-crud_appでプロジェクト作成するという方法で行います
  • sudo npm install -g yarnを再度実行します
  • rails webpacker:installを再度実行します
  • 同様のエラーが確認されました
rails webpacker:install      
rails aborted!
Don't know how to build task 'webpacker:install'
  • ここで、時間をかけたくないので、記事の多いRails6系にダウングレードして検証してみます
  • gem uninstall railsgem uninstall railties -v '<バージョン>'を実行し、rails -vなどでcommand not foundが表示されることを確認
  • gem install -v 6.1.4.1 railsを実行(今参照している記事のバージョンに準拠)
  • rails -vでバージョンが表示されることを確認
  • rails new sample-crud_appを再実行して、プロジェクトを作成します。
  • プロジェクトのディレクトリに移動して rails webpacker:installを再度実行します
  • 今度は行けました(最新バージョンはやはり不用意に触らない方が安全という教訓を得ました)
success Saved 0 new dependencies.
✨  Done in 1.13s.
Webpacker successfully installed 🎉 🍰
  • 続いてrails webpacker:compileを実行します

  • rails sを実行して、http://127.0.0.1:3000にアクセスし、welcomeページが表示されることを確認できればOKです

コントローラーの雛形作成

  • rails g controller <コントローラー名>で作成
  • app/controller配下にコントローラーが作成されていることを確認
class QuestionsController < ApplicationController
    #質問一覧表示
    def index
    end

    #質問の詳細ページ
    def show
    end

    #質問の作成
    def new
    end

    #質問の登録
    def create
    end

    #質問の更新
    def update
    end

    #質問の編集
    def edit
    end

    #質問の削除
    def delete
    end

end
  • config/routes.rbに以下の記述を追加することで、ルーティングの自動生成を行ってくれる
resources :questions (←この名称は環境によって変わります)
  • rails routesコマンドで確認

モデルの作成

  • テーブル構成 f:id:TechHotoke:20220206173612p:plain
  • rails g model<モデル名> name:string title:string content:textで作成
  • app/models配下にモデルが作成されていることを確認
  • app/db/migrate配下にdbのスキーマが定義されていることを確認
class hogehoge < ActiveRecord::Migration[6.1]
  def change
    create_table :hoge do |t|
      t.string :name
      t.string :title
      t.text :content

      t.timestamps
    end
  end
end
  • rails db:migrateを実行し、app/db/schema.rbファイルに反映されていることを確認(rails dbconsoleコマンドでも確認できます)

ビューの作成

  • app/views/questions配下にnew.html.erbを作成(修飾子注意)
  • rails routesコマンドでnewへのパスを探して、アクセス。下記のようなページが表示されればOKです。 f:id:TechHotoke:20220206175856p:plain

CREATE機能の実装

  • Controllerに下記記述を追加
    #質問の作成
    def new
        @questions = Question.new
    end
  • Viewを下記のように編集
<h1>質問新規作成画面です</h1>
<%= form_with model: @quetion do |form| %>
    <div>
        <%= form.label :title%><br>
        <%= form.text_field :title %>
    </div>
     <div>
        <%= form.label :content%><br>
        <%= form.text_field :content %>
    </div>
     <div>
        <%= form.submit %><br>
    </div>
<% end %>
  • Form builderとかいう便利なものがあるんですね。

Action View フォームヘルパー Webアプリケーションのフォームは、ユーザー入力を扱うための重要なインターフェイスです。しかしフォームのマークアップは、フォームのコントロール命名法や大量の属性を扱わなければならず、作成もメンテナンスも退屈な作業になりがちです。そこでRailsでは、フォームのマークアップを生成するビューヘルパーを提供し、こうした煩雑な作業を行わずに済むようにしました。しかし現実にはさまざまなユースケースがあるため、開発者はこれらを実際に使う前に、これらのよく似たヘルパーメソッド群にどのような違いがあるのかをすべて把握しておく必要があります。

参考:

railsguides.jp

  • Controllerの登録用メソッドを下記のように編集します
   #質問の登録
    def create
        p params
    end
  • フォームに適当な値を入力して、登録ボタンを押下します

  • こんなエラーが出てきました。

f:id:TechHotoke:20220206235653p:plain

  • /questions/new createメソッドが呼ばれる場合のパスが/questionsになっていることが原因のRouting エラーみたいなので、routingの設定を明示すれば解決できるような気がします。(自動生成されているからこの辺りのエラーの原因とか、仕様とか把握しないと根本的な解決は厳しいですね。。。。)

  • config/routes.rbファイルに下記のように記載したらいけるようです

 post 'questions/new' => 'questions#create'
  • ターミナルに下記のようなパラーメータが表示されていればOKです
"title"=>"hoge", "name"=>"hoge", "content"=>"hoeg", "commit"=>"Save "
  • ただこれではフォームの改ざんが出来てしまうそうなので、ストロングパラーメータというものを使ってそれを防げるようなので実装します。コントローラに下記のように記載します。
  private
    def questions_params
        params.require(:question).permit(:title, :name, :content)
    end
  • また、下記のようにcreateメソッドを書き換えます
def create
        p questions_params
    end
  • 動作確認をしてみると、以下のようなエラーになっているので対処します。
param is missing or the value is empty: question
  • ここは単純にquestionが空のパラメータとなっているためエラーが発生しているっぽいので、下記のようにrequireメソッドを外してみます。
  private 
    def questions_params
        params.permit(:title, :name, :content)
    end
  • そうするとエラーが変わりました。
Processing by QuestionsController#create as HTML
  Parameters: {"authenticity_token"=>"[FILTERED]", "title"=>"sadf", "name"=>"sdfasdf", "content"=>"sdafsa", "commit"=>"Save "}
Unpermitted parameters: :authenticity_token, :commit
#<ActionController::Parameters {"title"=>"sadf", "name"=>"sdfasdf", "content"=>"sdafsa"} permitted: true>
No template found for QuestionsController#create, rendering head :no_content
  • ここでは:authenticity_token, :commitが許可されていないパラメータであること、createメソッドを読んだ際にレンダリングするテンプレートファイルが無いという二点が問題のようです。

  • まずはコントローラーにリダイレクト処理を実装して、View配下に適当なテンプレートファイルを作成します。

controller

  #質問の登録
    def create
        # モデルの初期化
        @question = Question.new(questions_params)
        # モデルをDBへ保存
        @question.save
        # showアクションへリダイレクト
        redirect_to @question
    end

show.html.erb

<h2>Show</h2>
  • フォームに値を入力して、submitすると、正常に遷移されて、コンソールを見ると正常にINSERTクエリが走っていることが分かります。(permit errorは何だったのか。。。)
  Question Create (1.7ms)  INSERT INTO "questions" ("name", "title", "content", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["name", "sdfdsa"], ["title", "safs"], ["content", "sfdsadf"], ["created_at", "2022-02-07 01:22:33.402158"], ["updated_at", "2022-02-07 01:22:33.402158"]]
  • 試しに名前入力フォームのname要素を下記のように書き換えてsubmitしてみると、コンソールに下記のようなエラーが出て入力が弾かれます。 f:id:TechHotoke:20220207001341j:plain
Unpermitted parameters: :authenticity_token, :hoge, :commit

READ機能の実装

  • showメソッドを下記のように編集
 #質問の詳細ページ
    def show
        @question = Question.find(params[:id])
        p @question
    
    end
  • 先ほど登録したデータのIDを引数に渡すことで下記のように一件データを取得することができます。
  Question Load (0.3ms)  SELECT "questions".* FROM "questions" WHERE "questions"."id" = ? LIMIT ?  [["id", 6], ["LIMIT", 1]]
  • show.html.erbファイルを下記のように編集し、画像のような画面が表示されればOKです。
<h2>Show</h2>
<p>
    Title: <br>
    <%= @question.title %>
</p>
<p>
    Name: <br>
    <%= @question.name %>
</p>
<p>
    Content: <br>
    <%= @question.content %>
</p>

f:id:TechHotoke:20220207103624p:plain

続いて、一覧表示機能も実装していきます。

  • indexメソッドを下記のように編集
   #質問一覧表示
    def index
        @questions = Question.all
        p @questions
    end
  • index.html.erbファイルを作成
<h2>質問一覧</h2>

<table>
<tr>
    <th>Title</th>
    <th>Name</th>
    <th>Content</th>
</tr>
<% @questions.each do |question| %>
    <tr>
        <td><%= question.id %></td>
        <td><%= question.title %></td>
        <td><%= question.name %></td>
        <td><%= question.content %></td>
        <td><%= link_to 'show' , question_path(question) %></td>
    </tr>
<% end %>
</table>
  • link_toヘルパーメソッドを利用することで、rails routesで表示されるメソッドをidをパスに含むことで実現できちゃうみたいです
 question GET    /questions/:id(.:format)                                                                          questions#show
                                         PATCH  /questions/:id(.:format)                                                                          questions#update
                                         PUT    /questions/:id(.:format)                                                                          questions#update
                                         DELETE /questions/:id(.:format)                                                                          questions#destroy
  • 下記のような一覧画面が表示されること・リンクが正常に動作することを確認できればOKです。 f:id:TechHotoke:20220207105633p:plain

f:id:TechHotoke:20220207103624p:plain

UPDATE機能の実装

  • updateメソッド、editメソッドを下記のように編集
    #質問の更新
    def update
        @question = Question.find(params[:id])
        if @question.update(questions_params)
            # 更新が成功したら詳細画面にリダイレクト
            redirect_to @question
        else
            # 更新に失敗したら編集画面を描画
            render 'edit'
        end
    end

    #質問の編集
    def edit
        @question = Question.find(params[:id])
    end
  • edit.html.erbファイルを作成
<h2>編集画面</h2>
<%= form_with model: @quetion do |form| %>
    <div>
        <%= form.label :title %><br>
        <%= form.text_field :title %>
    </div>
    <div>
        <%= form.label :name %><br>
        <%= form.text_field :name %>
    </div>
     <div>
        <%= form.label :content %><br>
        <%= form.text_area :content %>
    </div>
     <div>
        <%= form.submit %><br>
    </div>
<% end %>
  • index.html.erbファイルに編集画面へのリンクを作成
 <td><%= link_to '編集' , edit_question_path(question) %></td>
  • リンクを押下してみると、何やらフォームに値が反映されていませんでした。

  • コンソールは下記のような感じになっています。

Processing by QuestionsController#edit as HTML
  Parameters: {"id"=>"4"}
  Question Load (0.2ms)  SELECT "questions".* FROM "questions" WHERE "questions"."id" = ? LIMIT ?  [["id", 4], ["LIMIT", 1]]
  ↳ app/controllers/questions_controller.rb:44:in `edit'
#<Question id: 4, name: "sdfdsa", title: "safs", content: "sfdsadf", created_at: "2022-02-07 01:22:33.402158000 +0000", updated_at: "2022-02-07 01:22:33.402158000 +0000">
  Rendering layout layouts/application.html.erb
  Rendering questions/edit.html.erb within layouts/application
  Rendered questions/edit.html.erb within layouts/application (Duration: 5.2ms | Allocations: 3745)
[Webpacker] Everything's up-to-date. Nothing to do
  Rendered layout layouts/application.html.erb (Duration: 17.3ms | Allocations: 7656)
Completed 200 OK in 21ms (Views: 17.9ms | ActiveRecord: 0.2ms | Allocations: 8887)
  • Nothing to do Rendered layout layouts/application.html.erbとの事なので、確認しにいきます。

  • わからんので一旦保留。。。

  • フォームに値を入力して、submitしコンソールにクエリが表示されかつ正常に遷移が行われていればOKです。

  Question Update (1.3ms)  UPDATE "questions" SET "name" = ?, "title" = ?, "content" = ?, "updated_at" = ? WHERE "questions"."id" = ?  [["name", "更新"], ["title", "更新"], ["content", "更新"], ["updated_at", "2022-02-07 02:37:05.443853"], ["id", 1]]

f:id:TechHotoke:20220207113933p:plain

DELETE機能の実装

  • destroyメソッドを下記のように編集
    #質問の削除
    def destroy
        @question =  Question.find(params[:id])
        @question.destroy
        redirect_to questions_path
    end
  • index.html.erbファイルに削除のリンクを作成
        <td><%= link_to '削除' , question_path(question), method: :delete, data: { confirm: '本当に削除してもよろしいですか?' } %></td>
  • 削除リンクを押下して、確認ダイアログが表示→はいを選択

  • 画面レンダリング及び、下記のようなクエリが表示されていることを確認できれば完了です。

f:id:TechHotoke:20220207115020p:plain

  Question Destroy (0.6ms)  DELETE FROM "questions" WHERE "questions"."id" = ?  [["id", 2]]

駆け足となりましたが、今回はここまでで終了です。 お付き合い頂きありがとうございます。

ソースコードはこちら

github.com