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

date_validator gem を使ったバリデーションにおいてオプションで Date.current を指定する際に気をつけること

Rails

date_validator に限らず validations のオプションの指定の仕方に注意が必要なやつ。

何が問題か

Rails の validation のオプションで validates :birthday, date: {before_or_equal_to: Date.current} のような指定をしていて、 (要するに今日より未来の日付で誕生日の指定はさせない的な)問題が起きた。 この記述だと、サーバーが起動したタイミングで Date.current が固定されてしまうので、 サーバー起動日より後の日付で、当日を birthday に指定しようとすると、バリデーションエラーになってしまう。

対策

オプションは Proc オブジェクトで指定しておく。 つまり、バリデーションが走るタイミングで動的に評価されるようにしておく。

原因

lambda, -> がダメな理由 ダメなわけじゃなく、内部的な実装の問題 Procであれば本来的には問題ない

  • まず date: {before_or_equal_to: Date.current} のバリデーション自体が date_validator Gem の機能です
  • この部分の具体的な処理は vendor/bundle/gems/date_validator-0.8.1/lib/active_model/validations/date_validator.rb
  • def validate_each の中でオプションの分解を L.71 で開始
  • この時 Proc オブジェクトであれば .call で実行するのだけど、問題は .call(record) という形の呼び方なので  -> { Date.curret }  だと、引数の数チェックで引っかかってエラー -> (record) { Date.current } なら落ちない( record 使わないなら意味ないけど

Rails にJSONでリクエストを投げた際に発生する JSON::ParserError をハンドリングする

API Rails JSON Schema

何が問題か

API として公開している Rails Server に対して、Invalid な JSON データを送信した場合、 parse error が発生した場合は、適切にリダイレクトやエラーメッセージの制御を行いたい。

だけど、挙動としては、Controller に到達する前の Rack Layer で勝手に parse 処理が走ってしまい、 Rack Layer で raise されて勝手に落ちてしまう。

具体的には ActionDispatch::ParamsParserが先にパラメータを見ている。

吐かれるログはこんな感じ

Started PUT "/api/users/5001" for ::1 at 2016-07-07 16:24:16 +0900
Error occurred while parsing request parameters.
Contents:

{
  "user": {
    "email": "sample@example.com",
    "password": "cGFzc3dvcmQ=",
    "name": "田中太郎"
  }
}:
  /.rbenv/versions/2.3.1/lib/ruby/2.3.0/json/common.rb:156:in `parse'
  activesupport (4.2.4) lib/active_support/json/decoding.rb:26:in `decode'
  actionpack (4.2.4) lib/action_dispatch/middleware/params_parser.rb:44:in `parse_formatted_parameters'

ログに生情報がそのまま出てしまうし、リクエスト元にも同じ内容が返ってしまう。 さらに、リダイレクトやエラーメッセージの表示を制御できないのもマズい。

どうすればいいのか

middleware のスタックに、ActionDispatch::ParamsParser  よりも前にハンドリング用の middleware を差し込む。

今回自分は Committeeという Gem を使っていて、この Gem で定義されている ハンドリング処理に任せたかったので、

application.rb では以下のように指定した

config.middleware.insert_before ActionDispatch::ParamsParser, Committee::Middleware::RequestValidation, schema: JSON.parse(File.read(schema_file))

config.middleware.insert_before ActionDispatch::ParamsParser, Committee::Middleware::ResponseValidation, schema: JSON.parse(File.read(schema_file))

参考

Railsのマイグレーションファイルで add_index する際、 using オプションまで指定する必要があるのか

MySQL Rails Today I Learned

インデックスの方法を明示的に指定したい場合のみ指定する。

Rails で using オプションを提供しているのはDBによって、インデックスの貼り方を指定できるためで、 MySQLInnoDB を使っているのに using: :btree する意味は全く無い(してもしなくても結果は同じ)

Ruby の `Enumerable#all?` が empty array にたいして `true` を返す

Ruby

[].all? => false が返ってもヨサソウなのに・・という疑問

チャットで聞いたりググったりして、数学的に正しい方向に倒している結果、今の挙動である true を返していることがわかった。

  • ruby's "any?" and "all?" methods behaviour on Empty Arrays and Hashes - Stack Overflow
    • So since the block never gets called, of course it never returns false or nil, thus all returns true.
  • Vacuous truth - Wikipedia, the free encyclopedia
    • For example, the statement "all cell phones in the room are turned off" will be true whenever there are no cell phones in the room. In this case, the statement "all cell phones in the room are turned on" would also be vacuously true, as would the conjunction of the two: "all cell phones in the room are turned on and turned off".
  • 自明性 (数学) - Wikipedia)
    • 証明の主要な部分は空でない集合の場合を考え、元を詳細に検査するであろう。集合が空の場合には、性質は自明にすべての元によって持たれている、なぜならば元がないからである。

TIL

Today I Learned Rails