Rails6.1ハンズオン(1)～掲示板基本機能実装

## ターゲット

- Rails、Rubyを全く知らない人
- 何らかのプログラミング言語でコードを書いたことがある人
- ある程度shellの操作に慣れている人
- macがなくてwindowsしかない人

## 目的

Rails6.1で基本的な機能を雑に触れながらそれっぽいアプリを作る

## やること

- Gitの設定
- ログインなしで書き込めるネット掲示板を作る
- ベースは[これ](https://qiita.com/Hirororocky/items/d5a035a2a1a7961cf12a)を使う + vscodeに拡張機能を入れて開発

## 1. 開発環境を整える

### 1-1. vscodeを整える

vscodeをインストールする。

[Visual Studio Code - コード エディター | Microsoft Azure](https://azure.microsoft.com/ja-jp/products/visual-studio-code/)

これでWSLに接続して開発できる（これ以降の拡張機能は必要に応じてWSL上にインストールされる。）

[Remote - WSL - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl)

日本語化

[Japanese Language Pack for Visual Studio Code - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=MS-CEINTL.vscode-language-pack-ja)

アイコンをわかりやすく表示

[Material Icon Theme - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=PKief.material-icon-theme)

Ruby関連

[Ruby - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=rebornix.Ruby)

補完とかしてくれる言語サーバ solargraphさん

```bash
# solargraphをインストール
gem install solargraph
```

[Ruby Solargraph - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=castwide.solargraph)

endを自動で入れてくれる

[endwise - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=kaiwood.endwise)

draw.ioを使いたい

[Draw.io Integration - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio)

### 1-2. Gitを整える

first commit

```bash
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
```

github SSH認証の準備

参考にさせていただくもの：
[WSL2 から起動した VSCode DevContainer に SSH agent で Git の鍵を渡す - Qiita](https://qiita.com/frozenbonito/items/5bb6f30f1e8df8e02317)

```bash
cd ~
ssh-keygen -t ed25519 -P ""
# 何も入れずにEnter
cd .ssh
more id_ed25519.pub
# 内容をコピー
```

[Build software better, together](https://github.com/settings/keys)

SSH and GPG keysで「New SSH Key」ボタンを押す。
わかりやすいTitle（WSL2とか）を入力、Keyにコピーした公開鍵を貼り付け、Add

~/.bashrcを開いて以下のコードを追加

```bash
if [ -z "$SSH_AUTH_SOCK" ]; then
   # Check for a currently running instance of the agent
   RUNNING_AGENT="`ps -ax | grep 'ssh-agent -s' | grep -v grep | wc -l | tr -d '[:space:]'`"
   if [ "$RUNNING_AGENT" = "0" ]; then
        # Launch a new instance of the agent
        ssh-agent -s &> $HOME/.ssh/ssh-agent
   fi
   eval `cat $HOME/.ssh/ssh-agent`
fi

ssh-add $HOME/.ssh/id_ed25519
```

github上にリポジトリを新規作成、（ここでは「Rails6.1_hands_on」という名前）

```bash
git remote add origin git@github.com:hirorocky/Rails6.1_hands_on.git
git push -u origin master
# Rails6.1だとまだ”master”
```

## 2. 掲示板を作る

### 2-0. Railsの基礎知識：MVCモデル

詳しくは「Rails MVC」で検索！
私は正しく理解している自信はないですが、こんな図を描いてみました↓

![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70400/13cc9a80-1cf9-7250-7ddc-2264c22543aa.png)
リクエストが来たら、Rails上のroutes.rbがコントローラーとアクション（＝メソッド）を決めて、コントローラー上のアクションでモデルからデータを取りながら、ビューを作って、その結果をブラウザに返すイメージです。

### 2-1. 設計

◆モデル図

（拡張機能により、〇〇.drawioというファイルを作ればvscode上でdraw.ioが使える！）
![Untitled 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70400/d2b9f87f-96a5-5dab-3bad-27240b7a123b.png)


◆ワイヤーフレーム（？詳しくない）

![Untitled 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70400/af2deb6f-3f42-f8c6-042c-1ed1addd151b.png)


４枚のページが必要そう。

- communities_controller#index：トップページ
- communities_controller#new→#create：コミュニティ作成ページ
- communities_controller#show：1つのコミュニティ＆コメント一覧
- comments_controller#new→#create：コメント投稿ページ

なぜこのコントローラー×アクションの組み合わせなのかは聞かないでください。

### 2-2. 実装

#### 2-2-1. モデル

DB上に各テーブルを作る。

' rails generate model <モデル名> <カラム名>:<型>...'

でモデルに関するファイルを自動で作ってくれる。

rails generateはrails gと省略できる。※以降gで書きます。

```bash
rails g model Community title:string owner_name:string
rails g model Comment author_name:string content:text community:references
```

db/migrateフォルダ内に、2つのファイル（マイグレーションファイル）ができる。

以下のコマンドでDBにテーブルを作成する。

```bash
# sqlite上にRails用DBを作成
rails db:setup
# DB上にマイグレーションファイルをもとにテーブルを作成
rails db:migrate
```

app/models/community.rbにて

```ruby
class Community < ApplicationRecord
  has_many :comments
end
```

app/models/comment.rbにて

```ruby
class Comment < ApplicationRecord
  belongs_to :community
end
```

こうするだけで、RailsのORMがいい感じにしてくれる。

※ちょっと試すにはターミナルでrails console(rails cでも可)コマンド。

```ruby
irb(main):001:0> commu = Community.create(title: 'コミュニティタイトルです', owner_name: 'たろう')
   (0.4ms)  SELECT sqlite_version(*)
  TRANSACTION (0.1ms)  begin transaction
  Community Create (0.5ms)  INSERT INTO "communities" ("title", "owner_name", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["title", "コミュニティタイトルです"], ["owner_name", "たろう"], ["created_at", "2021-02-13 02:21:28.386695"], ["updated_at", "2021-02-13 02:21:28.386695"]]
  TRANSACTION (5.0ms)  commit transaction
=> #<Community id: 1, title: "コミュニティタイトルです", owner_name: "たろう", created_at: "2021-02-13 02:21:28.386695000 +0000", updated_at: "2021-02-13 02:21:28.386695000 +0000">

irb(main):002:0> commu.title
=> "コミュニティタイトルです"

irb(main):003:0> commu.comments.create(author_name: 'じろう', content: "コメント\nああああ")
  TRANSACTION (0.1ms)  begin transaction
  Comment Create (0.5ms)  INSERT INTO "comments" ("author_name", "content", "community_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["author_name", "じろう"], ["content", "コメント\nああああ"], ["community_id", 1], ["created_at", "2021-02-13 02:26:21.103087"], ["updated_at", "2021-02-13 02:26:21.103087"]]
  TRANSACTION (5.1ms)  commit transaction
=> #<Comment id: 1, author_name: "じろう", content: "コメント\nああああ", community_id: 1, created_at: "2021-02-13 02:26:21.103087000 +0000", updated_at: "2021-02-13 02:26:21.103087000 +0000">

irb(main):004:0> commu.comments.create(author_name: 'じろう', content: "コメント2\nああああ")
  TRANSACTION (0.1ms)  begin transaction
  Comment Create (0.5ms)  INSERT INTO "comments" ("author_name", "content", "community_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["author_name", "じろう"], ["content", "コメント2\nああああ"], ["community_id", 1], ["created_at", "2021-02-13 02:26:35.918169"], ["updated_at", "2021-02-13 02:26:35.918169"]]
  TRANSACTION (7.1ms)  commit transaction
=> #<Comment id: 2, author_name: "じろう", content: "コメント2\nああああ", community_id: 1, created_at: "2021-02-13 02:26:35.918169000 +0000", updated_at: "2021-02-13 02:26:35.918169000 +0000">

irb(main):005:0> commu.comments
  Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."community_id" = ? /* loading for inspect */ LIMIT ?  [["community_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Comment id: 1, author_name: "じろう", content: "コメント\nああああ", community_id: 1, created_at: "2021-02-13 02:26:21.103087000 +0000", updated_at: "2021-02-13 02:26:21.103087000 +0000">, #<Comment id: 2, author_name: "じろう", content: "コメント2\nああああ", community_id: 1, created_at: "2021-02-13 02:26:35.918169000 +0000", updated_at: "2021-02-13 02:26:35.918169000 +0000">]>
```

ログを見ると、rubyのコードをSQLに変換して、DBを操作しているのがなんとなくわかると思います。

ここでは見ませんが、上記コードだけで、DBに1つのcommunityレコードと2つのcommentレコードができています。

#### 2-2-2. Communityのコントローラー・ビュー

◆コントローラー

```bash
rails g controller communities index new create show
```

設計段階で必要なコントローラーとビューがわかったので、

上記コマンドを入力。すると色々作成される（いらないものもできてしまう...※この辺は設定でいい感じにできますがここでは触れません）

app/controllers/communities_controller.rbのアクションの中にコードを入れる

```ruby
class CommunitiesController < ApplicationController
  def index
    @communities = Community.all
  end

  def new
    @community = Community.new
  end

  def create
    @community = Community.new(community_params)
    if @community.save
      redirect_to communities_path
    else
      render :new
    end
  end

  def show
    @community = Community.find(params[:id])
    @comments = @community.comments
  end

  private

  def community_params
    params.require(:community).permit(:title, :owner_name)
  end
end
```

`@hoge`はインスタンス変数で、この文脈で必要な知識としては、

「インスタンス変数はビューに渡せる」ということです。

community_paramsメソッドはStrong Parameterというやつです。セキュリティ的に必要なもので、詳しくはググってください。

◆ルーティング

```ruby
Rails.application.routes.draw do
  root to: 'communities#index'
  resources :communities, only: %i[index new create show]
end
```

自動生成されたものは全部削除、ルートをcommunities_controllerのindexアクションに割りあて、resourcesメソッドでcommunities_controller関連のルーティングを一気に作成。

```ruby
rails routes
```

で設定されているルーティングを見ることができ、またもっと見やすくしたいときは、

ブラウザ上で「http://[::1]:3000/rails/info/routes」で見れる。

◆ビュー

ビュー関連はapp/viewsの中に入っている。

自動で作成されたcreate.html.erbはいらないので削除。

~~早くhamlを使いたい。~~

一旦、見た目ガン無視の最低限の機能を実装する。

app/views/communities/index.html.erb

```html
<h1>掲示板</h1>

<%= link_to 'コミュニティ作成', new_community_path %>

<% @communities.each do |community| %>
  <div>
    <p><%= link_to community.title, community_path(community) %></p>
    <p><%= community.created_at %></p>
    <p><%= community.owner_name %></p>
  </div>
<% end %>
```

<% %>の中にrubyのコードを入れることができる。

<%= %>はrubyの評価結果をそのままhtmlに書き出す。

=かそうじゃないかは慣れだと思う。

app/views/communities/new.html.erb

```html
<h1>コミュニティ作成</h1>

<%= form_with model: @community do |form| %>
  <%= form.label :title %>
  <%= form.text_field :title %>
  <%= form.label :owner_name %>
  <%= form.text_field :owner_name %>

  <%= form.submit '作成' %>
<% end %>
```

※form_withは一時期デフォルトがremote: trueだったが、local: trueになった。

app/views/communities/show.html.erb

```html
<h1><%= @community.title %></h1>

<% @comments.each do |comment| %>
  <div>
    <p><%= comment.author_name %></p>
    <p><%= simple_format(comment.content) %></p>
    <p><%= comment.created_at %></p>
  </div>
<% end %>
```


#### 2-2-3. Commentのコントローラー・ビュー

◆コントローラー

```bash
rails g controller comments new create
```

app/controllers/comments_controller.rb

```ruby
class CommentsController < ApplicationController
  before_action :set_community
  
  def new
    @comment = @community.comments.new
  end

  def create
    @comment = @community.comments.new(comment_params)
    if @comment.save
      redirect_to community_path(@community)
    else
      render :new
    end
  end

  private

  def set_community
    @community = Community.find(params[:community_id])
  end

  def comment_params
    params.require(:comment).permit(:author_name, :content)
  end
end
```

◆ルーティング

```ruby
Rails.application.routes.draw do
  root to: 'communities#index'
  resources :communities, only: %i[index new create show] do
    resources :comments, only: %i[new create]
  end
end
```

rails routesコマンドでこうなる。

```ruby
Prefix                Verb URI Pattern                                       Controller#Action
                 root GET  /                                                 communities#index
   community_comments POST /communities/:community_id/comments(.:format)     comments#create
new_community_comment GET  /communities/:community_id/comments/new(.:format) comments#new
          communities GET  /communities(.:format)                            communities#index
                      POST /communities(.:format)                            communities#create
        new_community GET  /communities/new(.:format)                        communities#new
            community GET  /communities/:id(.:format)                        communities#show
```

◆ビュー

app/views/communities/show.html.erb

```ruby
<%= link_to '←戻る', communities_path %>
<h1><%= @community.title %></h1>

<%= link_to 'コメントする', new_community_comment_path(@community) %>

<% @comments.each do |comment| %>
  <div>
    <p><%= comment.author_name %></p>
    <p><%= simple_format(comment.content) %></p>
    <p><%= comment.created_at %></p>
  </div>
<% end %>
```

app/views/comments/new.html.erb

```ruby
<%= link_to '←戻る', community_path(@community) %>
<h1>コメントする</h1>

<%= form_with model: [@community, @comment] do |form| %>
  <%= form.label :author_name %>
  <%= form.text_field :author_name %>
  
  <%= form.label :content %>
  <%= form.text_area :content %>

  <%= form.submit '投稿' %>
<% end %>
```

app/views/comments/create.html.erbは削除。

#### 2-2-4. Gemfileの変更

左上に出ている表示が邪魔なので、rack-mini-profilerを削除。

デバッグ用にpry-railsを入れる。

```ruby
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '3.0.0'

gem 'rails', '~> 6.1.2', '>= 6.1.2.1'
gem 'sqlite3', '~> 1.4'
gem 'puma', '~> 5.0'
gem 'sass-rails', '>= 6'
gem 'webpacker', '~> 5.0'
gem 'turbolinks', '~> 5'
gem 'jbuilder', '~> 2.7'
# gem 'redis', '~> 4.0'
# gem 'bcrypt', '~> 3.1.7'
gem 'bootsnap', '>= 1.4.4', require: false

group :development, :test do
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'pry-rails'
end

group :development do
  gem 'web-console', '>= 4.1.0'
  # gem 'rack-mini-profiler', '~> 2.0'
  gem 'listen', '~> 3.3'
  gem 'spring'
end

gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
```

ひとまず完成。

[次回：Rails6.1ハンズオン(2)～見た目をそれっぽくする](https://qiita.com/Hirororocky/items/910464c0ae74c8f1ea12)