読者です 読者をやめる 読者になる 読者になる

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

2017-01-14(土)の Yokohama.rb Monthly Meetup #76 に参加したので、メモを書きます。1 週間たってしまった。

yokohamarb.doorkeeper.jp

Ruby レシピブック

今回はレシピ 244 から 247 まで読みました。システム寄りの話題が中心です。

レシピ 244

スレッドファイバの扱いかたについてです。

スレッドを使うときに気をつけるべき点は次のようなところでしょう。

  • あるスレッドが別のスレッドの変数を操作しないように、Thread#fork の引数としてスレッド固有のローカル変数を渡す
  • メインスレッドが勝手に終了するのを防ぐため Thread#join でスレッドが終了するのを待つ

ファイバは軽量スレッドのようなものです。スレッドは自動で他スレッドに制御が移りますが、ファイバが持つコードを実行するには Fiber#resume を実行し、制御を呼び出し元に戻すには Fiber#yield を実行する必要があります。Enumerable#each など内部イテレータをジェネレータのような外部イテレータに変換するコードとともに紹介されていました。

ちなみに、Ruby 1.9 以降では組み込みの Enumerator をジェネレータとして使うことができます。

sentence =<<EOF
lorem ipsum
hoge fuga fizz bazz
EOF
enumerator = sentence.each_line  # Enumerator を返す
loop { p enumerator.next }
# => "lorem ipsum\n"
# => "hoge fuga fizz bazz\n"

また、レシピのサンプルコードで Object#__send__ というメソッドが使われていました。これは、send という名前が一般的で再定義されやすいことから定義された Object#send の別名ということです。

レシピ 245

プロセスのフォークについてです。

レシピ内では fork して exec するサンプルコードが紹介されていました。この用途なら Kernel.#spawn でもよいという話がありました。子プロセスの終了を待つなら Process.waitpid2 を使う必要があります。

pid = spawn('job')
exitpid, status = *Process.waitpid2(pid)  # 'job' が終了するまで待つ

レシピ 246

プロセスがシグナルを受けたときの動作を設定するシグナルハンドラについてです。

有名な SIGINT などの他に SIGUSR1, SIGUSR2 というはシグナルがあって、これはユーザ定義用のものだそうです(参照:シグナル (ソフトウェア) - Wikipedia)。

Pry など REPL 上で Singal.trap(:USR1) { puts 'USR1' } などとして、killSIGUSR1 を送ると、シグナルハンドラが動作していることが確かめられます。

レシピ 247

プロセスをデーモン化する方法についてです。

注意点として、デーモンの場合、必ず Kernel.#exit! で終了するようにします。exit!ensure 節など後処理も飛ばしてプロセスを終了します。これを使わないと stdio ライブラリのバッファが 2 回フラッシュされてしまうなどの問題が起きるそうです。

そもそもなぜデーモンなのか? nohup ではだめなのか?という話に展開して、次のページを皆で見ていました。

もくもく

後半はもくもく会でした。僕は swagger-docsSwagger UI を使って、Rails で作った API のドキュメント化を試していました。

懇親会

miyohide さん誕生日おめでとうございました。


次回は 2017-02-18(土)です。次のリンクから申し込めます。

yokohamarb.doorkeeper.jp

冪等な更新処理の API エンドポイントでは PUT を使うとよさそう

結論

  • star や like を付与するような、冪等性を持つアクションのための API エンドポイントでも POST はよく使われる
  • が、HTTP メソッドの意図を考慮すると、冪等な更新処理の API エンドポイントにアクセスするときの HTTP メソッドは、POST より PUT がよりよさそう

何をやったか

Twitter の like や GitHub の star を付与するようなアクションを、API エンドポイントとしてどのように表現すればよいのかというのを考えていました。

データを編集する操作なので POST, PUT, PATCH のいずれかで、さらに新規に like を表すリソースを作ると考えると、POST か PUT かなと思いました。この二つのどちらがよさそうか、既存 Web サービスの公開 API を見ながら調べてみました。

調査結果

POST

Twitter や Instagram の like は POST でした。

Twitter だと、リソース favorites/create を POST で作成するエンドポイントとなっています。

POST https://api.twitter.com/1.1/favorites/create.json

データを編集するための HTTP メソッドには POST はよく使われます。今回のような操作でも POST が使われている場合が多いようです。ただ、POST は送信したデータを指定 URI に従属させることを意図しています*1。たとえば

POST /v1/users

users の配下に新しくユーザのリソースを作成するようなパターンです。

なので、Twitter の API のように、直接リソース favorite/create を URI で指定するのは、POST の本来の意図とは若干異なってくるのかなと感じました。

PUT

GitHub API v3 の star は PUT でした。

リポジトリに対する star では、PUT で user が持つ starred/:owner/:repo というリソースを作成するエンドポイントとなっています。

PUT /user/starred/:owner/:repo

また、Gist に対する star では、PUT で star を作成するエンドポイントとなっています。

PUT /gists/:id/star

PUT を使う場合の要件として

  • 冪等性を担保すべき*2
  • URI でリソースを直接指定すべき*3

があります。

よって、次の理由

  • 一般に star や like は一度付与したら何度付与しても増えたりしない(冪等な処理である)
  • リソース (:repostar) を URI 内で直接に指定している

から、今回のようなエンドポイントでは POST よりも適しているのかなと思いました。

参考資料

Web API: The Good Parts

Web API: The Good Parts

次の Qiita 記事がかなり参考になりました。

qiita.com

*1:『Web API: The Good Parts』2.3.2 節参照

*2:http://stackoverflow.com/questions/630453/put-vs-post-in-rest

*3:『Web API: The Good Parts』2.3.3 節参照

2016 年ふりかえり

2016 年をふりかえらないと 2017 年が始まらないので、ハイライトでふりかえります。

上半期と下半期で在籍していた会社が違うので、そのくくりで分けてます。

上半期

個人活動

2015 年の秋ぐらいから触っていた Ruby で引き続き遊んでいました。

読書メーターにある自分の読書記録を自分でハンドリングしたくて、スクレイピング用 gem を作ったりしてました。

github.com

スクレイピングは結構つらいということがわかりました。なんやかんやあって結局ブクログに移行しました。

blog.kymmt.com

また、hatenablog gem や、それを使ったブログ編集用ツールに pull request をいただきました。ありがとうございます!!!

blog.kymmt.com

ほかには、ひとりで Ruby を書いてるだけではなく外に出ないといかんと思い、Yokohama.rb に参加し始めました。なぜ横浜かというと、この当時は横浜市内に住んでいたからです。当たり前なのですが Ruby にとても詳しい方が多く、感動した覚えがあります。また、ここで id:kurotaky さんに出会うのが、この後の伏線になります。

blog.kymmt.com

毎回参加記事を書いていたら、レシピブック読書会まとめマン的役割を若干確立しつつある感じになっています。

インフラ周りに慣れるべく、独自ドメインを取って設定したり、Let's Encrypt で証明書取得して nginx のサーバ設定を書き換えたりもしていました。

blog.kymmt.com

blog.kymmt.com

業務

MS Word と Eclipse を 7:3 ぐらいで使っていた記憶があります。

転職活動

5 月ぐらいに本格的に転職しようという気持ちになって、Yokohama.rb で kurotaky さんから聞いていた GMO ペパボのペパランチョンを思い出し、参加させてもらいました。

pepabo.com

これきっかけでペパボがとてもよさそうという感想に至り、6 月下旬にカラーミーショップのエンジニアとして応募したところ、8 月頭に内定しました。人生なにがあるかわからないですね。

前職は 9 月末で退職、10 月からはペパボ所属となりました。

下半期

ペパボカレッジ

入社後 1 か月かけて、福岡でペパボカレッジという研修を受講しました。詳しくは同期の qkake とともに社のブログにまとめたのでご参照ください。

tech.pepabo.com

業務

カラーミーショップというネットショップ作成サービスを開発しているグループで、カート画面を新しくするプロジェクトに加わって開発作業しています。Rails, PHP, Angular などを使っています。実力的にまだまだで、日々反省点も多いです。粛々とできることを増やしていきたいという気持ち。

個人活動

hatenablog gem の 0.4.0, 0.5.0 をリリースしました。8 月に引き続き、pull request もいただけたので、ありがたかったです。

blog.kymmt.com

blog.kymmt.com

ペパボ福岡支社でペパボカレッジ受講中の 10 月に Fukuoka.rb に参加させていただきました。

blog.kymmt.com

社のアドベントカレンダーに参加して、業務で得た知識を膨らませてアウトプットしてみました。

blog.kymmt.com

所感

アウトプットしたり外に出ていったのが功を奏した 1 年だったように思います。まさか興味本位で Ruby を触りはじめたころに「1 年後に Ruby を書く仕事をやっている」とは思っていなかったというのが本当のところです。

仕事を通じて常に学びがあるのでとてもよいのですが、それで社内に活動が閉じがちになってしまうのももったいないという気持ちがあります。今年はこれまでより (気楽に || 雑に) エントリなりコード片なりをアウトプットしたいと思います。

あとは pull request をもらってばかりなので、使っているライブラリなどに contribute できる余地があれば、できるだけしたいですね。

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

2016-12-10(土)の Yokohama.rb Monthly Meetup #75 に参加しました。

yokohamarb.doorkeeper.jp

次の流れでした。

  • 前半は Ruby レシピブック読書会
  • 後半は LT

今回は、参加者の @Nabetani さんがすでにメモを書いてくださっています。僕もメモしておきます。

qiita.com

レシピブック

今回はレシピ 240 から 243 まで読みました。おもにシステム寄りの Ruby プログラミングについてでした。

レシピ 240

システムのダイナミックリンクライブラリを Ruby から呼ぶためのレシピです。

システムライブラリを呼ぶためのライブラリとして dl が紹介されていますが、これは Ruby 2.0 以降では deprecated になっており、2.2 以降では削除されています。かわりに fiddle を使うとよさそうです。

また、macOS 環境では libc の代わりに libSystem.B.dylib を使う必要があります。

stackoverflow.com

たとえば、次のような感じで標準 C ライブラリの関数としておなじみの strlen が呼べます。

require 'fiddle/import'
module LibSystem
  extend Fiddle::Importer
  dlload 'libSystem.B.dylib'
  extern 'int strlen(char*)'
end
p LibSystem.strlen('abc')  #=> 3

レシピ 242

Ruby 付属のベンチマークライブラリである benchmark についてです。

@igrep さんからベンチマークにより便利な benchmark-ips を紹介してもらいました。与えたブロックの 1 秒あたりの繰り返し回数 (iteration per second: ips) を測定する gem です。評価値が大きいほど評価がよいので、直感的という話などをしていました。

github.com

軽く使いたいなら Benchmark#measure を使っておくと楽そうです。次のように書けます。

require 'benchmark'
puts Benchmark.measure { 'a' * 1_000_000 }

レシピ 243

シェルのコマンドを Ruby から起動するためのレシピです。

@Nabetani さんも書いておられますが、Kernel.#open の引数に | (パイプ)をつけて | df -k のように文字列を渡すと、IO.popen と同じ動きになることにびっくりしました。つまり、任意のコマンドを実行できるということです。この仕様については、次のドキュメントの最初にも

ファイル名 file が `|' で始まる時には続く文字列をコマンドとして起動し、 コマンドの標準入出力に対してパイプラインを生成します

と明記してあります。

さらに、これは macOS でも Windows でも実行できることを、前回に引き続き @igrep さんに Windows 環境で実験してもらいました!

File.open にはこのような仕様がないので、ファイル操作にはこちらを使っておいたほうが、万一のコマンドインジェクションは防げるので安心ということだそうです。

LT

LT 一本目は、先月と話がかなり被るのですが、僕が 10 月に受けていた GMO ペパボの第二新卒向け研修「ペパボカレッジ」について、社のテックブログに書いたエントリをもとにご紹介しました。

LT 二本目は、@ryonext さんから、先日の Re:Invent で発表された AWS のサービス群について紹介がありました。AWS は VPS から人工知能まで幅広いですね……。

懇親会

もう 12 月なので、忘年会と銘打たれていた気がします。来年もよろしくお願いします!!1


次回は 2017-01-14(土)です。次のリンクから申し込めます。ぜひぜひ。

yokohamarb.doorkeeper.jp

Ruby (on Rails) で使える enumeration 実装を比較してみた

こんにちは、GMO ペパボの人間です。これは pepabo Advent Calendar 2016 の 9 日目の記事です。

昨日は我らが CTL けんちゃんくんさん「gemビルドしようとして The validation error was 'yourgem-x.y.z contains itself (yourgem-x.y.z.gem), check your files list と出たとき」 でした。

今日は Ruby, とくに Rails で使える enumeration 実装

  • Enumerize
  • ActiveRecord::Enum

の機能を比較してみました。

動機

私事ながら、仕事で Rails アプリケーションの開発をするようになって 1 か月経ちました。

コードを読んでいるといろいろと勉強になることが多いのですが、Enumerize という enumeration の gem が使われており、boolean を返す predicate(述語)メソッドや Active Record の scope を生成できるので便利〜という気持ちになっていました。

一方 Rails にも enum (ActiveRecord::Enum) があるのをなんとなく知っていたので、これらの違いが気になり、調べてみることにしました。

以下のコード例では require 'enumerize' しているものとします。また、userUser のインスタンスとします。

Enumerize 概要

The Ruby Toolbox の "ActiveRecord Enumeration" カテゴリでは 1 位になっている gem です。

github.com

O/R マッパとは独立した gem なので、Active Record オブジェクトはもちろんのこと、プレーンな Ruby オブジェクトに対してもふつうに使えます。

次のようなコードで、クラスに enumeration を組み込めます。

class User
  extend Enumerize
  enumerize :membership, in: [:free, :regular, :premium]
end

ActiveRecord::Enum 概要

※名前が長いので以下 enum とします

Rails 4.1 から入った機能です。名前空間が示すとおり、Active Record オブジェクトで使うのが前提となっています。

Active Record を継承したクラスへ次のように enumeration を組み込めます。

class User < ActiveRecord::Base
  enum membership: [:free, :regular, :premium]
end

比較

上で例示した User のインスタンス user を使い、両者の主な機能について表にまとめてみました。

機能 Enumerize ActiveRecord::Enum
値の更新 user.membership = :member user.member!
文字列化 user.membership.text user.membership
デフォルト値の設定 default: :free(lambda も渡せる) DB のデフォルト値設定が別途必要
i18n 対応 デフォルトで対応 enum_help が別途必要
predicate メソッド生成 predicate: true で生成 デフォルトで生成
scope 生成 scope: true で生成し、名前変更も可能 デフォルトで生成
prefix, suffix 生成 predicate だけ生成可能 predicate, 更新メソッドに生成可能(ただし Rails 5.0 から)
序数の取得 user.membership.find_value(:free).value user.membership[:free]
複数値の保持 可能 不可能
テスト用のマッチャ あり なし

以下、かいつまんで見ていきます。

値の更新

enum は bang メソッドで更新できるのでちょっと短く書けて Ruby ぽいです。

user.membership = :regular  # Enumerize
user.regular!               # enum

デフォルト値の設定

Enumerize は enumeration の宣言部分でデフォルト値をオプションとして渡すことができます。

class User
  extend Enumerize
  enumerize :membership, in: [:free, :regular, :premium], default: :free
end

一方、enum ではマイグレーションの中でデフォルト値を設定する必要があります。

class AddMembershipToUsers < ActiveRecord::Migration
  def change
    add_column :users, :memebership, :integer, default: 0
  end
end

predicate, scope メソッドの生成

Enumerize は enumerize の宣言部分で predicate と Active Record の scope メソッドを生成するオプションを渡せます。scope は名前も変えられます。

class User < ActiveRecord::Base
  extend Enumerize
  enumerize :membership, in: [:free, :regular, :premium], predicates: true, scope: true
end
user.regular?  # predicate
user.with_membership(:premium)  # premium user だけ取得

# 自分で scope 名を指定できる
class User < ActiveRecord::Base
  extend Enumerize
  enumerize :membership, in: [:free, :regular, :premium], scope: :having_membership
end
user.having_membership(:free)  # free user だけ取得

一方、enum は自動で predicate, scope の両メソッドを生成してくれています。ただし scope の名前は変えられません。そのぶんシンプルともいえます。

class User < ActiveRecord::Base
  enum membership: [:free, :regular, :premium]
end
user.regular?  # predicate
user.premium  # premium user 取得

プレフィックス/サフィックス生成

Enumerize は enumeration の宣言部分で predicate メソッド生成時に prefix, suffix オプションを渡すと、predicate メソッドのプレフィックス、サフィックスを生成できます。

class User
  extend Enumerize
  enumerize :membership, in: [:free, :regular, :premium], predicates: { prefix: true }
end
user.membership_free?

# 自分でプレフィックス名を指定できる
class User
  extend Enumerize
  enumerize :membership, in: [:free, :regular, :premium], predicates: { prefix: 'member' }
end
user.member_free?

enum にも Rails 5.0 からプレフィックス/サフィックス生成機能が入りました。_prefix, _suffix というアンダースコア付きのオプションを渡すと predicate, 更新メソッドの両方にプレフィックス/サフィックスを生成します。

class User < ActiveRecord::Base
  enum membership: [:free, :regular, :premium], _prefix: true
end
user.membership_free!  # :free に更新

# 自分でプレフィックスを指定できる
class User < ActiveRecord::Base
  enum membership: [:free, :regular, :premium], _prefix: :member
end
user.member_premium?

複数値の保持

Enumerize は複数の値を保持できます。つまり、次のように、multiple オプションを渡すと、ある enumeration について複数状態を持てます。

require 'active_support/core_ext/object/blank'  # << の中で blank? 使っているので必要

class User
  extend Enumerize
  enumerize :subscriptions, in: [:newspaper, :magazine, :podcast], multiple: true
end
user.subscriptions << :newspaper
user.subscriptions << :podcast
user.subscriptions  #=> #<Enumerize::Set {newspaper, podcast}>

テスト用のマッチャ

Enumerize では RSpec などのマッチャが使えます

describe User do
  it { is_expected.to enumerized(:membership) }
end

まとめ

Enumerize のほうが ActiveRecord::Enum よりまだ機能は多いということがわかりました。とくに、Enumerize にはデフォルトの i18n 対応、デフォルト値設定や scope 名設定の柔軟さがあるのはよい点だと思います。独立した gem であることから、プレーン Ruby オブジェクトや Mongoid オブジェクトで使えるようになっているのもよいですね。

とはいえ、Rails 4.1 以降では、ActiveRecord::Enum で Rails の機能の一部として enumeraion の基本機能や predicate や scope の生成と言った機能が使えるようになっていることがわかりました。ですので、プロジェクトによっては、これぐらいのシンプルさで十分ということもあると思います。Rails 5.0 へのアップデートが必要になるものの、_prefix, _suffix も使えるようになっていることも知れたのでよかったです。

とくに結論はないですが、ご参考になればということで。興味を持った方は下記の公式ドキュメントをご参照ください。