開発備忘録 2018/10/25
RSpec で example の一覧を出力する
RSpec の -f (--format) オプションを使って出力形式を指定します。
指定できる形式として p (progress) や d (documention) などがありますが、今回は d を指定します。(デフォルトは p)
bundle exec rspec -f d spec/controller/hoge_controller_spec.rb GET #index renders the :index template
他にも色々オプションがあるみたいですが、まだ試せていないので後日書きます。
Faker でダミーデータをつくる
ランダムな数字
Faker::Number.number(10) # 10桁のランダムな数字の組み合わせ => "1968353479"
ランダムな文字列
Faker::Lorem.characters(10) # 10桁のランダムな英数字の組み合わせ => "ang9cbhoa8"
他にも Faker::Games::Pokemon や Faker::Games::Zelda など、変わり種がたくさんありますが、ドキュメントを検索する上ではノイズですね……。
開発備忘録 2018/10/24
MySQL で SELECT の結果に対して UPDATE を適用する
status
が temp
のレコードを全て active
にする。
UPDATE users, ( SELECT users.id FROM users WHERE status = 0 # 0: temp, 1: active, 2: leaved ) AS temp_users SET users.status = 1 WHERE users.id = temp_users.id
FactoryGirl の transient について
FactoryGirl において一時的な attribute を定義することができる。
transient で定義した attribute はモデルを build (create) した際にセットされることはなく、また attribute_for
で attribute を取り出すときには無視される。
何のためにこの機能が提供されているかというと DRY に書くためである。
例えば、以下の FactoryGirl のコードを DRY にする方法について考えてみる。
factory :character do name { "Jack (job)" } trait(:knight) do name { "Jack (knight)" } end trait(:fighter) do name { "Jack (fighter)" } end trait(:wizard) do name { "Jack (wizard)" } end end
job
に応じて name
の表記が微妙に変わるというものだが、今回の場合だと name { "Jack" }
の部分が重複していて DRY ではない。
これが transient を使うとこう書ける。
factory :character do transient do job "job" end name { "Jack (#{job})" } trait(:knight) do job "knight" end trait(:fighter) do job "fighter" end trait(:wizard) do job "wizard" end end
job
を transient に定義しておいて、trait で job
の値を更新するというわけだ。
ちなみに job
の値を外から渡したい場合はこう書く。
create(:character, job: 'thief')
また callback で transient を利用したい場合は第二引数が transient なのでそれを利用する。
factory :character do ... after(:create) do |character, evaluator| character.name += " #{evaluator.job}" end end
開発備忘録 2018/10/23
Web サイトを訪れたユーザーを特定するための方法
見出しの内容について本日相談を受けました。
私は Web にはあまり詳しくはないのですが、色々調べたので今日はそれについて書いていこうと思います。
Cookie
まずは Cookie を利用した方法です。
サイトへのアクセス時にランダムな文字列を生成し、Cookie へセットします。
ユーザーは次回のアクセス時にその Cookie を渡して通信を行うので、その Cookie の値からユーザーを判断するといった方法です。
少し前までは広告からアクセスしてきたユーザーを特定するためにこの方法が使われたりしていましたが、最近はサードパーティ Cookie (違うドメインの Cookie) を禁止するブラウザが増えてきたので、あまり使われなくなっているみたいです。
ブラウザフィンガープリント (デバイスフィンガープリント)
フィンガープリントを利用した方法です。
そもそもフィンガープリントって……?
フィンガープリントとはコンテンツの内容をハッシュ化したハッシュ値のことで、コンテンツの改ざん、すり替えを防ぐために使われます。
例えば、メールを送信するときに、送信者は本文の内容のハッシュ値を本文に加えて送信します。そして受信者は受け取ったメールの本文をハッシュ化し、それを送られてきたメールにくっついてきたハッシュ値と同値であるかを見ることで第三者に改ざんされていないかをチェックすることができます。
ブラウザフィンガープリントも考え方としてはほぼ同じで、こちらはブラウザから取得できる情報 (ブラウザの名前、OS のバージョン、使用しているプラグインの情報など) を組み合わせてハッシュ化したデータのことを指します。
ブラウザフィンガープリントは情報の組み合わせ方次第ではかなり高い精度でユーザーを区別することができるようです。
ただ OS のバージョンが上がったりするとブラウザフィンガープリントの値も変わってきてしまうので、厳密にユーザーを区別したい場合にはこの方法は向かないと思います。
端末識別子
端末識別子 (MACアドレスや UDID) を使って区別する方法ですが、こちらはオススメしません。
iOS に関しては Apple が公式に端末の UDID や MACアドレスの取得を禁止していますし (API は残っているが無効な文字列を返す・使用するだけでリジェクト対象)、Android も MACアドレスの取得は現在できないようになっています。
昔はこの方法を取ることもできたという、一応ご紹介です。
開発備忘録 2018/10/22
[RSpec] テストを保留にする skip と pending の違い
skip
example は実行されず、そのステータスは pending になります。
describe "an example" do it "is skipped" do skip end end => 1 example, 0 failures, 1 pending
pending
example でエラーが発生するとそのステータスは pending となり、パスすると逆にステータスが failure になります。
describe "an example" do it "is implemented but waiting" do pending("something else getting finished") fail end end => 1 example, 0 failures, 1 pending describe "an example" do it "is implemented but waiting" do pending("something else getting finished") expect(1).to be(1) end end => 1 example, 1 failure
使い分け
何らかの事情でいまはパスさせることができないけどいつか修正するよ、といったテストには pending を使い、それに当てはまらない場合は skip を使うのがいいと思います。
任意のクラスを継承しているかを調べる
ActiceRecord を継承した User
クラスが存在し、それが ActiveRecord::Base
を継承するのかを知りたい。
class User < ActiveRecord::Base; end
is_a?
オブジェクトが引数で受け取ったクラスのインスタンスであれば true、そうでなければ false を返します。
user = User.find(1) user.is_a?(ActiveRecord::Base) => true
ancestors.include?
インスタンスからではなく、クラス自体が任意のクラスを継承しているかを知りたいとき。
ちなみに ancestors
メソッドは親クラスを配列にして返します。
User.ancestors.include?(ActiveRecord::Base) => true
開発備忘録 2018/10/19
Ruby の代入演算子 ||=
Ruby の実装でたまに見かけるこの記述について調べてみた。
user ||= fetch_user
上記の式は user
が未定義だと fetch_user
が呼び出されて user
に代入され、そうでないなら user
を返す。
何故こうなるのかが分からなかったが、どうやらこの式は以下のように展開されるらしい。
user || (user = fetch_user)
Ruby の場合 ||
は左辺が真なら右辺は評価しないという性質があるので、user
が定義済みなら user
を返し、そうでないなら fetch_user
が呼び出され代入される、という仕組みになっていたようだ。
RSpec のモック化
Twitter からユーザーのツイートを取得するクライアントが実装されており、それを使ってユーザーのツイート一覧を表示する Controller があるとする。
class TweetsController < ApplicationController def index twitter_client = TwitterClient.new render json: { tweets: twitter_client.fetch_tweets } end end
この Controller のテストを書くときに、ツイートを取得する部分 (twitter_client.fetch_tweets
) をモック化する必要があるが、RSpec の場合はこう書く。
tweet_mock = %i[ 'こんにちは' 'はじめまして' ] allow(twitter_client).to receive(:fetch_tweets).and_return(tweet_mock)
これにより fetch_tweets
は tweet_mock
を返すようになり、テストが書けるようになる。
ちなみに allow
は expect
と書くこともでき、両者の違いは allow
は fetch_tweets
が呼びだされていなくてもエラーにはならないが、expect
の方は fetch_tweets
が呼び出されていないとエラーになる。
Ruby における Hash の定義方法
{'key' => 'value'} => {"key"=>"value"} {:key => 'value'} => {:key=>"value"} {key: 'value'} => {:key=>"value"}
最後の書き方は Ruby 1.9 以降の書き方ですが、いまはもうこの書き方が主流な感じがしますね。
最初と最後の書き方では key が文字列になるかシンボルになるかの違いがありますが、可能なら key はシンボルで定義した方がいいと思います。
Ruby のシンボルはただの数値 (:key なら 256348 という感じ) なので、文字列の生成と比べてコストが低いです。
またシンボルは何回呼び出してもひとつしか生成されない (皆んなが 1 つのオブジェクトを参照する) ので、そういった意味でもシンボルの方がパフォーマンスが高いです。
以上の理由から、hash を定義するときは key はなるべくシンボルで定義した方がいいですね。
開発備忘録 2018/10/18
acts_as_paranoid で Rails に soft delete を追加する
ActiveRecord の destroy
は物理削除でモデルを削除しようとします。
acts_as_paranoid という gem を使うことでこの挙動を論理削除に変えることができます。
使い方は非常にシンプルでモデルに acts_as_paranoid
を追加するだけ。
class Paranoiac < ActiveRecord::Base acts_as_paranoid end
これにより、destroy
を呼び出すと物理削除ではなく、deleted_at
カラムに timestamp を記録するような挙動に変わります。
ちなみに deleted_at
以外のカラムも指定可能で、また型も time
以外にも boolean
や string
を指定することが可能です。
destroy
の挙動の変化以外にも default_scope
は deleted_at
が NULL 以外を取得するようになります。
削除済みのレコードも取りたい場合は Paranoiac.with_deleted
で取得が可能です。
この他にも色々機能があるみたいですが、それはまた追々記事していこうかと思います。
ところで acts_as_paranoid と似たような機能を提供する gem に paranoia というものがありますね。
Paranoia is a re-implementation of acts_as_paranoid for Rails 3/4/5
と書いているのでたぶん paranoia の方が後発だけど、いまは acts_as_paranoid も Rails 4/5 サポートしてますし、結局どっちがいいんでしょう……。
開発備忘録 2018/10/17
In-App Purchase における自動更新型サブスクリプションの状態更新通知について
In-App Purchase では、サブスクリプションの状態に関する変更を App Store が通知してくれます。(※自動更新型のみ)
開発者はその通知を受け取り適切な処理を行うことで、サブスクリプションの状態を正しく更新することができます。
(以前はこの状態の更新を通知してくれる仕組みがなかったため、自分たちのサーバーから定期的に Apple のサーバーにポーリングする仕組みを整える必要がありました。)
App Store が POST してくれる通知の種類は以下の 5 つです。
種類 | 説明 |
---|---|
INITIAL_BUY | 初回の購読 |
CANCEL | Apple カスタマーサポートによって購読がキャンセルされた |
RENEWAL | 期限切れになっていた購読が再び更新された |
INTERACTIVE_RENEWAL | 期限切れになっていた購読をユーザーが意図的に更新した |
DID_CHANGE_RENEWAL_PREF | プランを変更した |
それぞれの通知について詳しく解説します。
INITIAL_BUY
アプリケーションが提供するプランをユーザーが初めて購読した際に通知されます。
CANCEL
Apple カスタマーサポートが購読をキャンセルした際に通知されます。
例えば、子供が誤ってサブスクリプションを登録してしまった場合などに、Apple に問い合わせて事情を説明することで購読をなかったことにしてくれるみたいですが、恐らくその際に通知されるものだと思われます。(実際に試したわけではないので、間違っていたらすみません……。)
ちなみにユーザーが設定アプリから自発的に購読をキャンセルした場合は通知されません。
RENEWAL
決済に不備があり購読が中断された後、Apple は何度か決済のリトライを行います。
その間にユーザーが決済情報を変更し、購読の更新に成功した場合にこの通知が POST されてきます。
INTERACTIVE_RENEWAL
期限切れになっていた購読をユーザーが自発的に更新した際に通知されます。
例えば期限切れになった後に、アプリケーションのプランの購入画面から再度購入した場合などに通知されます。
また、購読の更新に失敗し、期限切れになった場合にも通知されます。
DID_CHANGE_RENEWAL_PREF
プランを変更した際に通知されます。
自動更新型のサブスクリプションはサブスクリプション間にランク付けを行うことができます。
例えば、ベージックプランやプレミアムプランといった感じでグレードに違いをもたせます。
ちなみにグレードがベーシック < プレミアムだとして、ベーシックからプレミアムに移行した場合は「アップグレード」といって、即座にプランが変更されます。
一方でプレミアムからベーシックに移行した場合は「ダウングレード」といい、こちらは次回の購読の更新時にプランが切り替わります。
どちらの場合でも POST されてくる通知の種類は同じ (DID_CHANGE_RENEWAL_PREF
) です。