ruby on railsのチュートリアルをやってみる

# はじめに

とある事情によりruby on railsでアプリ開発することになったのでとりあえずチュートリアルやってみる。
今回やってみるチュートリアルはこちら:point_down:
https://guides.rubyonrails.org/getting_started.html
ruby on railsの公式ガイド(だと思う)です。

おそらく日本語訳とかそういうのはありふれてると思うので、rubyすら知らない段階から初めてつまづいたところや分かりにくかったところを残しておこうと思います。

# チュートリアルを進める

私がチュートリアルを始めたときの環境は以下です。
ここからrailsに必要なものを入れ始めました。

* Mac OS Mojave
* fish 2.7.1
* anyenv + ndenv (普段Node.js触ってるから)
* ruby (v2.3のどれか…忘れました。Mac買った最初から入ってたバージョンのまま一切触ってない。)

## 3. Creating a New Rails Project

チュートリアルに従うと、まずはrubyのバージョンを確認してrailsをインストールする…のですが。
「anyenv入れてるしrbenvとかあるらしいしrubyのバージョン管理はrbenvでしたほうが良いんじゃね？」
という謎の思いつきによりrbenvを導入。fish環境でanyenvを使うという本筋と違うところで時間食ったけどなんとか成功。
この部分は別途記事書いてもいいかも。
(`fish anyenv`とかでググるとよく出てくる「手動でスクリプト書き換える」がやりたくなくて頑張った。)

このあとは手順通りやれば特に難しいところもなし。
…のはずだった。

```shell:実行したコマンド
# 3.1 Installing Rails
$ ruby -v # rubyのバージョン確認
$ sqlite3 --version # sqlite3も必要なのでバージョン確認 (このマシンでは最初から入ってた)
$ gem install rails
$ rails --version # railsがインストールされていることを確認

# 3.2 Creating the Blog Application
$ rails new blog # 新しく　"blog"という名前のプロジェクトを作成
$ cd blog
```

## 4. Hello, Rails!

### 4.1 Starting up the Web Server

チュートリアルで毎度おなじみのHello Worldですね。
とりあえずサーバを起動します。

```shell:実行したコマンド
$ bin/rails server
Could not find public_suffix-3.0.3 in any of the sources
Run `bundle install` to install missing gems.
```

はい、コケました。チュートリアル通りやったやん！:cry:
エラーメッセージをよく見ると、`bundle install`してねって書いてあるのでやってみます。

```shell:実行したコマンド
$ bundle install  # 不足しているgemをインストールしてくれるらしい(?)
$ bin/rails server # 今度は起動した！
```

`bundle install`なんてチュートリアルになかった…なんて思って見直してみると、3.2節の説明にちょこっと書いてました。ショック。
たぶんruby触ってる人に取ってはこれくらいは常識なのでしょう。

あと気になったのは、`rails`コマンドをgemでインストールしたはずなのに、`bin/rails`からサーバ起動させてるところ。
これはきっとnode.jsで言うところの`npm install --global`で入れたコマンドがプロジェクトごとにバージョン違って困っちゃう問題と同じ意味合いなのだろう。

これと関連して、`gem install`でインストールするgemはrubyのバイナリと紐付いているようなので、rbenvでrubyのバージョン変えると「そんなgemはこのバージョンにはねぇ！」って怒られた。
gemをプロジェクトごとに管理することってできないのだろうか。
知ってる人教えてください。

### 4.2 Say "Hello", Rails

ここまでだとデフォルトのページが表示されただけなので、ちゃんと"Hello, Rails!"って出しましょう。

```shell:実行したコマンド
$ bin/rails generate controller Welcome index
```

これで新しくControllerというものを作った…らしい。
機能単位で分けたページ群というぐらいのくくりだろうか。
Welcomeコントローラのviewを編集するだけで特に難しいところもなし。

### 4.3 Setting the Application Home Page

サーバーのルートにアクセスしたときにHello, Railsが表示されるようにしてみましょう、ということらしい。

チュートリアルに従って`config/routes.rb`を編集してみる。

```ruby:config/routes.rb
Rails.application.routes.draw do
  get 'welcome/index'
 
  root 'welcome#index'
end
```

これで以下の2つのルートが設定された。

* `localhost:3000`にアクセスしたらHello,Railsが表示されるようになった。
* `localhost:3000/welcome/index`にアクセスしてもHello,Railsが表示されるようになった。

ここで2つ疑問。
1つ目は、一番最初に表示されていたページはどこにアクセスしても見られなくなった。
初期プロジェクトのどのファイルが初期ページと紐付いていたのでしょう…？

2つ目は、`routes.rb`の`get`と`root`でパスの表記が違うこと。
生成された`controllers/welcome_controller.rb`に`def index`という記述があるので、どちらもこいつの指定だと思うのだけれど…。

とりあえず疑問を残したまま次へ進む。

## 5. Getting Up and Running

次はリソース(?)を追加する。

```ruby:config/routes.rb
Rails.application.routes.draw do
  get 'welcome/index'
 
  resources :articles # ここを追加した
 
  root 'welcome#index'
end
```

なんとこれだけで`localhost:3000/articles`周りのREST Interfaceが用意されるらしい。(もちろん中身を実装しないとアクセスしてもエラーだけど)
なんて楽な。

さらに、今どんなルートが設定されているかも見れるらしい。

```shell:ルート確認
$ bin/rails routes
```

普段Node.js+expressでサーバ組んでる私にとっては結構衝撃的である。いつもルートが訳わからなくなるんだよねー。こりゃ便利。

### 5.1 Laying down the groundwork

articlesの中身を実装していくらしい。

```shell:実行したコマンド
$ bin/rails generate controller Articles
```

そういえばWelcome controllerを作ったときより引数が一つ少ない。
この場合、viewには何も生成されなくて、controllerの中身も空らしい。なるほど。

チュートリアルに従うと、まずはControllerに実装したいパス(今回は/articles/new)を定義して。

```ruby:app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def new
  end
end
```

次に、そのパスにアクセスされたときに表示するviewを作る。

```ruby:app/views/articles/new.html.erb(新規作成)
<h1>New Article</h1>
```

すると、エラーなく表示されるようになる。ふむふむ。

### 5.2 The first form

railsでフォームを扱うときには`form_with`というヘルパーメソッドが使えるらしい。
チュートリアルのものをそのまま使う。

```ruby:app/views/articles/new.html.erb
<h1>New Article</h1>

<%= form_with scope: :article, local: true do |form| %>
  <p>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </p>

  <p>
    <%= form.label :text %><br>
    <%= form.text_area :text %>
  </p>

  <p>
    <%= form.submit %>
  </p>
<% end %>
```

これでタイトルとテキストを入力するフォームと、submitボタンが配置された。
おもむろに適当なテキストを入力してsubmitすると、`No route matches [POST] "/articles/new"`だそうだ。
確かに、ブラウザからアクセスした(GETリクエスト)ときに表示させるページは作成したが、POSTしたときの動きは実装していない。

ただ、RESTの考えから行くと、ここでPOSTしてほしいのは`/articles/new`ではなく`/articles`のはずだ。
なぜならPOSTして作って欲しいオブジェクトは`article`であって`new`ではない。

というところの解決策が次に書いてあった。
どうやら`url:`というオプションを使うらしい。

```ruby:app/views/articles/new.html.erb(抜粋)
<%= form_with scope: :article, url: articles_path, local: true do |form| %>
```

この`articles_path`というのがどこから来たのか気にはなるが、きっと`config/routes.rb`に書いた`resources`あたりと紐付いているのだろう。

### 5.3 Creating articles

次にやるのは`/articles`にPOSTしたときの動作を実装すること。
まずはControllerにパスを登録する。

```ruby:app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def new
  end
 
  def create
  end
end
```

パスを登録する、と言ったが、`def create`となっているあたりどうも違うらしい。
ここではあくまでこのControllerが扱う動作を定義しているだけで、ここで定義した動作とサーバのパスをマッピングするのは`config/routes.rb`なのだろう。

次に、`create`で行う動作を記述する。

```ruby:app/controllers/articles_controller.rb(抜粋)
def create
    render plain: params[:article].inspect
end
```

`render`メソッドで行えるのは単純に画面にデータを貼り付けることらしい。なるほど。

### 5.4 Creating the Article model

ここまで散々`article`というものが出てきたが、そういえばそのデータモデルは何も作っていない。
ここでやっと作るらしい。

```shell:実行したコマンド
$ bin/rails generate model Article title:string text:text
```

ここで、実行すると何やら`db`というディレクトリの中にファイルが生まれたことに気づく。
どうやらrailsではアプリの中にDBまで含んであるようだ。ほほう。

### 5.5 Running a Migration

5.4が終わった段階で`/articles/new`にアクセスすると、何やら`Migration Error`というものが出てきた。
データモデルを変更したときは、DBのマイグレーションをしなければならないようだ。

```shell:実行したコマンド
$ bin/rails db:migrate
```

どうやらこのコマンドを実行することで、アプリに組み込んであるDBのテーブル構造を新しいものにマイグレーションするらしい。
自分でSQL書いてゴニョゴニョする必要がないのは確かに楽だと思う。

チュートリアルにも強調してあるが、上のコマンドでマイグレーションが実行されるのはあくまでdevelopment環境だけだそうだ。
production環境もマイグレーションする場合、`RAILS_ENV=production`というオプションを付ける必要があるらしい。

### 5.6 Saving data in the controller

データモデルを作成したので、`/articles/new`で表示するフォームから送信したデータを保存するように修正する。

```ruby:app/controllers/articles_controller.rb(抜粋)
def create
  @article = Article.new(params[:article])
 
  @article.save
  redirect_to @article
end
```

修正して実行したら通る…と思いきや。`ForbiddenAttributesError`だそうな。
どうやらrailsが持つ`strong_parameters`と呼ばれる機構で保護されているらしい。

これの許可を取るために下のようにも書けるそうな。

```ruby:app/controllers/articles_controller.rb(抜粋)
@article = Article.new(params.require(:article).permit(:title, :text))
```

ただ、操作ごとに毎回書くのも汚いし良くない、ということで次のようにprivateにしてやる方が良いらしい。

```ruby:app/controllers/articles_controller.rb(抜粋)
def create
  @article = Article.new(article_params)
 
  @article.save
  redirect_to @article
end
 
private
  def article_params
    params.require(:article).permit(:title, :text)
  end
```

### 5.7 Showing Articles

前節で`create`したときの動作を定義したが、よく見ると"記事を保存したあと、その記事自体にリダイレクトする"という動作になっている。
そのため、今度は`/articles/:id`へのGETリクエストを処理する部分を実装しなければならない。

```ruby:app/controllers/articles_controller.rb(抜粋)
def show
    @article = Article.find(params[:id])
end
```

`:id`というのは、`bin/rails routes`で確認できるパスのうち、`/:id`となっている部分と紐づくらしい。

ここでは`redirect`や`render`をしていないので、viewを用意してやる必要がある。

```ruby:app/views/articles/show.html.erb(新規作成)
<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
```

ここまで来ると、記事登録画面→API経由で記事を登録→登録した記事を表示、までできるようになる。

### 5.8 Listing all articles

続きは時間のあるときにやります…。

# まとめ

ちゃんと覚えればWebサービスをサクッと立ち上げたいときにはすごく楽かもしれない。
外部のサービスと連携したいときは？とか、まだ気になるところはたくさんあるけれども。