PHPerがRailsデビューしてWebAPIを作りRSpecでテスト書いてCap3/CircleCIでデプロイして分かった事を1ヶ月前の自分に教えたいので、まとめてみた

  • 866
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

タイトル長い。すまぬ。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で入れてるけどコード上では、もっとわかり易い名前で扱いたいとき。積極的に使っていきたい。

models/user.rb
# 性別設定
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 srails 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経由で参照する。

ダメダメ

hoge.html.erb
<%= render partial: 'parts' %>
_parts.html.erb
<p><%= @hoge %></p>

よい

hoge.html.erb
<%= render partial: 'parts', locals: { hoge: @hoge } %>
_parts.html.erb
<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]
hoge.jbuilder
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さんが対応してくれているのでガッツリ使おう。

config/locals/ja.yml
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とは

テストコードを見ていて、ちょっと分かりにくかったのが describecontext の使い方。あってもなくても良いんだけど、テストコードの固まりを示したり、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ならリクエストテストも超簡単。

api_user_spec.rb
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の設定をすることが可能。自動デプロイの設定はこんな感じ。

circle.yml
machine:
  ruby:
    version: 2.1.2
deployment:
  production:
    branch: master
    commands:
      - ./deploy_prod.sh
  staging:
    branch: staging
    commands:
      - ./deploy_staging.sh
deploy_staging.sh
#!/bin/bash
BRANCH=staging bundle exec cap staging deploy

最後に

Railsじゃないとできないなんてことは、もちろんなくて、単純に本質的なコード書きに集中できるかどうかで言うと、Railsはかなり良かったです。PHPはもちろん好きだし、用途や状況に合わせて選択できるように色々試すのが大事だなと思いました。まぁ、LLなPHP/Rubyで比較してもそこまであれか。

Railsやってみたいけど…って思っている方がいたら、ぜひお勧めします。学習コストにつ得ては、集中すれば約1ヶ月間あれば、ひと通りのことはできるようになると思います。最初の1週間だけ超イライラするかもしれないですがw