LoginSignup
0
4

More than 3 years have passed since last update.

ミニQ & Aサービスの開発

Posted at

Udemyの教材でミニQ&Aサービスの開発について学んだので、備忘録として記録します。
教材:https://www.udemy.com/course/the-ultimate-ruby-on-rails-bootcamp/

Rubyのバージョン管理

今回はバージョン2.5.1で進めました。

ターミナル
$ rvm -v #rvmのバージョンを調べる
$ rvm 1.29.8 (1.29.8) by Michal Papis, Piotr Kuczynski, Wayne E. Seguin [https://rvm.io]

$ rvm list
=* ruby-2.5.1 [ x86_64 ]
   ruby-2.6.3 [ x86_64 ]

# => - current
# =* - current && default
#  * - default

$ rvm install 2.5.1 #バージョンを指定してインストールしたい場合

$ rvm use 2.5.1  #currentになる
$ rvm --default use 2.5.1  #current && defaultになる

Railsのインストール

バージョンを指定する場合の書き方。-Nはドキュメントをインストールしないという意味で時間短縮になる。

ターミナル
$ gem install rails -v 5.2.1 -N

Bundler RubyGemsを管理するツール

RubyGems Rubyで書かれたサードパーティ製のライブラリ(プログラムのまとまり)
ex) ユーザー認証機能、画像の管理機能、管理画面の機能、など
http://rubygems.org/

ミニQ and Aアプリ作成

ターミナル
$ rails _5.2.1_ new qanda
Gemfile
#エラー回避
#変更前
gem 'sqlite3'
#変更後
gem 'sqlite3', '~> 1.3.6'
ターミナル
$ bundle update

データベースを作成する

ターミナル
rails db:create

Bootstrapの導入

Gemfile
gem 'bootstrap', '~> 4.1.1'
gem 'jquery-rails', '~> 4.3.1' #bootstrapはjQueryに依存している
ターミナル
$ bundle install

stylesheetのファイル形式変更(CSS→Sass)

ターミナル
$ mv app/assets/stylesheets/application.css app/assets/stylesheets/application.scss
application.scss
@import "bootstrap";
application.js
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require jquery3 #ここを追記
//= require popper #ここを追記
//= require bootstrap-sprockets #ここを追記
//= require_tree .

route

ルーティングの設定

ターミナル
$ rails routes

Prefix Verb   URI Pattern                                                                              Controller#Action
                     root GET    /                                                                                        questions#index
         question_answers GET    /questions/:question_id/answers(.:format)                                                answers#index
                          POST   /questions/:question_id/answers(.:format)                                                answers#create
      new_question_answer GET    /questions/:question_id/answers/new(.:format)                                            answers#new
     edit_question_answer GET    /questions/:question_id/answers/:id/edit(.:format)                                       answers#edit
          question_answer GET    /questions/:question_id/answers/:id(.:format)                                            answers#show
                          PATCH  /questions/:question_id/answers/:id(.:format)                                            answers#update
                          PUT    /questions/:question_id/answers/:id(.:format)                                            answers#update
                          DELETE /questions/:question_id/answers/:id(.:format)                                            answers#destroy
                questions GET    /questions(.:format)                                                                     questions#index
                          POST   /questions(.:format)                                                                     questions#create
             new_question GET    /questions/new(.:format)                                                                 questions#new
            edit_question GET    /questions/:id/edit(.:format)                                                            questions#edit
                 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
       rails_service_blob GET    /rails/active_storage/blobs/:signed_id/*filename(.:format)                               active_storage/blobs#show
rails_blob_representation GET    /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
       rails_disk_service GET    /rails/active_storage/disk/:encoded_key/*filename(.:format)                              active_storage/disk#show
update_rails_disk_service PUT    /rails/active_storage/disk/:encoded_token(.:format)                                      active_storage/disk#update
     rails_direct_uploads POST   /rails/active_storage/direct_uploads(.:format)                                           active_storage/direct_uploads#create
config/routes.rb
Rails.application.routes.draw do  
  root 'questions#index' #indexアクションのビューをrootに設定されるようする
  resources :questions do #questionsコントローラーに関係するルーティングを自動で用意してくれる
    resources :answers
  end
end

model

table.jpg

Questionモデルの作成

ターミナル
$ rails g model Question name:string title:string content:text
$ rails db:migrate

Answerモデルの作成

ターミナル
$ rails g model Answer question:references name:string content:text #question:references→1対多の関係を定義出来る
$ rails db:migrate

データベース構造の確認

ターミナル
$ rails dbconsole
SQLite version 3.7.17 2013-05-20 00:56:22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .schema #.schemaと入力
CREATE TABLE "schema_migrations" ("version" varchar NOT NULL PRIMARY KEY);
CREATE TABLE "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
CREATE TABLE "questions" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "title" varchar, "content" text, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
CREATE TABLE "answers" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "question_id" integer, "name" varchar, "content" text, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL, CONSTRAINT "fk_rails_3d5ed4418f"
FOREIGN KEY ("question_id")
  REFERENCES "questions" ("id")
);
CREATE INDEX "index_answers_on_question_id" ON "answers" ("question_id");
sqlite> .q #.qと入力
question.rb
class Question < ApplicationRecord
  has_many :answers, dependent: :destroy #questionは複数のanswerを持っているという意味
  #dependent: :destroy→親のquestionが削除されたら、それに紐づくanswersも全て削除される

  validates :name, presence: true #データが未入力の時に保存しないようにする
  validates :title, presence: true
  validates :content, presence: true
end
answer.rb
class Answer < ApplicationRecord
  belongs_to :question #answerはquestionによって所有されていると言う関係

  validates :content, presence: true
  validates :name, presence: true
end

controller

ターミナル
$ rails g controller questions index show new edit #questionsコントローラーのindex,show,new,editアクションを作成
question_controller.rb
class QuestionsController < ApplicationController

  #コントローラーの各アクションが実行される前にset_questionを実行する
  before_action :set_question, only: [:show, :edit, :update, :destroy]


  #一覧を表示する
  def index
    #Questionの全データの配列を取得して、@questionsインスタンス変数へ代入する
    @questions = Question.all
  end

  #詳細を表示する
  def show
    # @question = Question.find(params[:id]) #リファクタリング
    @answer = Answer.new #Answerクラスのインスタンスを作り、@answerインスタンス変数へ代入
  end

  #新規作成
  def new
    @question = Question.new #Questionモデルの空のインスタンスを作り、@questionインスタンス変数へ代入
  end

  #保存
  def create
    @question = Question.new(question_params) #name, title, contentを@questionに代入
    if @question.save #保存されたら
      redirect_to root_path, notice: 'Success!' #「Success!」と表示してルートパスにリダイレクトする
    else #保存できない場合は
      flash[:alert] = "Save error!" #「Save error!」と表示して
      render :new #新規ページに戻る
    end
  end

  #編集
  def edit
    # @question = Question.find(params[:id]) #リファクタリング
  end

  #アップデート
  def update
    # @question = Question.find(params[:id]) #リファクタリング
    if @question.update(question_params) #name, title, contentをアップデートできたら
      redirect_to root_path, notice: 'Success!' #「Success!」と表示してルートパスにリダイレクトする
    else #アップデートできない場合は
      flash[:alert] = "Save error!" #「Save error!」と表示して
      render :edit #編集ページに戻る
    end
  end

  def destroy
    # @question = Question.find(params[:id]) #リファクタリング
    @question.destroy #削除する
    redirect_to root_path, notice: 'Success!' #「Success!」と表示してルートパスにリダイレクトする
  end

  private
  def set_question #@question = Question.find(params[:id])が重複しているので共通化する!
    @question = Question.find(params[:id])  #(params[:id])を取得して@questionに代入 
  end

  #ストロングパラメーター
  private #クラスの内部だけで使用する
  def question_params
    params.require(:question).permit(:name, :title, :content) #name,title,contentのみの値を@questionに渡す
  end
end

★paramsに入るデータをデバッグツールbyebugで確認する
formに値を入れて、ターミナルでparamsを入力するとフォームから送られてきた値を確認することができる
終了するには、quitを実行する

question_controller.rb
private
def question_params
  byebug #byebugを入力
  params.require(:question).permit(:name, :title, :content) #paramsにはフォームから送られてきたデータが入る
end
ターミナル
$ rails g controller answers edit  #answersコントローラーのeditアクションを作成
answers_controller.rb
class AnswersController < ApplicationController

  #保存
  def create
    #question_idを元にデータベースから該当のquestionを取得して変数に代入する
    @question = Question.find(params[:question_id]) 

    #Answerモデルの空のインスタンスを作り、@answerインスタンス変数へ代入
    @answer = Answer.new 

    #content,name,question_idを保存できたら
    if @answer.update(answer_params) 

      #「Success!」と表示して詳細ページににリダイレクトする
      redirect_to question_path(@question), notice: 'Success!'

    #アップデートできない場合は
    else 
      #「Invalid!」と表示して詳細ページににリダイレクトする
      redirect_to question_path(@question), alert: 'Invalid!' 
    end
  end

  def edit
    #question_idを元にデータベースから該当のquestionを取得して変数に代入する
    @question = Question.find(params[:question_id]) 

    #取得した@questionから編集したいanswerのidを取得して@answerに代入する
    @answer = @question.answers.find(params[:id]) 
  end

  def update
    #question_idを元にデータベースから該当のquestionを取得して変数に代入する
    @question = Question.find(params[:question_id])

    #取得した@questionからアップデートしたいanswerのidを取得して@answerに代入する
    @answer = @question.answers.find(params[:id])

    #アップデートできたら
    if @answer.update(answer_params) 

      #「Success!」と表示して詳細ページににリダイレクトする
      redirect_to question_path(@question), notice: 'Success!'

    #アップデートできない場合は
    else
      #「Invalid!」と表示して
      flash[:alert] = 'Invalid!' 
      #編集ページに戻る
      render :edit 
    end
  end

  def destroy
    #question_idを元にデータベースから該当のquestionを取得して変数に代入する
    @question = Question.find(params[:question_id]) 
    #取得した@questionから編集したいanswerのidを取得して@answerに代入する
    @answer = @question.answers.find(params[:id])
    #削除する
    @answer.destroy 
    #「Deleted!」と表示して詳細ページににリダイレクトする
    redirect_to question_path(@question), notice: 'Deleted!' 
  end

  #ストロングパラメーター
  private
  def answer_params
    #content,name,question_idのみの値を@answerに渡す
    params.require(:answer).permit(:content, :name, :question_id) 
  end
end

view

questions/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Qanda</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <div class="container">
      <% if flash[:notice] %>
        <p class="text-success"><%= flash[:notice] %></p>
      <% end %>
      <% if flash[:alert] %>
        <p class="text-danger"><%= flash[:alert] %></p>
      <% end %>
      <%= yield %>
    </div>
  </body>
</html>

質問一覧ページの作成

questions/index.html.erb
<h2>Questions</h2>
<div class="row">
  <div class="col-md-12">
    <table class="table table-striped">
      <thead class="thead-light">
        <tr>
          <th>ID</th>
          <th>Title</th>
          <th>Menu</th>
        </tr>
      </thead>
      <tbody>
        <% @questions.each do |question| %>
          <tr>
            <td><%= question.id %></td>
       <!-- 質問詳細画面へのリンク -->
            <!-- prefixに_pathをつけている -->
            <!-- (question)→:idとして読み込まれる -->
            <!-- question GET    /questions/:id(.:format)   questions#show -->
            <td><%= link_to question.title, question_path(question) %></td>

            <!-- edit_question GET    /questions/:id/edit(.:format)    questions#edit -->
            <td>[<%= link_to 'Edit', edit_question_path(question) %>]
            <!-- question DELETE /questions/:id(.:format)  questions#destroy -->
            [<%= link_to 'Delete', question_path(question), method: :delete, data:{ confirm: 'Are you sure?' }  %>]</td>
          </tr>
        <% end %>
      </tbody>
    </table>
    <div>
    <!--  new_question GET  /questions/new(.:format) questions#new -->
      <%= link_to 'New question', new_question_path %>
    </div>
  </div>
</div>

シードファイルを使った初期データの投入

db/seeds.rb
Question.create(id: 1, name: 'Test name 1', title: 'Test question 1', content: 'Test content 1')
Question.create(id: 2, name: 'Test name 2', title: 'Test question 2', content: 'Test content 2')
Question.create(id: 3, name: 'Test name 3', title: 'Test question 3', content: 'Test content 3')
rails db:seed

新規質問投稿ページの作成

questions/new.html.erb
<div>
  <div class="col-md-4 offset-md-4">
    <h2 class="text-center">New question</h2>
    <%= render 'form' %> <!--_form.html.erbを読み込む -->
  </div>
</div>

質問編集ページの作成

questions/edit.html.erb
<div>
  <div class="col-md-4 offset-md-4">
    <h2 class="text-center">Edit question</h2>
    <%= render 'form' %> <!--_form.html.erbを読み込む -->
  </div>
</div>

共通部分の作成

リファクタリング:プログラムを外部から見た時に動作を変えずに、ソースコードを整理すること
new.html.erbとedit.html.erbはほぼ同じ内容になっている
共通部分を_form.html.erbで作成し、new.html.erbとedit.html.erbで読み込む

questions/_form.html.erb
<%= form_with model: @question, local: true do |f| %>
  <div class="form-group">
    <label>Name</label>
    <%= f.text_field :name, class: "form-control" %>
  </div>

  <div class="form-group">
    <label>Title</label>
    <%= f.text_field :title, class: "form-control" %>
  </div>

  <div class="form-group">
    <label>Content</label>
    <%= f.text_area :content, class: "form-control" %>
  </div>

  <div class="text-center">
    <%= f.submit "Save", class: "btn btn-primary" %>
  </div>

<% end %>

質問詳細ページの作成

questions/show.html.erb
<div class="row">
  <div class="col-md-12">
    <h2><%= @question.title %></h2> <!-- 質問のタイトル -->
    <div>
      Content: <%= @question.content %> <!-- 質問の内容 -->
    </div>
    <div>
      Name: <%= @question.name %> <!-- 名前 -->
    </div>
    <hr>

    <div>
      <h3>Answers</h3>
      <table class="table table-striped">
        <% if @question.answers.any? %> <!-- 質問の答えがある場合は下記を表示する -->
          <thead class="thead-light">
            <tr>
              <td>Answer</td>
              <td>Name</td>
              <td>Menu</td>
            </tr>
          </thead>
          <tbody>
            <% @question.answers.each do |answer| %>
            <tr>
              <td>
                <%= answer.content %> <!-- 答えの内容 -->
              </td>
              <td>
                <%= answer.name %> <!-- 名前 -->
              </td>
              <td>
          <!-- edit_question_answer GET /questions/:question_id/answers/:id/edit(.:format)answers#edit -->
                [<%= link_to 'Edit', edit_question_answer_path(@question, answer) %>]

                <!-- question_answer DELETE /questions/:question_id/answers/:id(.:format) answers#destroy -->
                [<%= link_to 'Delete', question_answer_path(@question, answer), method: :delete, data: {confirm: 'Are you sure?'} %> ]
              </td>
            </tr>
            <% end %>
          </tbody>
        <% else %>
        <p>No answer yet.</p> <!-- 質問の答えがない場合に表示する -->
        <% end %>
      </table>
    </div>

    <h3>Post new answer</h3>
    <%= form_with model: [@question, @answer], local: true do |f| %>
      <%= f.hidden_field :question_id, { value: @question.id} %>
      <div class="form-group">
        <label>Name</label>
        <%= f.text_field :name, class: 'form-control' %>
      </div>
      <div class="form-group">
        <label>Content</label>
        <%= f.text_area :content, class: 'form-control' %>
      </div>
      <div class="text-center">
        <%= f.submit "Post", class: 'btn btn-primary' %>
      </div>
    <% end %>

    <div>
      <%= link_to '> Home', root_path %> <!-- ルートパスに飛ぶ -->
    </div>
  </div>
</div>

<% end %>

回答編集ページの作成

answers/edit.html.erb
<div>
  <h2>Updates answer</h2>
  <%= form_with model:[@question, @answer], local: true do |f| %>
    <div class="form-group">
      <div class="form-group">
        <label>Name</label>
        <%= f.text_field :name, class: "form-control" %>
      </div>
      <div class="form-group">
        <label>Content</label>
        <%= f.text_area :content, class: "form-control" %>
      </div>
      <div class="text-center">
        <%= f.submit "Update", class: "btn btn-primary" %>
      </div>
    </div>
  <% end %>
</div>
0
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
4