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
#エラー回避
#変更前
gem 'sqlite3'
#変更後
gem 'sqlite3', '~> 1.3.6'
$ bundle update
データベースを作成する
rails db:create
Bootstrapの導入
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
@import "bootstrap";
// 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
Rails.application.routes.draw do
root 'questions#index' #indexアクションのビューをrootに設定されるようする
resources :questions do #questionsコントローラーに関係するルーティングを自動で用意してくれる
resources :answers
end
end
##model
####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と入力
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
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アクションを作成
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を実行する
private
def question_params
byebug #byebugを入力
params.require(:question).permit(:name, :title, :content) #paramsにはフォームから送られてきたデータが入る
end
$ rails g controller answers edit #answersコントローラーのeditアクションを作成
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
<!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>
質問一覧ページの作成
<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>
####シードファイルを使った初期データの投入
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
新規質問投稿ページの作成
<div>
<div class="col-md-4 offset-md-4">
<h2 class="text-center">New question</h2>
<%= render 'form' %> <!--_form.html.erbを読み込む -->
</div>
</div>
####質問編集ページの作成
<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で読み込む
<%= 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 %>
####質問詳細ページの作成
<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 %>
回答編集ページの作成
<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>