方針

  • Rails のサーバが全てのフロント用のファイルを返す旧式の開発を一旦学ぶ => これによって既存のモノシリックな Rails アプリケーションのコードを読み、フロントを独立させる大型工事をするスキルを習得する
  • 後に Rails をシンプルな API 用サーバとするための技法を学ぶ => これによって既存の資産を残しながらフロント開発のしやすい開発体制へ移行させるスキルを習得する
  • さらに、Rails を TypeScript + Node や Scala, Kotlin などの言語に最終的には置き換えても良い
  • 何れにせよ、既存の Rails コードを読み、仕様をコードから読み解き、換骨奪胎し、モダンな開発環境に作り変えるスキルを習得する

Rails は Ruby のフレームワーク

Rails は Ruby のフレームワークで、React と JavaScript の関係に近い。(React は一般的にはライブラリと呼ぶべきだが)

Ruby 及び Rails の開発環境と JavaScript 開発の比較

フロントエンドエンジニアにわかりやすい対比で Ruby 及び Rails の開発ツールと開発環境の用語を説明する。

npm ライブラリ = gem

JavaScript において package.json で管理するライブラリに対応するのが、Ruby の Gem。Rails は Ruby の Gem の一つである。

package.json = Gemfile(拡張子無し)

Ruby において、プロジェクトで使用する Gem ファイルの管理は Gemfile が担当する。丁度 JavaScript の package.json に相当する。

npm = bundler

Gemfile(package.json) から gem(npm library) をインストールするために使うのが bundler(npm) である。gem(npm library) の実行にも bundler(npm) 経由でおこなわれる。

ただし Rails のシンタックスは Ruby とはかなりかけ離れている

Rails のシンタックスは Ruby とは(一見)かなりかけ離れている。もちろん Rails が実行しているのは Ruby である。しかし、Rails で使用するコードや各種コマンドは、その結果実行される内容を高度に隠蔽しており、コードをそのものを読んでも実行内容を理解することはほぼ不可能である。

学習においては、その隠蔽された実行内容が何を意味しているのか、これによって生成されたコードが一体何を意味するのか、Rails のドキュメント及びガイドラインと照らし合わせながら特定していく必要がある。(少なくとも序盤においては)Rails は Ruby ではないと考えた方がよい。

Rails を始める

Ruby と Rails のインストールについては以下資料を参照。注意点は rbenv インストール後、bash_profile 等に echo 'eval "$(rbenv init -)"' を必ず書くこと。nvm と違って勝手にやってはくれない。

Rails Girls の資料

  • ruby の管理ツールとして rbenv を使う(nvm に相当)
  • rbenv は homebrew からインストールできる
  • rbenv から ruby をインストール(nvm で node をインストール)
  • gem install bundler で bundler をインストール
  • gem install rails -v 指定のバージョン(グローバルに express-generator をインストールする感じ)
  • Rails が入ったら以下コマンドで Rails プロジェクトを作成する(グローバルの express-generator を使って必要なファイルを一式作る感じ)

Rails App の雛形を作り、開発用ウェブサーバーを立てる

rails app を作る
## 様々なファイルが生成され、また自動的に bundle install で必要な gem もインストールされる
rails new 

## rails s でもいい。省略形
## 開発用の puma という Web Server が起動する
## localhost:3000 にアクセスすればイニシャルページがみれる
rails server

この時点でかなりの隠蔽がある

localhost:3000 にアクセスした際に表示される HTML までのルートが既に隠蔽されている。何が起きているか、以下説明する。

まず /config/routes.rb がルーティングを決める

localhost:3000 にアクセスした際に、そのパスによって実行内容が分岐される。つまりルーティング機能だが、それは /config/routes.rb で定義されている。(おそらくこれを Ruby のウェンブサーバー用 Gem である Puma が読み込んでいるのではないか)

最初は以下のように何もルーティングがされていない。(にも関わらず特定の HTML につながっているのは、おそらくデフォルトの設定がそうなっている)

/config/routes.rb
Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

以下のように変更する。参考資料

これによって localhost:3000(つまり特定のドメインのルートパス)は特定の Controller の特定の Action を実行するように定義される。

/config/routes.rb
Rails.application.routes.draw do
  root 'application#hello'
  # rootPath へのルーティングは、ApplicationController の hello という Action に結びつける
end

つまりルーティングは、Controller へのルーティングを定義している

つまりルーティングは、Controller へのルーティングを定義している。

Controller の定義は /app/controllers に

/config/routes.rb の定義によって結び付けられる Controller の定義は、/app/controllers 配下の .rb ファイルに定義されている。

例えば、ルーティングで application#hello と指示した場合には(恐らく)自動的に /app/controllers/application_controller.rb の class ApplicationController < ActionController::Basehello メソッドに紐づけられる。

つまり routes.rb のおける application というコントローラーの指定は、applocation + _controller に自動的に結び付けられる。このファイルではさらにファイル名を大文字キャメルケースにした ApplicationController というクラスを(恐らく)持つ必要がある。

ファイルに変更を加える

以下のように変更を加え、ルートパスにアクセスすると hello, world! と表示される。つまりルーティングで指定した ApplicationController の hello というメソッド(アクション)が実行される。

  • Ruby はインデントでブロックを形成する。(.sass と同じ)
  • def methodName ~ end でメソッドを定義する。
  • render html: は恐らく HTML 要素を返り値にもつメソッドを実行している。
/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  def hello
    render html: "hello, world!"
  end
end

routes でコントローラーを指定すると、暗黙的にファイル名、クラス名、メソッド名も指定していることになる(恐らく)

普通 JavaScript であれば、import をしたクラスなりコンポーネントなりに明示的に紐づけるが、Rails の場合はそれが隠蔽され暗黙的に紐づけられる。この点が Rails 全般の特徴なので注意されたい。

最初の魔法を唱える

以下の呪文を詠唱すると、膨大なファイルが作成される。またファイルに変更が加えられる。

最初の魔法
bin/rails generate scaffold User name:string email:string
  • DB に users というテーブル名で name と email というカラムを持つものを作成するための 「migration 用ファイル」
  • このマイグレーション用ファイルは bin/rails db:migrate で実行できる
  • (実はすでに DB 設定用のファイルは rails new の際暗黙的的に作成されていた)
  • ルーティングの変更(UsersController へのルーティングが追加される)
  • コントローラーの作成(UsersController が作成される)
  • 各ルーティングで表示する Web ページ用のリソース

これをブラウザから確認するためにまずは bin/rails db:migrate を実行して DB にテーブルを作成しよう。その後以下の説明にしたがって IDE とブラウザから確認してほしい。

/config/routes.rb に加えられた変更

ルーティング設定に一行追加される。

routes.rb
Rails.application.routes.draw do
  resources :users # 追加される
  root 'application#hello'
end

resources はさらなる魔術であって、以下のようなルーティングを自動生成する。つまり UsersController へのルーティングを複数の action を指定して暗黙的に行う。いわゆる CRUD に必要なものが全て暗黙的にルーティングされる。

Screen Shot 2019-05-19 at 19.32.22

さらに、なんと UsersController も先ほどの詠唱 generate scaffold で完成していた。

/app/controllers/users_controller.rb の作成

以下のようなメソッド(とそれが記述される新たなファイル users_controller.rb)がすでにできている。(同時に対応する HTML をレンダリングするファイルも作成されているので)試しに以下の URL にアクセスして結果をみてほしい。

  • /users => DB からユーザー一覧の情報を取得して対応する HTML レンダリングメソッドで出力されたページがみれる。
  • /users/12 => id:12 のユーザーが表示される。まだこのユーザーがいなければエラー。
  • /users/new => ユーザー追加の画面が見れる。

コントローラーの内部はスカスカである。もちろんここも魔法が支配している。これについては後述する。

/app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  # GET /users
  # GET /users.json
  def index
    @users = User.all
  end

  # GET /users/1
  # GET /users/1.json
  def show
  end

  # GET /users/new
  def new
    @user = User.new
  end

  # GET /users/1/edit
  def edit
  end

  # POST /users
  # POST /users.json
  def create
    @user = User.new(user_params)

    respond_to do |format|
      if @user.save
        format.html { redirect_to @user, notice: 'User was successfully created.' }
        format.json { render :show, status: :created, location: @user }
      else
        format.html { render :new }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /users/1
  # PATCH/PUT /users/1.json
  def update
    respond_to do |format|
      if @user.update(user_params)
        format.html { redirect_to @user, notice: 'User was successfully updated.' }
        format.json { render :show, status: :ok, location: @user }
      else
        format.html { render :edit }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /users/1
  # DELETE /users/1.json
  def destroy
    @user.destroy
    respond_to do |format|
      format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_user
      @user = User.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def user_params
      params.require(:user).permit(:name, :email)
    end
end

各ルーティングで表示する Web ページ用のリソースの作成 /views

views の直下に users というフォルダが作成され、その中に大量のファイルができているはずだ。これが、先ほど /users, /users/12 というにアクセスされた際に表示されたページを作っている正体だ。

Rails はデフォルトでは API ではなく、HTML を返すモノシリックなアプリケーションとなる。(もちろん API として運用することもできるが)我々フロントエンドの人間からすると信じられないが、レスポンスが HTML なのだ。とにかく今はそうなっていることを確認してほしい。

.erb ファイルはテンプレートエンジン

.erb は独自のシンタックスで書かれており、これが Rails によってコンパイルされ、HTML にされる。

以下のようなファイルがあるが、これらへのルーティングは誰がその役目を果たしているのか。おそらくその答えは、ルーティングではなく、コントローラだ。

  • index.html.erb
  • show.html.erb
  • edit.html.erb

コントローラーと /views の結びつきの説明

users_controller.rbindex アクションに何も記述をしなかった場合、それは暗黙的に /views/index.html.erb を参照することになる。

コントローラーを自動で作成する / rails controller name

controller を生成する
rails generate controller hello
  • コントローラー、テスト、ヘルパー、JS、CSS、
  • View は作られないが対応するフォルダだけ作られる
  • ルーティングはされない(scafold の場合にはされていた)

ルーティングの書き方一部

routes.rb
Rails.application.routes.draw do
  resources :users # 一気に API に必要なメソッド分のルーティングをする
  root 'application#hello' # ルートパスをコントローラーに結びつける
  get 'hello/index' # /hello/index のパスを hello コントローラーの index メソッドに自動的に結びつける(/hello ではアクセスできない)
  get 'hello' , to: 'hello#index' # /hello を hello コントローラーの index メソッドに明示的に結びつける
end

URL params を使う

http://localhost:3000/hello?name=nakanishi

class HelloController < ApplicationController
  def index
    name = params['name'] # params で url params にアクセスできる
    render html: "hello, " + name
  end
end
  • ruby では変数宣言をしなくてもそのまま使える
  • params で url params にアクセス可能
  • params は hash という JavaScript のオブジェクトに似たもの
  • hash(JS object like) へのアクセス方法は別記

hash == JavaScript Object

  • hash は JavaScript のオブジェクトに似ている
  • 定義方法はいくつかある
  • : を使うタイプの方が基本的にいい
  • hash.propertyName のようにはアクセスできない
hash について
class HelloController < ApplicationController
  def index
    # like a JavaScript Object
    hash1 = {name: 'name', job: 'job'}
    hash2 = {:name => 'name', :job => 'job'}
    hash3 = {'name' => 'name', 'job' => 'job'}

    name = params[:name]
    # or name = params['name']
    
    render html: "hello, " + name

    hash1[:name] # ok
    hash1['name'] # error

    hash2[:name] # ok
    hash2['name'] # error

    hash3[:name] # error
    hash3['name'] # ok
  end
end

.erb ファイルとコントローラーのインスタンス変数

  • controller でインスタンス変数を @name = 'value' で定義する
  • .erb ファイルからインスタンス変数を参照する <%= @name %>
/controllers/hello_controller.rb
class HelloController < ApplicationController
  def about
    name = params[:name]
    @name = name # インスタンス変数を定義
  end
end
/vies/about.html.erb
<h1>my first ruby <%= @name %></h1>

DB と Model

Rails の Model は DB の操作と結びついたクラスで、この Model は Controller から参照できる。Controller 内で Model を参照し、この Model に対して操作を加えることで、間接的に DB への操作をおこなうことができる。

まずは Model を作る呪文を詠唱する

model を作る
rails generate model person name:text age:integer mail:text
  • person テーブルを作る。name, age, mail カラムを持つ。(正確にはそのようなテーブルを作るマイグレーションファイルを作る)
  • Person Model が作成される。/models/person.rb(このファイルには何も書かれていないが動く。当然黒魔術が支配しているからだ)
  • ルーティングはされない
/models/person.rb
class Person < ApplicationRecord
end

DB への migration

以下コマンドで /db/migration にあるファイルを元に DB へクエリを発行し、テーブル等々を自動で作ることができる。

マイグレーションをする
rails db:migrate

DB にデータを追加する

seeds を実行することで、DB にデータを追加できる

db/seed.rb
Person.create(name: 'taro', age: 38, mail: 'test@test')
Person.create(name: 'nakanishi', age: 28, mail: 'test2@test')
DB にデータを追加する
rails db:seed

コントローラーから参照する

以下のように Person Model は単に、Person とするだけで暗黙的に参照できる。なんの import も必要とせず。

/controllers/people_controller.rb
class PeopleController < ApplicationController
  def index
    @person = Person.all
  end
end

上記コードでは @person = によって、インスタンス変数を定義しているので、これを .erb ファイルから参照できる。

.erb ファイルにて

peopel.index.erb
<h1>People#index</h1>
<p>Find me in app/views/people/index.html.erb</p>
<div>
  <% @person.each do |o| %>
    <h2>
      <%= o.name %>
      <%= o.mail %>
    </h2>
  <% end %>
</div>
  • @person.each person インスタンス変数に含まれる配列の要素それぞれについて以下を実行する
  • これは JavaScript でいうところの以下に相当する
  • <% %> はようは PHP のあれ。HTML にスクリプト言語を埋め込む。
in JS
person.forEach(o => {
  o.name;
  o.mail;
});

model.find(idValue)

Person.find(id) で DB から id が一致するアイテムを取得できる

controller
class PeopleController < ApplicationController
  def show
    id = params[:id]
    @person = Person.find(id)
  end
end

model.create(hash)

Person.create() で DB にアイテムを新規追加する

controller
class PeopleController < ApplicationController
  def add
    new_person = {
        name: 'name',
        age: 12,
        mail: 'm@m'
    }
    Person.create(new_person)
    redirect_to '/people'
  end
end

post の場合にのみ実行する場合

controller
class PeopleController < ApplicationController
  def add
    if request.post? then
      new_person = {
          name: 'name',
          age: 12,
          mail: 'm@m'
      }
      Person.create(new_person)
      redirect_to '/people'
    end
  end
end
  • request.post? を確認すればいい
  • if then については以下コード参照(if then end で一セット)
in JS
if (request.post) {
  const newPerson = {
    name: "name",
    age: 12,
    mail: "m@m"
  };

  Person.create(newPerson);
  redirectTo("/people");
}

heroku(AWS, GCP 的なやつ)へデプロイ

  • Rails のデフォルトの DB は SQLite3 だが、Heroku は PostgreSQL = pg なので変更する。
  • dev, production, test それぞれの環境項目を設定できるので prod は heroku に合わせて設定する。

Heroku を使う手順

  • 公式ドキュメント わかりやすい日本語資料
  • アカウントを作ってログインする。
  • Heroku CLI をインストールする brew install heroku/brew/heroku
  • heroku login でログインする => ブラウザが立ち上がる => ログインする => CLI の方でもログイン完了したことが示されていれば OK。
  • 開発ディレクトリに行き heroku create する。
  • これをすると git remote に heroku が追加され、また heroku のサービス側にもそのための app が作成され、これと紐づけられる。
  • git push heroku master でデプロイする。
  • ようは、remote リポジトリ heroku に git でプッシュすればいいだけ。
  • そのあと heroku run rails db:migrate で migration する。ようはリモートの rails コマンドを実行できる。

react と組み合わせる

参考記事

rails app を api モードで作る

  • -T デフォルトの test tool を使わない
  • -N Gem をインストール際にドキュメントを読み込まない的なことらしい
  • --api api モードで!
rails project を作る
rails new . --api --database=postgresql -T -N

rails server を port 3001 で起動する

rails server を起動
rails s -p 3001

react app /client 配下に作る

npx create-react-app client --typescript