Validationのことを考えてたらFunctional Validationという考え方に行き着いた

問題意識

  1. Rails の validation の基本は ActiveModel::Validations だが、Strong Parameters を別に書くのは二重化してしまう 部分 がある
  2. さらに API クライアントみたいな SPA みたいなものがあると三重化してしまう
  3. Conditional Validation はやめたい

Rails内でvalidationをDRYにするdry-validation

例えば Laravel には独立した Validator があるし、Hanami::Validations も独立している。

で、Hanami::Validations は dry-validation を利用している。

dry-rb - dry-validation - Introduction

まず 1 の Rails 内で validation が DRY でないということで、そのまんまの名前の dry-validation を代わりに使うことを検討した。

独自の DSL で Schema を定義し、それで data を validate する。

Schema = Dry::Validation.Schema do
  required(<attr>).filled
  ..
end

result = Schema.(data).inspect

こんな感じ。

例えば Model では AciveModel::Validations を一切定義せずに

def valid?
  result = Schema.(self.attributes).inspect

  if result.errors.size == 0
    true
  else
    errors = result.errors
    false
  end
end

とすれば雑な感じでは validation を置き換えることができる。(たぶん考慮しなきゃいけないことは足りてない。)Controller では

before_action do
  params.permit!
end

def <action>
  result = Schema.(params.to_h).inspect

  if ( result.erros.size > 0 )
    head 400
  else
    head 200
  end
end

みたいな感じになると思う。宣言的に書けず分岐が一つ深くなるのはイヤということであればなんか raise する guard 節でもよい。

 raise NankaError if Schema.(params.to_h).failure?

ただし、標準の機能ではないし、許可されていない値を突っ込もうとするといきなり例外で死ぬ、みたいな「うっかり防止」感は弱いので、まず「Mass Assignment 脆弱性ってのがあってね」といった基本をしっかり押さえてから切り替えた方がよさそう。

ActiveModel::Validations はともかく Strong Parameters の記述はちっとも分かりやすさがなく、もう「とりあえずホワイトリストにしか使わない」感じはあって全然嬉しくないなーと思っていたので、独立した Validator を使うのは一時的には道具は増えるけど、いろんな書き方を覚えられなくて時間を浪費するよりはよさげだなぁという感触。

ただし許可されていないパラメータは明示的に禁止しないとダメで、書き方としては

optional(:id).value(:empty?)

みたいな感じになる。ここが少し罠っぽい1

もう一つ、Schema 定義が独立すると当然だけど「登場人物が増えるとどっちをテストすればよいのか迷う問題」が発生し得る。例えば Schema に対してテストコードを書くか、Model に対して書くか、といった問題。

まぁ自分がやるなら特に難しくは感じてなくて、

  • Model に対しては本当に validation が間違いなく実行されるかどうか
  • 詳細は Schema に対して

テストコードを書くという作り方をすると思う。そこが「関心」なので。

そういうサンプルが手近にないと Schema のテストはバッチリしてたけど実際に validation が意図通りに実行されていないことが e2e のテストの終盤で見つかりました、みたいなことは起きかねないかもなぁと思っている。丁寧に commit を分けたサンプルがあるといいんだろう。

cf.

JSON Schemaはprmdのサンプルはクソ面倒だけどまぁまぁか?

JSON Schema | The home of JSON Schema

JSON Schema は狙いがいくつもあって非常に分かりにくいし、prmd init するとサンプルがてんこ盛りで分かりにくい。分かりにくい。

ただまぁ、言語非依存で schema 定義を行えるのでサーバとクライアントで共有できる、クライアントの言語を問わない、といったメリットは間違いなくある。

とりあえず我慢して作ってみて Vue.js にぶっこんでみた。

使った npm は tdegrunt/jsonschema: JSON Schema validation

実際に書くとこんな感じ。

<template>
  <Error :errors=errors v-if=errors />
  ..
</template>

<script>
import {validate} from 'jsonschema'
import Schema from 'schema.json'
import Error from 'Error.vue'

components: {
  Error
},
data() {
  ..
},
computed: {
  errors() {
    return validate(this.$data, Schema).errors
  }
}
</script>

ここでは computed に入れてあるが、実際には validate を走らせるタイミングが大事になってくるはずなので、あくまで上のコードはイメージ。また、Error の component が独立してるのは「大雑把にViewコンポーネントから責務をひっぺがしていくフロントエンド設計」で考えていた話。

JSON Schema はよく JSON Hyper Schema の文脈で語られたり API サーバの validation とクライアントの validation の両方に適用できるという意味で使われたりしている。

が、個人的には

  • API サーバの validation とクライアントの Model 相当のものの validation は意味が違うのでは?
  • むしろ API サーバ上の Model の validation とクライアントの Model の validation が近いのでは?
  • サーバの Model は状態を持つので条件によって validation ルール変わったりする(Conditional Validation)よね?
  • ということは JSON Schema は何種類必要なの?
  • でも当然重複もあるよね?
  • Schema 表現としてどの程度の自由度があるの?

など、非常に気になることが多い。これは次の話に続く。

cf.

Conditional ValidationではなくFunctional Validationがよさそう?

Conditional Validation という考え方が実際にはよくあって、例えばブログ一つ取っても

  • 下書き保存の際には何が欠けててもよい
  • 「公開」時にはタイトルもカテゴリも本文もアイキャッチの画像も全部埋まってないとダメ

みたいなことがあちこちで生まれる。

上の例だと素朴には「公開」かどうかを表す attribute をまず見て、それに応じて validation が追加設定されるという感じになりがち。イメージとしては以下のようになる。

validates :body presence: true, if: -> { public? }
validates ...                 , if: -> { public? }
...

だが、これはやめたいと思っている。理由は

そもそもの validation の意味、field の内容を見て追加される validation の意味、意図がコードから読み取りにくい

から。

ではどうするかというと Validator というか Schema というか、それに名前を付けて、

この条件が成り立つ時はこういう意図のこの名前の validation が行われる

という形にすべき。validation が複雑になればなるほど意図が大切になる。

しかし、ここでも schema 定義の多重化が起きてしまう。そこで「Schema の merge ができれば便利なのではないか?」と思ったら dry-validation にはしっかりその機能があった。

Extending a validation schema - Support / dry-validation - dry-rb discussion forum

では JSON Schema はどうだろうか?と思ったが、ここでハタと困ってしまった。そもそも JSON Schema 自体が分かりやすいとは言えないのにこいつを merge とかできるのか?というかそんな機能を持ってる JSON Schema ライブラリは存在しないように見える…。

ここでしばらく悩んだが、

を読んでいたのと、最近 Functional Reactive Programming のことを考えていたので、JSON Schema 自体に詳しくなろうとするより考え方を変えた方が良いなと思い直し、

Schema を merge するのではなく Validation の結果を compose すればよいのでは?

と思いついた。

実現方法は簡単で、複数の schema を使った複数回の validation の結果を一つにまとめておく result オブジェクトがあればよい。しかもこれなら JSON Schema での validate だろうが dry-validation の validate だろうが何も気にしなくてよい。Schema の定義方法も Validation Engine の実装も何もかも違っていてよい。これなら JSON Schema と dry-validation で Schema が重複してしまうといったことも気にする必要がない。Rails 以外だろうと他にどんなツールが増えようと、ずっと使える考え方だ。

「なんてこった天才かオレ」と思ったけど、やはりすでにこの考え方をしている人はいます。そういうことかー。

A Functional Approach to Data Validation

※ protocol に依存せずに結果の errors が欲しいので、発想としてはたまによく gem で見かける :context を使えばよいのかな?

実は… ActiveModel::Validations だけならとっくにできますhttps://railsguides.jp/active_record_validations.html#validates-with が、今回の発見はどんな schema validator でも組み合わせようと思えばできること。です。

cf.

  1. Mass Assignment脆弱性対策のためだけに許可する params を列挙するだけに使うという使い方は残してもよいのかもしれない。でもそのためだけに private メソッドを使うのもイマイチなんだよねぇ。Action メソッドの中に宣言的に書けるならいいと思う。でも Action がメソッドだと難しいよね、これ。Hanami のように Action が class になってるとよさそう。 

Rubyで変数から定数を得る

はいはい。例によって twitter のログです。

20:57:16 wtnabe< Ruby で変数の中身を定数として参照して #{var}.new みた
いなことってできないよね。eval しか方法ないのかな。

(この間、ご飯を食べる)

21:11:01 finalfusion> @wtnabe 変数がクラス名を抱えていて、そこからイン
スタンスをつくりたい?
21:20:02 m_seki> @wtnabe const_get('ClassName') ?
21:29:40 wtnabe< @m_seki おぉ。できました! ありがとうございます!
const_get() ってそういう意味だったんですね。
21:30:53 wtnabe< @finalfusion ですです。疑問を twitter に投げてご飯を食
べてたら解決しましたw
21:47:01 finalfusion> @wtnabe const_get 書こうとしたらすでに @m_seki さ
んがががw

なるほど。

こんな風に使います。

def namespaces
  return %w(dotfiles firefox)
end

namespaces.each { |n|
  namespace n do
    utils = Object.const_get( n.capitalize ).instance

なるほどなぁ。なんとなく String#to_const みたいなメソッドがあるのかと思ってたんだけど、そういうんじゃないのね。

git独自制限shellなんてものがあるのか

何の気なしに

$ git-<TAB>

と補完させてみたところ、

git-cvsserver       git-shell           git-upload-pack
git-receive-pack    git-upload-archive

と出てきたので git-shell ってなに?と思ったら

$ man git-shell

NAME
      git-shell - Restricted login shell for GIT-only SSH access

mjd.

そうか、そうなると ssh 越しの git を使わせるのはずいぶん楽になるんだね。何しろ git そのものが制限 shell を提供してくれるんだから。

なるほどなぁ。

PhpDocumentor の templatebase って効いてる?

プロジェクトの数がなんか知らない間に増えそうな予感がするので、phpdoc の生成を今までよりも高次元で自動化しようと思ったわけです1が、この際、例によってこの子は iso-8859-1 しか喋る気のない大変困ったちゃんなわけです。

で、テンプレートの方に手を加えるんだよという話はあちこちで書かれているわけですが、

配布物に手を加えたらアップグレードのときに面倒くさそう

というのはある程度経験を積んだ方なら当然感じます。いやな予感がビンビンするところです。

幸い、phpdoc コマンドは -tb ( –templatebase ) というオプションで別な場所に置いてあるテンプレートを取り込むことができると書かれています。おぉこれは便利。早速これを使ってオリジナルのテンプレートを作っていこう…と思ったら効かねーよ!

当然階層違いとか間抜けなことは言いませんよ。Converters フォルダ、またその上のフォルダの phpDocumentor のフォルダから順にすべてのレベルのフォルダを templatebase で指定した場所に置いてみました。見事にすべて無視されます。

頭に来たのでソースを追いかけます。関係しそうなファイルは

  • Converter.inc
  • IntermediateParser.inc
  • Io.inc

辺りですが、睨んだ場所に print を仕込んでもわざと間違った場所を指定して error_reporting を仕込んでも全然必要な情報が取れません。お前はいったいどこを通過してどのフォルダ、どのファイルを求めているの?

面倒くさくなったので2他のテンプレートが置かれているのと同じフォルダ(今回は /usr/share/php/data/PhpDocumentor/phpDocumentor/Converters/HTML/frames/templates)に、そのまま *.i18n という名前で元のテンプレートフォルダをコピーして、そこでカスタマイズを始めました。やったことは iso-8859-1 を検索して削っていくだけ。

探すと同じような症状が

PEAR :: Bug #9250 :: –templatebase does not function as expected

にあるんだけど、再検証したっていう報告が結局上がっていない。あんまり使われてないのかなぁ。この辺の困った具合を除けばそれなりによくできたツールだと思うんだけども。

※ というわけで pdoc, jsdoc, rdoc, phpdoc 生成の自動化完成。phpdoc はテンプレートで、jsdoc は正規表現が再帰マッチするらしくメモリ不足で segmentation fault してハマりましたが、どうやら解決。ちくしょう意外と面倒だった。

  1. 一度 svn co してドキュメンテーション対象のコードのフォルダを増やすと自動的にそれが svn update と phpdoc の対象となるように。 

  2. デバッガでトレースとか面倒なことはやってられません。 

PHP 4.3 以降で SQLite

[2006-08-01 追記]以下の方法で入るのは SQLite 2 です。3 も 2 も両方使えるのは 5.1 以降 + PDO 環境だけのようです。

PHP で SQLite って言うと、5 で標準搭載という話題ばかりになってしまってるわけですが、実際には PECL から extension をインストールしちゃえば 4.3 以降で使えます。非Win環境では

pear install pecl/SQLite

(要ビルド環境)して extension を load するように設定を書き換えれば ok. Windows では http://pecl.php.net/package/SQLite からダウンロードしてくること。

なんだ、楽勝じゃんと思ったら OS X(Panther)の CLI版では extension の load がうまく動かず、リリースされたばかりの MDB2_Driver_sqlite がインストールできません。仕方ないので

pear install --ignore-errors MDB2_Driver_sqlite

として強行突破。

ちなみにエラーは具体的にはまず Unable to load dynamic library で sqlite.so が load できずに、pear のチェックで弾かれて MDB2_Driver_sqlite がインストールできないと言われる。これは extension_dir が適切にセットされていないから。

しかし extension_dir をセットすると今度は

dyld: /usr/bin/php Undefined symbols:
_OnUpdateInt
_core_globals
_display_link_numbers
_executor_globals

と言われて pear が起動しないという有様。

ちなみに mod_php の方では問題なく動くので

sudo cp /etc/php.ini /etc/php-cli.ini

として CLI 版用の ini ファイルを作り、そこでは

;extension=sqlite.so

とコメントアウトしたら pear が起動できるようになった。pear の起動ができないんじゃ CLI 版の意味ないんで、CLI からの SQLite の利用はとりあえず諦めるということで。(この辺の ini ファイルの扱いについてはマニュアルの 実行時設定 参照。)

FreeBSD では普通に使えているし、これは OSX 版の標準の PHP だけの挙動かも。全部 Fink で入れたらまた違うのかもしれないけど、面倒なんでやってない。

feed meter の画像がチョコになってる!

なんだーハートマークになってるから「おぉ、”あーありがち”の人気が上がったのか?」と思ったがそう言えば世間的には今日はチョコの日ですね(違

個人的にはバレンタインのチョコも節分の太巻きも商売のにおいがぷんぷんして好きじゃない(もちろんクリスマスもだ)んだけど、クリスマスやバレンタインはまだそこにコミュニケーションが存在するから、節分の太巻きよりは許せる。

……。太巻きがそもそも好きじゃないってだけかも。って何の話だ。

Apache 2 の WebDAV

ちょっとハマったので新たにメモを書き起こした。というかそう言えば今まで日本語ファイル名のものを含む WebDAV 環境を真面目に構築してテストしたものをまとめたことってなかったんだな。

ということで今日説教講座の方に載せた。

Windows じゃなかったらまた違うノウハウなのかなぁ。

とりあえず ImageMagick or GraphicsMagick

昨日の WSH とか HSP とかってのは実は画像の加工を自動化できないかっていう要求から始まったものだった。例えば IrfanView でもいいんだけど、その一括変換に存在しない機能を、キーボードマクロやこの手の簡単なアプリで実現したらよさげではないか、要するに AppleScript のイメージだったんだけど、実験したところ IrfanView には求める機能がそもそも存在しておらず、キーボードマクロを組み合わせても何の意味もないことが分かった。

てーことはやっぱ画像の加工ツールそのものにそれなりの機能と外部からの操作を実現する仕掛けがないと無理だなぁという結論に至り、じゃあ ImageMagick か、と相成ったわけだ。(ちなみにそのときの要求は別な人間が Photoshop のアクションで解決した。)

この手の話題で最近真っ先に思い浮かぶのは Python を内蔵した PaintShopPro 8 だが、これは Windows のみ。しかし ImageMagick は各種言語で利用できる API があり、ImageMagick 本体もサポートするプラットフォームはかなり広い。例えば MacOS 9 で Python を利用して制御、同じことを Windows でも Unix でもできまっせ、ということが可能なわけだ。ノウハウはできるだけ広く通用する方が嬉しいという考え方の自分にはもってこいなツールだ。

よし、ImageMagick でいいんじゃん。あとはどの程度のことが可能なのか、ってことだな。基本的にフォーマット変換ツール、あとはおまけ、程度の機能しかないと少し見劣りするし、なんかこう、こちらのモチベーションが上がらないので。

ちなみに、ImageMagick は単体のアプリではなく、各種のコマンドの集合体である。いちばん GUI OS な我々に馴染み深いのは display. これがビュワーに当たる。その他各種のエフェクトや機能によってコマンドが分かれている。これをかなりの部分で一つのコマンドにまとめ、cvs のようにサブコマンドで各機能を実現する実装に fork したのが GraphicsMagick だ。ImageMagick のバージョンで言うと 5.5.2 から分派している。ただ、本家 ImageMagick より動きが遅いようだし、Web サイトの管理や回線の状況など苦戦している感じが否めない。パッケージ管理の行き届いた Unix ならともかく、Windows で使う場合は GraphicsMagick + alias の方がなんか使い勝手よさげなだけに、もうちょっと頑張ってくれると嬉しいかな。

Kacis プロノート / マイノート体験版配布開始

http://nks.mvi.co.jp/index.php3?file=products/promynote.html

ちょっと試した感じではインターフェイス的には大きな変更はないですね。相変わらずデフォルトではツールバーごてごてで使いやすそうに見えません(^^; HTML に書き出してみたのですが、見事にフレーム分割で Kacis Book 風インターフェイスになっていました。いやな予感がしたのですが、HTML のソースはなんだか、えーと…。

アウトラインプロセッサとして考えると重いし、なんだか判断に困るソフトになってしまった気がするなぁ…(^^; 大事に大事に Kacis Writer Free 1.09 を使おう(笑)

/.j first post してまいました

初めての first post 記念日にして今朝は初めての違反切符(バスレーン) 6,000円也 (-"-#

About

例によって個人のなんちゃらです

Recent Posts

Categories

Tool 日々 Web Biz Net Apple MS ことば News Unix howto Food PHP Movie Edu Community Book Security Text TV Perl Ruby Music Pdoc 生き方 RDoc ViewCVS CVS Rsync Disk Mail FreeBSD Cygwin PDF Photo Zebedee Debian OSX Comic Cron Sysadmin Font Analog iCal Sunbird DNS Linux Wiki Emacs Thunderbird Sitecopy Terminal Drawing tDiary AppleScript Life Money Omni PukiWiki Xen XREA Zsh Screen CASL Firefox Fink zsh haXe Ecmascript PATH_INFO SQLite PEAR Lighttpd FastCGI Subversion au prototype.js jsUnit Apache Trac Template Java Rhino Mochikit Feed Bloglines CSS del.icio.us SBS qwikWeb gettext Ajax JSDoc Rails HTML CHM EPWING NDTP EB IE CLI ck ThinkPad Toy WSH RFC readline rlwrap ImageMagick epeg Frenzy sysprep Ubuntu MeCab DTP ERD DBMS eclipse Eclipse Awk RD Diigo XAMPP RubyGems PHPDoc iCab DOM YAML Camino Geekmonkey w3m Scheme Gauche Lisp JSAN Google VMware DSL SLAX Safari Markdown Textile IRC Jabber Fastladder MacPorts LLSpirit CPAN Mozilla Twitter OpenFL Rswatch ITS NTP GUI Pragger Yapra XML Mobile Git Study JSON VirtualBox Samba Pear Growl Mercurial Rack Capistrano Rake Win RSS Mechanize Sitemaps Android JavaScript Python RTM OOo iPod Yahoo Unicode Github iTunes God SBM friendfeed Friendfeed HokuUn Sinatra TDD Test Project Evernote iPad Geohash Location Map Search Simplenote Image WebKit RSpec Phone CSV WiMAX USB Chrome RubyKaigi RubyKaigi2011 Space CoffeeScript Nokogiri Hpricot jQuery Node GTD CI UX Design VCS Kanazawa.rb Kindle Amazon Agile Vagrant Chef Windows Composer Dotenv PaaS Itamae SaaS Docker Swagger Grape WebAPI Microservices OmniAuth HTTP 分析基盤 CDN Terraform IaaS HCL Webpack Vue.js BigQuery Middleman CMS AWS PNG Laravel Selenium OAuth OpenAPI GitHub UML GCP TypeScript SQL Hanami Document SVG AsciiDoc Pandoc DocBook Develop Jekyll macOS Node.js Vite Heroku Transformer AI Data Cloud Wasm