フロントエンドエンジニアのための Rails と Ruby 入門
Tweet方針
- 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 と違って勝手にやってはくれない。
- 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 の雛形を作り、開発用ウェブサーバーを立てる
## 様々なファイルが生成され、また自動的に 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 につながっているのは、おそらくデフォルトの設定がそうなっている)
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 を実行するように定義される。
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::Base
の hello
メソッドに紐づけられる。
つまり routes.rb のおける application
というコントローラーの指定は、applocation + _controller
に自動的に結び付けられる。このファイルではさらにファイル名を大文字キャメルケースにした ApplicationController
というクラスを(恐らく)持つ必要がある。
ファイルに変更を加える
以下のように変更を加え、ルートパスにアクセスすると hello, world! と表示される。つまりルーティングで指定した ApplicationController の hello というメソッド(アクション)が実行される。
- Ruby はインデントでブロックを形成する。(.sass と同じ)
- def methodName ~ end でメソッドを定義する。
- render html: は恐らく HTML 要素を返り値にもつメソッドを実行している。
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 に加えられた変更
ルーティング設定に一行追加される。
Rails.application.routes.draw do
resources :users # 追加される
root 'application#hello'
end
resources はさらなる魔術であって、以下のようなルーティングを自動生成する。つまり UsersController へのルーティングを複数の action を指定して暗黙的に行う。いわゆる CRUD に必要なものが全て暗黙的にルーティングされる。
さらに、なんと UsersController も先ほどの詠唱 generate scaffold
で完成していた。
/app/controllers/users_controller.rb の作成
以下のようなメソッド(とそれが記述される新たなファイル users_controller.rb)がすでにできている。(同時に対応する HTML をレンダリングするファイルも作成されているので)試しに以下の URL にアクセスして結果をみてほしい。
- /users => DB からユーザー一覧の情報を取得して対応する HTML レンダリングメソッドで出力されたページがみれる。
- /users/12 => id:12 のユーザーが表示される。まだこのユーザーがいなければエラー。
- /users/new => ユーザー追加の画面が見れる。
コントローラーの内部はスカスカである。もちろんここも魔法が支配している。これについては後述する。
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.rb
の index
アクションに何も記述をしなかった場合、それは暗黙的に /views/index.html.erb
を参照することになる。
コントローラーを自動で作成する / rails controller name
rails generate controller hello
- コントローラー、テスト、ヘルパー、JS、CSS、
- View は作られないが対応するフォルダだけ作られる
- ルーティングはされない(scafold の場合にはされていた)
ルーティングの書き方一部
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 のようにはアクセスできない
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 %>
class HelloController < ApplicationController
def about
name = params[:name]
@name = name # インスタンス変数を定義
end
end
<h1>my first ruby <%= @name %></h1>
DB と Model
Rails の Model は DB の操作と結びついたクラスで、この Model は Controller から参照できる。Controller 内で Model を参照し、この Model に対して操作を加えることで、間接的に DB への操作をおこなうことができる。
まずは Model を作る呪文を詠唱する
rails generate model person name:text age:integer mail:text
- person テーブルを作る。name, age, mail カラムを持つ。(正確にはそのようなテーブルを作るマイグレーションファイルを作る)
- Person Model が作成される。/models/person.rb(このファイルには何も書かれていないが動く。当然黒魔術が支配しているからだ)
- ルーティングはされない
class Person < ApplicationRecord
end
DB への migration
以下コマンドで /db/migration にあるファイルを元に DB へクエリを発行し、テーブル等々を自動で作ることができる。
rails db:migrate
DB にデータを追加する
seeds を実行することで、DB にデータを追加できる
Person.create(name: 'taro', age: 38, mail: 'test@test')
Person.create(name: 'nakanishi', age: 28, mail: 'test2@test')
rails db:seed
コントローラーから参照する
以下のように Person Model は単に、Person とするだけで暗黙的に参照できる。なんの import も必要とせず。
class PeopleController < ApplicationController
def index
@person = Person.all
end
end
上記コードでは @person =
によって、インスタンス変数を定義しているので、これを .erb ファイルから参照できる。
.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 にスクリプト言語を埋め込む。
person.forEach(o => {
o.name;
o.mail;
});
model.find(idValue)
Person.find(id)
で DB から id が一致するアイテムを取得できる
class PeopleController < ApplicationController
def show
id = params[:id]
@person = Person.find(id)
end
end
model.create(hash)
Person.create()
で DB にアイテムを新規追加する
class PeopleController < ApplicationController
def add
new_person = {
name: 'name',
age: 12,
mail: 'm@m'
}
Person.create(new_person)
redirect_to '/people'
end
end
post の場合にのみ実行する場合
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 で一セット)
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 new . --api --database=postgresql -T -N
rails server を port 3001 で起動する
rails s -p 3001
react app /client 配下に作る
npx create-react-app client --typescript