タイトル長い。すまぬ。PHPerとして約10年近く。Ruby自体は案件によってちょこっとだけ触ったことがある程度。Rails自体を本格的にさわるのは今回が初めて。PHPだとCakePHPを中心にZend/Symfonyなどいくつか。そんな僕が今回、Rails4デビューをして、WebAPIを作り、RSpecでテスト駆動開発風味で、GitHubプルリクベースの、CircleCI経由デプロイをするまでの開発の流れをひと通りやってみて、分かったことがいくつかあったので、それをまとめてみた。過去の自分のために。
注意点としては、今回作ったのはWebサービスではなく、スマホゲーム(ネイティブ)のサーバサイドWebAPIという点。なので、いわゆるViewに関わる部分はあんまり出てこないです。すまぬ。
それと、ひと通りの流れをチュートリアル的に解説するような記事ではなく、躓いたポイントだったり、当時分かりにくかったことを箇条書きベースでメモするタイプのものです。すまぬ。
見当違いのこと書いてたら、教えて下さいませ。
Rubyまわりの開発環境について
Rubyを本格的にさわり始めてつまづいた最初のポイントが環境周り。Rubyでプログラミングしたいだけなのに、Ruby以外の名前が出てくる…しかもさも当然かのように。ここらへんは冷静にググれば分かるような事だけど、一刻も早くRubyでプログラミングしたい場合、焦って飛ばしてしまうことがある。
rbenv
Rubyのバージョン管理をしてくれる賢い奴/PHP以上にバージョンごとの挙動に注意しなくちゃいけないので、rbenvは役に立つのだろう。もう、これ経由でしかRubyはインストールしたことない。
あと、そのまま入れるとirbやpryで日本語通らないので下記設定が必要。
# Homebrewで readline を入れる
$ brew install readline
$ brew link readline --force
# Rubyのインストール
$ RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline)"
$ rbenv install 2.1.2
読み方はアールベンブだと思っていたけど、アールビーエンブの方が一般的っぽい。でもこれRuby environmentの略だろうから、ルビィエンブでも良い気がする。どうでも良いか。
ちなみに、似た機能を持つrvmというのがある。使ったことはないが、今だとrbenvの方が主流っぽい。
bundler&gem
先人の知恵をインストールしてバージョン管理してくれるツール。とりあえず、Gemfileにインストールしたいものを書いて、bundle install
すればOK。あと、バージョン管理には。Gemfile.lockを含めよう。じゃないと他の人と違うバージョンがインストールされて困ることがあるよ。PHPのComposerと同じ。
# bundler経由でインストールしたGemを実行
$ bundle exec コマンド
# Gemfiileに書かれたGemをインストール
$ bundle install
# 指定Gemを削除、その後Gemfileから手動削除
$ bundle exec gem uninstall 名前
Rack
Rubyで作ったものをHTTPDに載せたいと思った時に避けて通れないRack。でもRackってなんやねん。一言で言うと「Rubyで書かれたアプリケーションとサーバ間にあるインターフェイスライブラリ(Gem)」。
要は統一されたインターフェイスを持つことで、好きなアプリケーション/フレームワークとサーバを組み合わせられるよってことですね。Railsいれてるとディレクトリ直下にconfig.ru
っていうファイルがあるけど、これがRackのサーバ起動コマンドであるrackup
の設定ファイル。
非常に分かりやすい解説記事がQiitaにあったのでリンクをはっておきます。
Unicorn / Puma / Phusion Passenger / Thin
色いろあるアプリケーションサーバ。
サーバ | 概要 |
---|---|
Unicorn | nginxと組み合わせるのが定番っぽい。マルチプロセス・シングルスレッド。 |
Puma | ワーカー毎にスレッド立ち上げる。JRubyとかで動かすのが良いっぽい。マルチプロセス・マルチスレッド。 |
Phusion Passenger | Apacheやnginxの拡張モジュールとして動作。規模の大きくないアプリケーション向け?マルチプロセス・シングルスレッド。(商用バンはマルチスレッド?) |
Thin | イベント駆動系に向いているっぽい。マルチプロセス・マルチスレッド。 |
個人的には、割と普遍的に使えそうなUnicorn + nginxという構成にしている。
アプリケーションサーバ比較がとても分かりやすい記事があったのでリンク。
pryという便利ツール
対話式でRubyのコードを処理してくれる。デフォルトだと、irb
があるが、pry
の方がいくつかの点で便利。とりあえず、このコードの挙動試したいんだっていう時に。これがないともう死ぬ体になった。PHPにも似たやつがあるけど、試したことはない。rails console
も同様にとても便利。
よく使うコマンド
- .を付けるとshellコマンドが実行できる。例:
pry(main)> .ls
-
cd
オブジェクトの移動 -
ls
今有効なオブジェクト一覧が見れる -
hist
過去の式一覧が見れる -
show-routes
Railsルーティング一覧。rake routes
よりも早くて良い
PHPと比べて
Rubyは全てがオブジェクトだからか、自然にコードが書ける気がする。あと、メソッドチェインがとても良い。俺がJavaScriptスキーだから。あと、Perlのように(Perlの魔術的なほどではないが)コードを短く書くことができるのも良い。エラー処理とかは、後置 if / unlessでワンラインで書くのが最近の好み。
なんとなく、コードを俯瞰してみた時に、メソッドの固まりが分かりにくい感じもするけど。1メソッドの単位が小さくなるから画面占有率的に縦にも横にも低いからかな…。
フォームからのパラメータを受け取った数値データは、当然文字列として受取るわけだけど、Rubyの場合、注意が必要。"10" と 10 はぜんぜん違う。そりゃそうだ。
前は書き捨てるようなコードは、とりあえずPHPでっていうのが多かったけど、今はpryでサササッと書くことが増えた。
CakePHPと比べて
CakePHP1〜2を20〜30プロジェクトで、数年間使ってきた。Railsの影響を受けたらしいので、確かに似ているところはある(設定より規約のあたりとか)。が、実際に使ってみると、言語の違いも合ってか、そこまで似てない。MVCでディレクトリ構成が似ているっていう感じ。
bake
コマンドはほぼ使わなかったけど、Railsでは普通にrails new
はするし、rails g
も当然のようにする。この違いはなんだろう?と思ったけど対話式かどうかっていう所なのかな。
CakePHP3から変わると思うけど、配列地獄じゃなくなるっていうのは、一つのメリットなのかもしれない。
CakePHPから乗り換えるべきか
- 新規案件なら、Railsに乗り換える。
理由:慣れれば確かに開発工数が少し減る。身近にRails強者がいるならオススメ。初期の学習コストはRailsの方が高く感じた。
- 運用段階なら、CakePHPのままでいく。
理由:CakePHPのバージョンアップも、Railsのバージョンアップ、どちらもそれなりに大変。リプレイスして、運用工数が劇的に変わることはそんなにないと思う。
Ruby + Rails気になったポイント
pluckがとにかく便利
特定のカラムだけを集めた配列が作りたいときに使う。めっちゃ便利。
user_names = User.all.pluck(:name)
# => [hoge, piyo]
users = User.all.pluck(:id, :name)
# => [[1, hoge], [2, piyo]]
ActiveRecordのenum
ステータス情報とか、フラグとか。実データは数値やbooleanで入れてるけどコード上では、もっとわかり易い名前で扱いたいとき。積極的に使っていきたい。
# 性別設定
SEXES = { male: 1, female: 2 }
enum sex: SEXES
def sex_enum
SEXES
end
# 女性だけのレコードを取得する
females = User.female.all
# こんな書き方も
males = User.where('sex = ?', User.SEXES[:male]).all
# 保存するとき
user = User.new
user.sex = 'male'
user.save
シンボルの存在
なんやねん、シンボルって。シンボルは任意の文字列と一対一に対応するオブジェクトだそうです。要は名前を整数で管理し、その整数を表現したものがシンボル。もう色んな所で使うので、?と思うのは最初だけかも。
シンボルのメリットとしては、パフォーマンスの向上と、コードの可読性向上、そして不変であるということ。
procとブロック
Rubyでコード書く上で絶対に避けて通れないprocとブロック。非常に分かりやすい解説記事があったので紹介。
Rubyでは、メソッドはファーストクラスではなく、メソッドをなんからの形でオブジェクトに渡すことが出来ないのだけど、それを実現するのがブロック、そしてブロックが独立して存在できるようになったのがproc。無名関数とも呼べるのかな。
Springを使って高速起動
色々キャッシュして高速起動してくれる賢い奴。rails s
やrails c
そしてrspec
の実行は遅いので、積極的にSpringを使っていきましょう。Rails4ではデフォルトで入っているはず。
$ bundle exec spring binstub --all
これで、./bin/
ディレクトリ内のrailsコマンドなどがspring経由で起動するようになります。
一つ、注意しなくちゃいけないのがRSpecでテストしている時、コードの修正を行っても自動反映しない時があるということ。毎回じゃなくて、時々なので困る…。そんな時はSpringのキャッシュをクリアする./bin/spring stop
しましょう。当然、次回からRSpecの実行は遅くなります。
Cのインスタンス変数は極力減らす
これはRailsだけに限った話じゃないですけど、コントローラ内のインスタンス変数はなるべく減らす。できれば1つ。多くても3つくらい。理由はViewとデータのやり取りが多くなればなるほど、読みにくくミスりやすくなるから。
あと、絶対やっちゃいけいないのはpartialとかで部分テンプレートを使っていて、その中からインスタンス変数を参照すること。必ずlocals経由で参照する。
ダメダメ
<%= render partial: 'parts' %>
<p><%= @hoge %></p>
よい
<%= render partial: 'parts', locals: { hoge: @hoge } %>
<p><%= hoge %></p>
SNSとかでよく作られる友だち関係のアソシエーション
Userモデルと、Friendモデルがあって、Friendモデルには友だち関係が記録されているとする。変な名前になっているけど、両思いな友達リストと、片思いな友達リストを取得した場合。:source
で、対象モデル名を指定できる。+ラムダでwhereを渡して条件付けも可能。
has_many :otomodachis, -> { where('friends.received' => 1) }, :through => :friends, :source => :friend
has_many :kataomois, -> { where('friends.received' => 0) }, :through => :friends, :source => :friend
# 自身のフィールド値を引数としても渡せる
has_many :hoges, ->(hoge) { where('hoge.piyo' => hoge.aaa) }
jBuilderでキーのない配列を出力する
単純にこんな配列のJSONを出力させたい。
[1, 5, 10]
json.array! [1, 5, 10]
なんか、非常に分かりにくかったので。
nil? empty? blank? present?
とにかくこれ読む。
FactoryGirlでhas_manyなデータを作る
テストデータを作るのに超便利なFactoryGirl。かなり複雑なことも出来る。
factory :user, :class => User do |user|
user.items {[
create(:item)
create(:item)
create(:item)
# 他のデータを使って設定とかも可能
hoge = create(:item)
create(:item, piyo: hoge.piyo)
]}
end
RailsAdmin / ActiveAdmin / オリジナル管理画面について
- 使い勝手・便利:RailsAdmin
- カスタマイズ性:ActiveAdmin
なんだけど、個人的には開発初期にはRailsAdminをとりあえず入れて、暫定管理画面として。その後、オリジナルの管理画面をガッツリ作って、RilasAdminはサブに回す。というのが一番いいと思った。RailsAdminのカスタマイズ、色々やろうと思えばできるけど、正直死ぬ。ActiveAdminはカスタマイズ性は比較的高いけど、それだったらいっそ自前で作ったほうが結果良い管理画面になる。
ユーザ管理とかは、devise + cancancanで決まりですね。
i18n対応というか文言管理
もうデフォルトでRailsさんが対応してくれているのでガッツリ使おう。
hello: こんにちは!こんにちは!
p I18n.t('hello')
Rubyでsprintf風
p "Hello %s" % "zaru"
p "i like %s and %s" % ["sushi", "ruby"]
Gemという先人の知恵
なにか作ろうと思ったら、まずGemを検索。っていうくらい、Gemは素晴らしいですね。欲しいと思った機能は大抵ありますし、メンテナンスもわりとされている感あります。Nodeのnpmは、種類いっぱいあるけど似たようなものが乱立+放置されている/PHPのPackagistは、色々あるけどまだ完全にデファクトスタンダードになってない感ある。
役に立っているGemたち
ここらへんは、いろんな人達がすでに紹介しまくっていると思うので、中でも一押しだけ紹介。
rspec-json_matcher
WebAPIのJSONフォーマットテストをする際に、超お役立ち。json_expressions
でもできるけど、rspec-json_matcher
は差分の表示が超わかりやすい。感謝感激。
binding_of_caller & pry-byebug
ステップ実行をしてくれて、しかも指定の場所でpryが立ち上がって、状態を維持したまま色々できる。テストコードを書いてて、どうしても通らない時にこれやるとすぐ解決する。もう無くてはならない。
grape & grape-jbuilder
WebAPIをサクッと作れるGrape + JSONフォーマットを簡単にViewで書けるjBuilderの組み合わせ。
Railsの場合、デフォルトRESTなAPIをサポートしてくれているんだけど、純粋にWebAPIを作りたい場合には、ちょっとなんか書きにくい印象があった。Grapeを使うことでAPIで実装したいことは大抵サポートしてくれているので実装に集中できた。
ドキュメントも公式でかなり網羅されていて、読めばだいたい分かる。
バージョニングが便利
WebAPIの宿命、バージョニング。URLに含めるかHTTPヘッダにするか選べて、簡単便利に設定できる。
# URLに含める例 http://example.com/api/v1/...
version 'v1', using: :path
パラメータのバリデーションがシンプル
params do
# 必須(数値型指定)
requires :id, type: Integer, desc: 'ID'
# オプション(文字列指定・デフォルト値設定)
optional :name, type: String, default: 'hoge'
# さらに値のバリエーションを設定
optional :name, type: String, default: 'hoge', values: ['piyo', 'hoge']
# 正規表現もOK
optional :name, type: String, regexp: /^[a-z]+$/
end
複数フォーマットエンジンのサポート
XMLとJSON両方をサポートして、デフォルトをJSONに。
class HogeAPI < Grape::API
content_type :xml, 'application/xml'
content_type :json, 'application/json'
default_format :json
end
ブロックを渡すことで、自前でフォーマットを書くことも出来る。例えばExcelにする場合。
class HogeAPI < Grape::API
content_type :xls, "application/vnd.ms-excel"
formatter :xls, lambda { |object, env| object.to_xls }
end
モジュール化も簡単
class Hoge::API < Grape::API
mount Hoge::APIv1
mount Hoge::APIv2
end
エラー処理も簡単
class Hoge::API < Grape::API
get '/piyo' do
error! "存在しないですよ" 404
end
end
テストを書くという楽しさと安心
テストは書かないと書けるようにならないし、テストコードがあることによって、どんなメリットが自分にあるのかは実感できない。だから、まず書いてみることが大事。書き方の文法とコツさえ分かれば超簡単。
RSpecの文法
値の比較をするマッチャというオブジェクトがあり(一致するかどうかとか調べてくれる)、この文法を覚えればとりあえずはなんとかなる。RSpec2.xの古いやつと、2.14以降では文法が違うので注意。shoudが出てきたら古い版。toが出てきたら新しい版と覚えればOK。あえて古いので書くメリットはない。
hoge = "hoge"
expect(hoge).to eq "hoge"
expect(hoge).not_to eq "piyo"
よく使うマッチャ一覧
マッチャ | 意味 |
---|---|
eq | 等価。一番良く使う |
be >= | 以上。同様にbe <= be > be < など |
be_truthy | 真である。3.x以前はbe_true |
be_falsey | 偽である。3.x以前はbe_false |
eq true / false | true / falseである |
include [1] | 配列に1が含まれいる |
be_between(n, m) | n以上 m以下である |
be_between(n, m).exclusive | nより大きく mより小さい |
match | 指定文字列・配列・ハッシュと等価 |
describe / contextとは
テストコードを見ていて、ちょっと分かりにくかったのが describe
と context
の使い方。あってもなくても良いんだけど、テストコードの固まりを示したり、before
after
で共通処理をしたいときに便利。ネストはいくらでもできる。
-
describe
が機能の単位(メソッドとかAPIとかバリデーションとか) -
context
が状態(正常・パラメータ不足・異常など)
という感じで使っています。簡単な例。
describe "Hoge計算メソッドの振る舞い" do
context "正常" do
it "渡された引数の数値全てを合算した数値が返ってくること" do
# snip
end
end
context "パラメータがおかしい" do
it "渡された引数に文字列が混じっている場合、falseが返ってくること" do
# snip
end
end
end
before :eachをスキップしたい
共通の処理をまとめてやってくれる before :each
ですが、あるテストコードだけ処理をスキップしたい場合がある…という時に使える小技。
before :each do
unless example.metadata[:skip_before]
@user1 = create(:user)
end
end
describe "スキップするやでー", skip_before: true do
end
afterの使いドコロ
正直、afterは使う所ないだろ…とか思ってたけど、この前AWS SNSのプッシュ通知がからんだAPIのテストをするときに、デバイストークンの登録処理が重複するとAWS側でエラーになってしまい、連続テストができないのでafter
でデバイスの削除処理を入れることで、しのいだ。
DBをクリアにしてくれるdatabase_cleaner
でクリアにできないような処理に役立ちそう。ファイルの削除とか。
WebAPIのテスト
RSpecならリクエストテストも超簡単。
it "ステータス201が返ってくること" do
post '/api/v1/users', { hoge: piyo }, { 'HTTP_X_HOGE' => 'hoge' }
expect(response.status).to eq 201
end
it "規定のJSONフォーマットが返ってくること" do
pattern = {
user: {
hoge_id: /\A[0-9]{9}\z/
}
}
post '/api/v1/users', { hoge: piyo }, { 'HTTP_X_HOGE' => 'hoge' }
expect(response.body).to be_json_as(pattern)
end
テストを書くということ
まぁ、テストを書くこと自体の是非については散々議論されつくされているので今更ですが。
- 仕様の変更・コードの変更が怖くない
- 曖昧な仕様がどんどんハッキリしてくる
- テストを実行して結果がズラズラ出てくるときの楽しさ
もっと早く書けばよかった。
デプロイ
Capistarano
Railsでデプロイといえば、capistarano
なわけです。今はバージョン3で2とはだいぶ違うのでcap3とかって略されて呼ばれることが多いみたい。正直、設定は超難しかったです。というか、設定修正してのトライ&エラーがやりにくかった…。
設定方法のまとめは、以前Qiitaで書いたので、紹介。
一番ハマったところとしては、Gitのリポジトリを途中から変更した場合、それが反映されないという点。デプロイ先サーバの./repo/config
ファイルを修正する必要がある。
CircleCI
GitHubを利用していてcap3の設定済みの場合、CircleCIを使うと非常に便利。TravisCIに比べて一番安い料金プランでもスペックが良くて、単価も安かった気がする。
- GitHubでプルリク作成
- CircleCIが自動テスト
- テストの結果をGitHubのプルリクページに反映
- マージする
- 自動テスト後、通れば自動デプロイ
までやってくれる。
設定
circle.yml
という設定ファイルをプロジェクトに含めることでCircleCIの設定をすることが可能。自動デプロイの設定はこんな感じ。
machine:
ruby:
version: 2.1.2
deployment:
production:
branch: master
commands:
- ./deploy_prod.sh
staging:
branch: staging
commands:
- ./deploy_staging.sh
#!/bin/bash
BRANCH=staging bundle exec cap staging deploy
最後に
Railsじゃないとできないなんてことは、もちろんなくて、単純に本質的なコード書きに集中できるかどうかで言うと、Railsはかなり良かったです。PHPはもちろん好きだし、用途や状況に合わせて選択できるように色々試すのが大事だなと思いました。まぁ、LLなPHP/Rubyで比較してもそこまであれか。
Railsやってみたいけど…って思っている方がいたら、ぜひお勧めします。学習コストにつ得ては、集中すれば約1ヶ月間あれば、ひと通りのことはできるようになると思います。最初の1週間だけ超イライラするかもしれないですがw