Yokohama.rb Monthly Meetup #81に参加した

2017-06-10(土)のYokohama.rb Monthly Meetup #81参加メモです。

yokohamarb.doorkeeper.jp

3月の#78も参加していたのにメモを書いていなかったので、久しぶり感あります。

Rubyレシピブック

レシピ254から256まで。HTTP, SMTP, FTPというプロトコル3連発でした。

254: HTTPクライアントをつくる

HTTPクライアントを一からつくるというわけではなく、open-uri, net/http, httpclientを使ってみるというレシピでした。rest-clientというクライアントライブラリがあるという話も聞けました。

github.com

あとはlibcurlラッパのtyphoeusとかがありますね。

github.com

255: メールを送信する

net/stmpを使って、素のRubyスクリプトでメールを送るというレシピでした。ふだんはAction Mailerばかりなので新鮮。

今回はメールの受信テストにMailCatcherを使っていましたが、Railsのときはletter_openerを使ったり、Action Mailerにもプレビューがあるという話をしていました。

256: FTPでファイルを送受信する

Ruby, net/ftpというライブラリも標準添付されています。@igrepさんにPure-FTPdというFTPサーバを立ててデモしてもらいました。

バイナリファイル、テキストファイルの送受信がプロトコルとして存在していて、そのまま Net::FTP#getbinaryfile のように対応するメソッドが存在しています。get という名前ですが、返り値はなくローカルファイルを保存するという仕様。

その他

Rubocopを導入するとき、とっかかりのルールはどうしたらよさそうかという話をしたところ、onkcopの設定を参考にしてみるとよさそうというお話を聞いたので、それを眺めたりしていました。

github.com

あとはこのエントリをもくもく書いたり。

blog.kymmt.com



次回は2017-07-08(土)です。

yokohamarb.doorkeeper.jp

Springが動くRails+MySQLなAPIサーバの開発環境をDocker Composeで作る

このあいだ、Rails+MySQLという構成のアプリケーション開発環境をDocker Composeで構築できるようにしました。

blog.kymmt.com

いろいろと理解が深まるにつれて、何点かクリアしたい問題が見えてきました。

  • Dockerfile で使うイメージ
    • ruby:2.4.1-onbuildRUN bundle config frozen 1 しているので、あとから Gemfile を更新して bundle install できない
  • bundle install に時間がかかる
    • Gemfile にgemを追加するたびに、すべてのgemのインストールが走る
  • rails, rake コマンドの立ち上がりが遅い
    • アプリケーションプリローダSpringのサーバが立ち上がっていないため

今回はこれらの問題をクリアして、さらにいい感じの開発環境を作ります。

Dockerfile で使うイメージ

前回作った Dockerfile では ruby:2.4.1-onbuild というイメージを使っていました。このイメージは ONBUILD 命令を使っており、このイメージを使った Dockerfile をビルドすることで ONBUILD に指定されたコマンドが自動で実行されるようになっています。Rubyの場合だと、Gemfile のイメージ内へのコピーや bundle install の実行のような決まりきったコマンドが ONBUILD として指定されているので、Dockerfile の作成を省力化できます。ruby:2.4.1-onbuildDockerfile は次のものです。

しかし、この ruby:2.4.1-onbuild では、RUN bundle config --global frozen 1 というコマンドを実行するようになっています。これは、イメージ内の Gemfile を変更して bundle install できないようにするものです*1

デプロイするような用途であればこれでもよいですが、今回は開発環境がほしいので、Gemfile は必要なときに都度変更してgemをインストールできるようにしたいです。また、onbuild タグがついたイメージは非推奨となっているようでした*2

そこで、ruby:2.4.1-onbuild イメージを使うのはやめて、ruby:2.4.1 イメージをベースに自分でもろもろの作業をやる Dockerfile になるように書き直しました。

FROM ruby:2.4.1

ENV APP_HOME /usr/src/app
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME

COPY Gemfile \
     Gemfile.lock \
     $APP_HOME/

ENV BUNDLE_GEMFILE=$APP_HOME/Gemfile \
    BUNDLE_JOBS=4
RUN bundle install

(※後述の[追記]も参照してください)

これで、コンテナ内の Gemfile をあとから編集しても、bundle install が実行できます。

bundle install に時間がかかる

現在、bundle install で入るgemの保存先はコンテナ内になっています。この場合、Gemfile にgemを追加してインストールしたいときはイメージを作り直す必要がありますが、これだとすべてのgemのインストールが走ってしまいます。

これを回避するために、gemの保存先をDocker Volumeに変更します。docker-compose.yml でgem保存用のVolumeを作り、APIサーバを動かすコンテナにマウントします。

version: '3'
services:
  # dbの設定...

  app:
    build: .
    command: bin/rails s -p 3000 -b "0.0.0.0"
    depends_on:
      - db
    ports:
      - "3000:3000"
    stdin_open: true
    tty: true
    volumes:
      - .:/usr/src/app
      - bundle_cache:/usr/local/bundle
volumes:
  bundle_cache:

bundle_cache がgem保存用のVolumeです。マウント先が /usr/local/bundle になっているのは、ベースイメージとしている ruby:2.4.1GEM_HOME/usr/local/bundle としているからです。

https://github.com/docker-library/ruby/blob/752c5f7cf44870ceae77134b346d20093053c370/2.4/Dockerfile#L63

これで、gemをVolumeへ保存できるようになりました。コンテナが終了したあともVolumeにgemが残ります。Gemfile にgemを追加して bundle install したいときは、次のようにコンテナを起動すればOKです。

$ docker-compose run app --rm bundle install

[2017-06-11 追記]bundle install する場所について

bundle installDockerfile に書くと、イメージのビルド時にインストールされたgemがイメージに入ります。この時点ではgemを保存するVolumeが設定されていないのですが、docker-compose コマンド経由でコンテナを起動すると、Volumeがgem保存先にマウントされます。このVolumeが上述の説明で使っている名前付きボリュームだと、マウント先に存在するファイルはVolumeへコピーされます*3

しかし、コンテナ起動前後でgemの保存の仕組みが変わるのはわかりにくいです。Dockerfile 内に bundle install を書くのではなく docker-compose run --rm app bundle install のように明示的にVolumeへgemをインストールする、という方法のほうがわかりやすそうです。

rails, rake コマンドの立ち上がりが遅い

RailsにはデフォルトでSpringというアプリケーションプリローダが入っています。Springを起動させておくと、rails, rake のようなコマンドの実行を高速化できます。現在、Springのことを考慮していないので、APIサーバとは別にSpring用のコンテナを立ち上げるようにします。

実際の作業内容では、この記事が参考になりました。ほとんどこの記事のとおりやっています。

tech.degica.com

version: '3'
services:
  # dbの設定...

  app: &app_base
    build: .
    command: bin/rails s -p 3000 -b "0.0.0.0"
    depends_on:
      - db
    ports:
      - "3000:3000"
    stdin_open: true
    tty: true
    volumes:
      - .:/usr/src/app
      - bundle_cache:/usr/local/bundle
  spring:
    <<: *app_base
    command: bin/spring server
    ports: []
volumes:
  bundle_cache:

bin 配下にある railsrake のようなbinstubにSpringの処理を差し込み、叩くコマンドを省略できるようにする機能があるので、それを実行しておきます。

$ docker-compose exec spring bundle exec spring binstub --all
* bin/rake: spring inserted
* bin/rails: spring inserted

docker-compose upappspring のコンテナを立ち上げたあと、実行中の spring コンテナ内でSpringを通じたコマンドの実行ができます。

$ docker-compose exec spring bin/rails console

[参考]spring コマンド実行時にエラーが出る場合

もし .bundle/config 内で BUNDLE_DISABLE_SHARED_GEMS: 1 という設定がある場合は削除する必要があります。この設定があると、Springを通じたコマンド実行時にうまくgemの場所を解決ができず次のエラーが出てしまいます。

Could not find rake-12.0.0 in any of the sources
Run `bundle install` to install missing gems.

あんまりないと思いますが、以前に bundle install --path vendor/bundle したときに自動で BUNDLE_DISABLE_SHARED_GEMS が設定に入っていてハマりました…

結果

Dockerfile は次のようになりました。

FROM ruby:2.4.1

ENV APP_HOME /usr/src/app
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME

COPY Gemfile \
     Gemfile.lock \
     $APP_HOME/

ENV BUNDLE_GEMFILE=$APP_HOME/Gemfile \
    BUNDLE_JOBS=4
RUN bundle install

docker-compose.yml は次のとおりです。

version: '3'
services:
  db:
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
    image: mysql:5.7
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
  app: &app_base
    build: .
    command: bin/rails s -p 3000 -b "0.0.0.0"
    depends_on:
      - db
    ports:
      - "3000:3000"
    stdin_open: true
    tty: true
    volumes:
      - .:/usr/src/app
      - bundle_cache:/usr/local/bundle
  spring:
    <<: *app_base
    command: bin/spring server
    ports: []
volumes:
  bundle_cache:
  mysql_data:

Rails+MySQLなAPIサーバの開発環境をDocker Composeで作る

先日、"Quickstart: Compose and Rails"の手順にしたがいながら、Docker ComposeでRails+MySQLがとりあえず動くような環境を作りました。

blog.kymmt.com

今回は、RailsのAPIサーバの開発環境をもうちょっといい感じにDocker Composeで作ってみます。DBにはMySQL 5.7を使います。

ディレクトリ構成

コマンド rails new sample_app --api --db=mysql でRailsのプロジェクトツリーを作り、ルートに Dockerfiledocker-compose.yml を置きます。

.
├── Gemfile
├── Gemfile.lock
├── app
│   └── ...
├── config
│   ├── database.yml
│   └── ...
├── db
│   ├── migrate
│   │   └── ...
│   ├── seeds.rb
│   └── ...
├── Dockerfile
├── docker-compose.yml
└── ...

イメージの作成

最終的に立ち上げたいDockerコンテナは次のふたつです。

  • app
    • APIモードのRailsの実行環境
  • db
    • MySQL 5.7の実行環境

このうち、db は既存のMySQL公式イメージを使ってコンテナを立ち上げます。app は次の Dockerfile を書いてイメージを作ります。

FROM ruby:2.4.1-onbuild

ここでは、Ruby 2.4.1の公式イメージ*1、特に ruby:2.4.1-onbuild というイメージを使います。

Rubyの公式イメージは buildpack-deps というイメージをもとにしています。この buildpack-deps はRubyやPythonのライブラリのインストールに必要となりやすいライブラリをあらかじめインストールする便利なイメージです。

MySQLをRubyから利用するために、本来は libmysql-dev というライブラリをインストールする必要があります。しかし、このライブラリは buildpack-deps があらかじめインストールしているので、今回の Dockerfile には明示的に書かないでも大丈夫です。

このあと、本来はホストに存在するRailsアプリケーションのプロジェクトツリーを app イメージ内にコピーする必要がありますが、この処理を Dockerfile に書いていません。これは、app のもととなるイメージに ruby:2.4.1-onbuild を使っているからです。このイメージの Dockerfile はおおむね次のようになっており、処理が ONBUILD で記述されています。ruby:2.4.1-onbuild では Gemfile のコピーやBundlerでの依存gemインストール、プロジェクトツリーのコピーといった定型作業を ONBUILD で指定してあります。ONBUILD を持つイメージをもとに作った新たなイメージからコンテナをビルドしたあとに、ONBUILD で指定した処理が実行されるようになっています。

FROM ruby:2.4

RUN bundle config --global frozen 1

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

# このイメージをもとにしたイメージからコンテナをビルドしたあとに実行する
ONBUILD COPY Gemfile /usr/src/app/
ONBUILD COPY Gemfile.lock /usr/src/app/
ONBUILD RUN bundle install
ONBUILD COPY . /usr/src/app

ちなみに、もし buildpack-deps で入るもの以外のライブラリをインストールしたい場合は、今回書いたDockerfile に次のように追記する必要があります。

RUN apt-get update -qq && apt-get install -y \
    build-essential \
    nodejs \
 && rm -rf /var/lib/apt/lists/*

ここでは、Debianの公式パッケージのビルドに必要な build-essential をインストールしています。さらに && でつないでインストールしたいパッケージの名前(たとえば nodejs)を指定します。このあと、さらに rm -rf var/lib/apt/lists/* を実行していますが、これはベストプラクティスとされている方法であり、APTのキャッシュを削除することでイメージのサイズを削減しています。

Composeファイルの作成

appdb をコンテナとして起動するためにComposeファイルを作ります。

version: '3'
services:
  db:
    image: mysql:5.7
    volumes:
      - mysql_data:/var/lib/mysql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
    ports:
      - "3306:3306"
  app:
    build: .
    command: bin/rails s -b "0.0.0.0"
    volumes:
      - .:/usr/src/app
    ports:
      - "3000:3000"
    depends_on:
      - db
volumes:
  mysql_data:

ファイルの末尾で volumes を指定し、Docker Engineがサポートする名前付きボリュームとして mysql_data を作成しています。ボリュームというのはコンテナ間で共有できるデータを保存する仕組みです。ボリュームはコンテナとは独立して作成するため、たとえそのボリュームを使う db コンテナが破棄されてもデータはホストに残ります。

今回のComposeファイルでは、名前付きボリュームとして mysql_data を作成し、db の設定にある volumesmysql_datadb/var/lib/mysql ディレクトリにマウントする形で利用しています。

app では depends_on という設定項目に db を指定しています。これによって、依存先のサービスをコンテナとして立ち上げてから、本サービスを立ち上げるようになります。

Railsのデータベース設定

Railsの config/database.yml で、開発/テスト環境のDBを次のとおり設定します。

default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: root
  password:
  host: db

development:
  <<: *default
  database: sample_app_development

test:
  <<: *default
  database: sample_app_test

docker-compose.ymlapp の依存先として db を指定し、host: db と指定することで、コンテナ db へ接続することができます。

コンテナの立ち上げ

次の要領で appdb の各コンテナを立ち上げます。

  1. APIサーバで使うDBをセットアップ(DBの作成、テーブルの作成、初期データの投入など)する
  2. Docker Composeでコンテナ群を立ち上げる

コンテナ群を立ち上げる前にDBをセットアップしておかないと、APIサーバにリクエストがあったときにエラーが発生します。

次のコマンドでDBをセットアップします。

$ docker-compose run --rm app bin/rails db:setup

このコマンドによって、次のように処理が進みます。

  • app が依存する db のコンテナを先にイメージから立ち上げる
  • app のコンテナを Dockerfile から立ち上げる
  • app のなかのワーキングディレクトリ /usr/src/appbin/rails db:setup を実行し、DBのセットアップを実行する

これでAPIサーバを動かせるようになりました。最後に次のコマンドを叩き、appdb のコンテナを立ち上げると、RailsとMySQLが動きだします。

$ docker-compose up

curl http://localhost:3000/users/1 のようなリクエスト送信で疎通が取れます。また、ホストの Dockerfile を置いているディレクトリをコンテナ内の該当ディレクトリにマウントしているので、ホストでコードを編集すると直にコンテナ内に反映されます。

開発中に Gemfile を更新したときは次のコマンドでイメージをビルドし直します。

$ docker-compose build app

RSpecのテストは次の要領で実行できます。

$ docker-compose run --rm app bin/rspec

*1:Railsの公式イメージは存在しますが、今では非推奨となっています

"Quickstart: Compose and Rails"を読みながらDocker ComposeでRails+MySQLの環境を作る

次の記事などでDockerを使ってRailsの開発環境を構築しているのを見て、自分でもやっておこうと思ったので、やってみました。

dev.classmethod.jp

公式ドキュメントの"Quickstart: Compose and Rails"を読みながらやります。

docs.docker.com

前提

まったく同じ手順でやるだけというのもなんなので、一度おなじ手順でやったあとに、次のような変更を加えてやってみました。

  • Rubyのバージョンを2.3.3から2.4.1にする
  • Railsのバージョンを5.0.0.1から5.1.1にする
  • PostgreSQLのかわりにMySQLを使う

以降の記述でとくに言及していないファイルや手順は“Quickstart: Compose and Rails”での説明とおなじことをやっています。

DockerとDocker Composeは次のバージョンを使います。

  • Docker
    • 17.03.1-ce
  • Docker Compose
    • 1.11.2

作業

Dockerfile

Railsの環境を含むイメージを作るためにDockerfileを書きます。

FROM ruby:2.4.1

RUN apt-get update -qq && apt-get install -y build-essential libmysqlclient-dev nodejs

RUN mkdir /myapp
WORKDIR /myapp

ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock

RUN bundle install

ADD . /myapp

Ruby 2.4.1の公式イメージがあるので、タグ2.4.1を指定します。Rubyのイメージの大元はDebianのイメージなので、apt-getで必要なパッケージをインストールしていきます。

RubyのMySQLクライアントであるmysql2というgemがlibmysqlclient-devというライブラリに依存するので、このライブラリをインストールしておきます。

Gemfile

さきほどのDockerfileでイメージへコピーしていたGemfileは、Railsの5.1.1を使うことから次のような内容とします。

source 'https://rubygems.org'
gem 'rails', '5.1.1'

docker-compose.yml

複数のコンテナをDocker Composeで立ち上げるためにComposeファイルdocker-compose.ymlを書きます。

version: '3'
services:
  db:
    image: mysql
    environment:
      MYSQL_ROOT_PASSWORD: password
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

2017年5月現在、Composeファイルはバージョン3が最新版かつ推奨*1なので、version: 3を指定しておけばよさそうです。

MySQLを使うとき、環境変数にMYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD, MYSQL_RANDOM_ROOT_PASSWORDのどれかを指定する必要があります。詳しくはMySQL公式イメージのページにある"Environment Variables"の節を参照してください。今回はMYSQL_ROOT_PASSWORDでrootパスワードを指定しておきます。

イメージのビルド

手順どおりやればOKです。イメージビルド後にRailsプロジェクトを作成するときMySQLを使う設定ファイルを生成するために、オプション--databaseを指定しておきます。

$ docker-compose run web rails new . --force --database=mysql --skip-bundle

データベースへの接続設定

config/database.ymlpassworddocker-compose.ymlで環境変数に指定したパスワードを書いておきます。実際はパスワードも環境変数などの間接的な方法で管理することになるでしょう。hostdocker-compose.yml内で指定したDB用コンテナの名前dbを指定します。

default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: db

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test

はじめはDBがないので作る必要があります。Rails 5ではrailsコマンドでタスクを実行できます。

$ docker-compose run web bin/rails db:create

コンテナの起動

最後に次のコマンドでweb, dbのコンテナを立ち上げて、ブラウザでlocalhost:3000にアクセスすればRailsのデフォルトページが見られます。

$ docker-compose up

Rails を使った EC アプリケーション開発について学べる本 "Take My Money" を読んだ

読みました。

pragprog.com

どんな本か

副題が “Accepting Payments on the Web” となっているように、決済 (payment) システムをもつ Web アプリケーションを作る方法について説明しています。『達人プログラマー』などでおなじみの The Pragmatic Bookshelf シリーズの本です。

チケット販売システムの開発を通して、次のような具体的な話題に触れています。基本的には Rails 5 を使ってロジックからビューまでを開発していきます*1

  • 決済システムの実装
    • ショッピングカート
    • 外部決済サービスとの連携
    • サブスクリプション機能
    • エラーケースとその対策
  • 管理画面の実装
    • 返金など注文の操作
    • 認証/認可
  • その他実務上必要な項目
    • 監査用ログの保存
    • PCI DSS への準拠

なぜ読んだか

この本の発売当時(2017-01 ぐらい)に社の Slack で紹介されていたのを見かけて、その存在を知りました。半年ぐらい前から EC 系サービスの開発に携わっていて、その分野に関して一般的な知見をあらためて得たいなと思い、読むことにしました。

全体を読むのにかけた時間は 2 週間ぐらいです。pragprg.com のサポートページに置いてあるサンプルコードを見つつ、実際に動かしたりしながら読んでいました。

どうだったか

おさえている話題の幅が広かったです。EC 系のアプリケーションに必要なデータのモデリングや Rails で開発するうえでの実装の工夫から各種定番の gem についてまで、ひととおり説明がありました。自分が携わっているサービスとの共通点/相違点を認識しながら、こういうやりかたもあるのかという発見や知識の整理ができました。

紹介されている外部決済サービスや gem の使いかたの説明は風化が速い部分になってしまうとは思いますが、データモデリングの部分などはそれなりに長く通用する有用な具体例を示してくれているように感じます。

個人的に有用だと思った点をまとめておきます。

データのモデリング

商品在庫

この本では商品在庫をカウンタカラムで管理するのではなく、1 件ずつレコードを作るという方法がよいと述べていました。つまり、次のテーブル定義のような在庫用カウンタ tickets_count をもつものではなく、

# 採用しない例
create_table "tickets" do |t|
  # ...
  t.integer "tickets_count"
  t.integer "price_cents", default: 0, null: false
end

在庫ごとにレコードを作り、status カラムに enum で unsold, sold といったデータを持たせる次のような定義を採用していました。

# 採用する例
create_table "tickets" do |t|
  # ...
  t.integer "status"
  t.integer "price_cents", default: 0, null: false
end

これについては、開発当初はカウンタを持たせるのが簡単だが、だんだんと

  • 在庫数
  • カートに入っている商品数
  • 売上数

…のようにカウンタの種類が増えていって管理がつらくなる、という理由があるようです。代わりに、在庫ごとにレコードを作るのが商品グループを操作するうえではより簡単な方法だと述べています。

返金

返金のモデリングについては、「返金のおかげでこの本を書こうと思ったぐらい返金はだるい」(意訳)と述べていることから、重要かつ工夫のいる部分であることがわかります。まず、この本で開発するアプリケーションでは、決済したときに次のようなテーブルのレコードを作成しています。

create_table "payments" do |t|
  # ...
  t.integer "user_id"
  t.integer "price_cents", default: 0, null: false
  t.integer "status"
  t.string  "payment_method"
  t.json    "full_response"
end

返金のモデリングでは、次の 2 とおりの方法

  • payments テーブルに返金額を保存するカラムを追加する方法
  • 返金データとして price_cents が負の値の payments レコードを作成する方法

を選ぶ余地があります。

  • これまでに作成したレコードは不変のものとしたい
  • 決済処理時のペイメントゲートウェイからの JSON レスポンスを各レコードに保存しておきたい

と言った理由から、この本では返金データも 1 件の payment レコードとする方法をとっています。また、返金を表すレコードのために、返金の対象となった元の決済レコードへの参照用カラムを持たせています。

change_table "payments" do |t|
  t.references :original_payment, index: true
end

これで、複数回の一部返金にも対応できるようになります。これは、ER 図を描くと自分自身へのループ参照で表現されるようなイメージです。

実装上の手法

workflow による薄いコントローラ

コントローラにロジックを書かないために、workflow と称したクラスを app/workflows に切り出してロジックを分離する、という方法が紹介されています。

# app/controllers/shopping_carts_controller.rb
class ShppingCartsController < ApplicationController
  def update
    # performance はある映画のある時間における上映
    workflow = AddsToCart.new(user: current_user, performance: performance, count: params[:ticket_count])
    workflow.run # workflow に実際のロジックを委譲する
    # ...
  end
end

# app/workflows/adds_to_cart.rb
class AddsToCart
  # initialize など...
  def run
    # 実際にカートへ商品を追加するロジックを書く
  end
end

調べてみると Trailblazer の Operation も同じような発想に基づいているようです。

複数の決済方法

EC サービスを開発していると、決済方法は徐々に増えるものです。この本では、最初は Stripe だけを決済サービスとして利用していますが、途中で PayPal を追加します。このように決済方法が増えてきたときの対処として、上で説明した workflow を用いて解決しています。具体的には、抽象的な決済用 workflow を用意し、テンプレートメソッドでそれぞれの決済サービス用の workflow を実装しています。

# app/workflows/purchases_cart.rb
class PurchasesCart
  # ...
  def run
    update_tichets
    create_payment
    purchase
    calculate_success
  end
end

# app/workflows/purchases_cart_via_stripe.rb
class PurchasesCartViaStripe < PurchasesCart
  def purchase
    # Stripe の Web API クライアントを使って決済
  end
end

# app/workflows/purchases_cart_via_pay_pal.rb
class PurchasesCartViaPayPal < PurchasesCart
  def purchase
    # PayPal の Web API クライアントを使って決済
  end
end

そして、これらの workflow を作成するファクトリはメソッド create_workflow に切り出していました。次のような感じで使います。

class PaymentsController < ApplicationController
  # ...
  def create
    workflow = create_workflow(params[:payment_type]) # ビューからの payment_type で具象 workflow 作成
    workflow.run
    # ...
  end

  private

  def create_workflow(payment_type)
    case payment_type
    when "paypal"
      PurchasesCartViaPayPal.new( # 引数
      )
    else
      PurchasesCartViaStripe.new( # 引数
      )
    end
  end
end

定番の gem

アプリケーションを作るうえで必要になる定番の gem がいろいろと紹介されていました。次の gem は知らなかったので参考になりました。

  • money-rails
    • 金額計算や通貨変換に関する API を提供する money という gem を Rails へ統合する
  • Administrate
    • Thoughtbot 謹製の管理画面作成フレームワーク
    • 本の中では ActiveAdmin が使われているものの、こちらについても軽く言及があった
  • Pundit
    • Policy クラスでコントローラアクションに認可機構をかけられる
  • PaperTrail
    • ActiveRecord モデルのデータの変更を追跡してバージョン管理する
  • bundler-audit
    • アプリケーションに導入している gem のうち Gemfile.lock に書いているバージョンのものに脆弱性が報告されているかチェックする

おわりに

EC アプリケーションというドメインに絞って具体的な開発方法が説明されているニッチな本だと思いますが、個人的には参考になる部分がそれなりにあってよかったです。上に述べたような話が気になる人は読んでみてください。

*1:「クライアントサイド、サーバサイド両方 Rails 5 を使って開発しています」と書いていましたが、表現がよくないので修正しました