Rails 4.0 の STI を development モードで使うとサブクラスがロードされない問題

単一テーブル継承はたまに使いますが、使うたびに何かしら問題が起きる気がします。

class Animal < ActiveRecord::Base
end

class Dog < Animal
end

class Cat < Animal
end

これらのクラスを(実際は色々と問題があるので別ファイルに分けて)実装し

Animal.subclasses

と書くと

[Dog, Cat]

が返ってきて欲しいし、実際 production 環境では動作するのですが development 環境で素直に実行すると

[]

と空の配列が返ってきます。理由は development モードではモデルは都度ロードされるからなので config/environments/development.rb の末尾に1行追加して常時読まれる状態にしてしまいます。

Rails.application.configure do
  # 略
  [Dog, Cat]
end

あまり綺麗ではありませんが、コードに影響ない範囲まで分離することはできました。

iOS8 系で強制終了したアプリ向けのプッシュ通知を受け取る方法

世のアプリケーションでは表題のような動作をしているものが色々と存在します。
しかし、自社での開発中に上の問題が発生したのでメモとして残しておきます。

構成

https://developers.google.com/cloud-messaging/ios/start

上記サイトのサンプルを元に動かしました。
GCM -> APNs -> 端末という流れになります。

症状

アプリケーションが起動中、バックグラウンド、OSにより終了された場合のいずれもプッシュ通知を受け取れるが、ユーザが強制終了した場合のみプッシュ通知が飛んでこない。

対策

送信側のコードで対策します。
GcmServerDemo/MasterViewController.swift を開き、以下の箇所を直します。

Before
  func getMessage(to: String) -> NSDictionary {
  // [START notification_format]
    return ["to": to, "notification": ["body": "Hello from GCM", "content_available" : true]]
  // [END notification_format]
  }
After
  func getMessage(to: String) -> NSDictionary {
  // [START notification_format]
    return ["to": to, "notification": ["body": "Hello from GCM", "content_available" : true], "priority": "high"]
  // [END notification_format]
  }

雑感

実際にこれでプッシュ通知は届くようになったのですが、なぜなのかと聞かれるとさっぱり分かりません。
ただ、今回のケースではこれで解決したので一応メモとして残しておきます。

参考までに plist に Required background modes の設定などせずとも動作しました。

Rails 5.0 beta-2 で Puma のコンフィグを適用させたい

Rails 4.2 以降は rails server コマンドで起動するサーバが localhost で立ち上がるため LAN 内の別マシンからアクセスさせたい場合は

bundle exec rails s -b 0.0.0.0

のようにコンフィグを設定する必要がありました。

Rails 5.0 からはデフォルトのサーバが Puma になるようですが beta-2 で試している限り上記コンフィグが必要な状況は変わっていないようです。

省略したい

毎回オプションをつけたくないので省略できないのかと思い config/puma.rb を次のように変更してみました。

#port        ENV.fetch("PORT") { 3000 }
bind "tcp://0.0.0.0:3000"

この状態で rails server を叩いたのですが、上記の設定は反映されません。しかしエラーにもならないので、どこかで設定が上書きされるか無視されているようです。

それならばと思い次のように記述してみました。

bundle exec rails s -C config/puma.rb

これはこれでコマンドが長く当初の趣旨からは外れてしまいますが、これならコンフィグを確実に読んでくれるはず…と思いきや -C オプションをつけると強制的にどこかに存在する rack/handler 以下を読むようになっており、ファイルがロードできないと怒られてしまいます。

解決策

と、いう話を同僚にしていたら回避可能なコマンドを探してきてくれました。

bundle exec pumactl start

これであれば config/puma.rb の設定を反映してくれるようです。

残った問題

しかし、上記コマンドで起動したローカルサーバは所謂 Rails の development モードのログを出力してくれません。出力を揃える方法は無いのか、もう少し調査してみます。

Android の cookie 管理と CookieManager と flush()

Android アプリケーションで cookie を管理する際、デフォルトの動作はアプリとは非同期で揮発性のメモリに展開された値を最大数分(?)程度の遅れで不揮発な領域に書き込みに行くようです。
この辺り、過去のバージョンの情報などが錯綜していて分かりにくかったのですが、実装自体は記事執筆現在でも変化していない様子。

何がまずいのか

普通にアプリケーションを動作させている分には

CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setCookie(url, val);

などで揮発性メモリの値は書き換えられているのですが、例えば WebView からのログインを cookie で制御している場合。

session[:user_id] = user.id

のような処理は実際には cookie に {user_id: 1} のような値を書き込むわけですが、それが不揮発性メモリと同期される前にアプリケーションを即時に終了させると、次回実行時に(cookie の expire が実行時より後に設定されていようと)不揮発性メモリに残った値を参照してアプリの状態を復元するため、今行ったログイン処理がなかったことにされてしまう可能性が存在する、ということです。

どうすればいいのか

CookieManager には flush() というメソッドが用意されているので、これを用います。

CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setCookie(url, val);
cookieManager.flush();

この書き込み処理自体にもコストはかかるため、なるべく必要な箇所のみ flush() を用いるように制御するべきとは思いますが、これでアプリケーションを強制終了した場合でも実際の操作の範囲では問題なくなったはずです。

2016

                       コングラッチュレーション
             ,―==7     Congratulation!   コングラッチュレーション
             |く ___ _>                    Congratulation!
             fll`ーU+'
             `''、 ー=|      おめでとう・・・・・・・・!
          _,,..-´:|ヽー-;ー..,,_
.  ,−=-, ,,..-‘≡≡:| ><´|≡::|ヽ    おめでとう・・・・・・・・!  おめでとう・・・・・・・・!
.  | l____ヽ.|≡l≡≡≡| |::| |≡:::/::|
.  |(llー´_ヽ|≡|≡≡≡|.|:::|l≡::/::::|      新年あけましておめでとう・・・・・・・・・・・・!
.. 4 l__`=|_|≡:|≡≡≡::||:::|'≡/≡|
/|\,.・|::≡:|ヽ|≡≡≡≡≡:::/|≡::|                         _,,.........、
≡|/}:ヽ|:≡|::::|{≡≡≡≡≡:::{ .|≡::|                        ヽ_,,   ヽ
≡:| |:::|l≡:|≡|:|≡≡≡≡≡:::|. .|≡::|                        /_>   |
:::≡l|:::|'≡:|≡:|::|≡≡≡≡≡:::|. .|≡::|                       |7 llう.. |
≡≡≡≡/|≡ヽ≡≡≡≡≡::::|. ..|≡::|.    z-..,〃、             ム__ ll´.. |
::≡≡≡::/ ヽ≡ヽ≡::|―、≡≡::l ..|≡::|   /    ミ              1´/ヽ==,...
::≡≡≡|   \≡ヽ::|  ヽ≡≡l  .ljヽl  |   刀、ミ           _,,,..-`‐三=ー-
::≡≡≡|    |ヽ/ー.、.. ヽ≡≡l.  .|/  |  ノ= ∠i         /ヽ、≡≡≡≡≡
:|¬、≡≡ヽ.  |≡ゞー=ッ  |≡≡|   __/ (ll ー゜\|ヽ.       /≡::ヽ≡≡≡≡≡
:|  ヽ≡≡ヽ |≡≡ヽミ.   |≡≡|  l|. ll7| ヽu=/l二ll二l'''ヽ  /≡:::/≡≡≡≡≡
:|   ヽ≡≡ヽ≡≡|     |≡≡|  | | llヽ|w-ヽ/Nヽll  |  | /≡:::/≡≡≡≡≡≡

Sinatra を使う場合のセキュリティ設定について

Apache のヘッダーに色々と書いたら Sinatra で書いたアプリケーションが 404 を返したり不穏な挙動を起こすようになってしまった!ドキュメントを読め!というお話。

ことの発端

社内で使う小さな問い合わせフォームを Sinatra でミニマムに実装したのですが、「最低限のセキュリティ設定くらいした方がいいよな…」と思い次の設定を Apache で行いました。

Header set X-XSS-Protection "1; mode=block"
Header set X-Frame-Options DENY

すると ChromeSafari のコンソールで

Error parsing header X-XSS-Protection: 1; mode=block, 1; mode=block: expected semicolon at character position 14. The default protections will be applied.

このようなエラーが発生しました。これを解決します。

先に結論

Apache 側の設定は必要ありませんでした。

なぜそうなるのか

Sinatra がデフォルトで Rack::Protection を使ってセキュリティ対策を行っていたためです。

http://www.sinatrarb.com/intro-ja.html#%E6%94%BB%E6%92%83%E9%98%B2%E5%BE%A1%E3%81%AB%E5%AF%BE%E3%81%99%E3%82%8B%E8%A8%AD%E5%AE%9A

これにより Apache 側の設定が二重設定とみなされてコンフリクトしていたようです。
もちろんこれを外せば Apache 側の設定が読まれますが、基本的にこちらに任せた方がいいでしょう。

DROP DATABASE が通らない

理由は分かりませんが、何かのタイミングで

DROP DATABASE hoge;

が通らなくなってしまい、次のようなエラーメッセージが表示されるようになりました。

Error dropping database (can't rmdir './hoge', errno: 66)

困るので、データディレクトリを直接削除して対処します。
まずはデータディレクトリの位置を MySQLコマンドラインで確認(この手順はデータディレクトリの位置が明らかな場合省いてください)。

SHOW VARIABLES LIKE 'datadir';

実行すると、次のような出力が得られるはずです。

+---------------+-----------------------+
| Variable_name | Value                 |
+---------------+-----------------------+
| datadir       | /usr/local/var/mysql/ |
+---------------+-----------------------+

後は、このディレクトリに入っている hoge という名前のディレクトリを削除します。他のディレクトリに触ると無関係のデータベースまで壊れる可能性があるため、注意して行ってください。

rm rf /usr/local/var/mysql/hoge