Hashie::Dashでお手軽validationあるいはリソースを信用できるかどうか問題

intridea/hashie: Hashie is a collection of classes and mixins that make hashes more powerful.

まとめ

  • Ruby で nil を避ける方法は事前に validate する方法と取得時、利用時に気をつける方法がある
  • 事前に validate できる方が安心だしテストも楽(影響範囲が小さい)
  • いくつか validator はあるが Hashie::Dash は validate 機能内蔵していてかなりお手軽でいい感じ
    • 独立した validator を使うのは大袈裟だなと感じるようなカジュアルなシーンにハマりそう

Rubyでnilを避ける方法百選

nil が入るかもしれないところのコードをどうするかにはいくつか方法がある。

  • ActiveSupport の try
  • ぼっち演算子 (&. )
  • 文字列と分かっているなら .to_s
  • 逆に Hash から取り出す時に死んでほしい Hash#fetch
  • 素朴に nil?

などなど。

逆に DBMS 前提なら例えば ActiveRecord で

create_table do |t|
  t.string  :name, null: false
end

とすると name に nil を入れて save することはできない。

ま、扱うオブジェクトによっていろいろある。

事前に弾くのか、取得時に考慮するのか

上に挙げた例はアプローチが正反対である。

  • 前者は値の取得時、利用する際に nil を避ける方法
  • 後者はそもそも nil が入らないようにする方法

今回は後者の nil が入らないようにするということを validation と呼ぶことにしている。

Hashie::Dashの使い方

Hashie::Dash はみんな大好き Hashie::Mash に事前の property 定義が書けるもの。以下のような感じ。

class Model < Hashie::Dash
  property :foo, required: true
end

これで

Model.new(foo: nil)

とすると

ArgumentError: The property 'foo' is required for Model.

というエラーになる。おぉ、これならふいに nil を踏むことはなくなるじゃん。逆に

Model.new(foo: 'nil', bar: 1)

になると

NoMethodError: The property 'bar' is not defined for Model.

で、余計な property を突っ込むこともできない。

m = Model.new(foo: 'nil')
m.bar = 1

だと

NoMethodError: undefined method `bar=' for #<Model foo="nil">

おおおお。初期化時だろうと動的な変更だろうと弾いてくれる。

ということで Dash は以下のようなものらしい。

  • Hashie::Dash は事前に property を定義することができる Mash のようなもの
  • property の validation が自動で行われる
  • required: true の property は必須でかつ nil を与えることはできない
  • 余計な property を追加することもできない

intridea/hashie: Hashie is a collection of classes and mixins that make hashes more powerful.

の例を見ると他の property を参照することもできる。けっこう便利。

まぁ Rails 使ってるなら ActiveModel できっちり固めるという方法もあると思うし、Hanami みたいに dry-validation で schema を書くという方法もあると思うけど、もっとカジュアルに使いたいよねって時に Hashie::Dash で validate しておくと、単なる Hash より

  • いい具合にオブジェクトっぽく振舞ってくれる
  • 事前の定義で比較的安全に扱えるようにできる

ということが分かった。

なるほどな。

フロントエンドのrequireはテストを考えると注意が必要

※ 以下の話は Nuxt 1 + Webpack 3 前提です。

先日導入した Nuxt の環境で mocha-webpack でテストコードを書いている。

なんだけど、require / import 周りでどうしても伝統的な環境のクセが出てハマることが何回か起きていたのでそのメモ。

非webpack環境下でファイルを扱うサンプル

例えばファイルを扱うコードを書く際に、従来(webpack じゃない環境)はよく以下のようなコードを書いていた。

class Klass {
  /**
   * @return {String}
   */
  path() {
  }

  /**
   * @return {String}
   */
  load() {
    return <require とか read とか>(this.path())
  }
}

ファイルの扱いを気にしたくないので、ファイルの情報とその内容、データ構造などをセットで class の責務として持たせ、Klass を使う側は欲しい情報だけを適度に扱うメソッド経由で触る。

で、テスト時に path() を stub out してテスト用のファイルを食わせる。HTTP request だと request 先の URI を環境に応じて切り替えるような感じ。stub out じゃなくて外からパス情報を与えろとかあるかもしれないけど、まぁいずれにせよ「パス情報だけを独立して扱える」という前提なわけだ。

ところがこのようなコードは webpack ではいくつかの意味でダメなコードなのだ。

  1. そもそも webpack で動く require には変数も与えられない
  2. webpack の require は parse 時に即実行される

そもそもwebpackで動くrequireには変数は与えられない

これはメソッドとして分離しているかどうかではなく、以下のように動的にパスを組み立てるコードでも一緒。

const path = dir + base + param + '.js'
require(path)

これは「require が現れたらその中のパスを解釈していい具合に読み込んで JS の中に JS やデータを置く」という webpack の挙動に対して、「パスの解釈を許さない」というコードになってしまうのだ。

require の中に動的な組み立てが入っている場合は解釈できるので、どうしてもやりたい場合はそのように書くとよいが、その場合もテストには向いていない。

webpackのrequireはparse時に即実行される

プロダクトコードに require を書き、テストコードで stub out した require を書いた場合、実は require そのものが2回起きる。

まぁそらそうかという感じではあるんだけど、要は webpack を通す段階で require の登場する部分は遅延評価できない。人間の意識としてはコードが実行される前、 webpack がコードの変換を行う段階で require は実行、解決されてしまう。

まとめ

だから stub out するかどうかではなく、テスト対象のコードの require は都合よく変更できないので、以下のようにしてあげるのが正解。

  1. パス情報の解釈、require の処理は外で行い、
  2. 外からデータを与え、
  3. データの解釈だけを責務としてあげる

require ではなく HTTP になっていれば問題ないが、今回はそこまでやる必要はないので webpack の require で食わせるデータをいい具合にビルドプロセス1で生成してやろうと思っていたら、require 周りのテストで思ったように動かずに悩んでしまった、という話でした。

  1. こっちは webpack ではなく Node.js 

PYTHON-FIT 2回目

北陸でネットワーク越しに Python を勉強しようの会2回目が行われた。内容はチュートリアルなんだけど、

  • 石川の社会人組が KIT から卒業し、金沢駅西APAの中のシアトルズベストコーヒーへ
    • 同時にいつものインフラ支援を受けられない状態
  • 実は自分の機械で複数人の参加する Skype の音声チャットにそれなりに長い時間参加したのは初めて
  • ヘッドセット使うのも初めて

ということで今回も Python ネタ以外にいろいろ勉強になった。

ヘッドセット

Skype で使う場合は本体の環境設定の音声入出力とは別に Skype の方でも音声入出力に使うデバイスを指定する必要があった。なんか得意になって身構えていたのに本体から音が出ててまぬけなことに。

やはりテキスト以外の情報には信頼できるネットワークが必要

今回利用した石川の会場で拾える Wifi 3つのうち、2つは IP アドレスが取得できず、1つはセッション数の関係か急に詰まって使いものにならなくなった。スピードテスト的には 1.3M 出て申し分なかったんだけど、長時間音声およびテキストでチャットし続けるので、瞬発力ではなく持続力が必要。

e-mobile + PocketWifi は調子にもよるけど Skype 音声2人分を問題なく捌ける

前回の KIT 会場ではヘロヘロだった 3G 回線だったけど、今回の金沢駅周辺では文句なしに使えた。この辺、ビーンズとかにしておかなくてよかった。ビーンズのタリーズは e-mobile の電波弱すぎるのでこんな芸当はまず無理。

場所重要

今回はたまたま隣のテーブルがうるさかったが、比較的静かでかつ電波状況もよく、かつ電源1にも駐車場にも困らない場所ということで、このシアトルズベストコーヒーはかなり侮れない。

問題はちょっと周辺の駐車場が高い(1時間300円が相場)ことかなと思ったけど、ちゃんと探すと1時間100円や2時間100円の駐車場がある2ので、そういうところを使えばもっとずっと気軽に利用できる場所だと思った。まぁ、人数的にはせいぜい 1〜3人程度かなとは思うけど。

今日はなんだかんだで1700円くらい使ってる3けど、次回以降はもっと安くできそう。

※ あとで分かったが、建物の作り上、どうしても音が反響してしまい、雰囲気に反して騒がしくなってしまうようだ。これが Skype の向こうの人には多くのノイズとして伝わってしまうので、一人で黙々作業するには良い場所だが Skype する場所としては向いてないという結論になってしまった。

Python は?

  • トリプルクォートを複数行コメント代わりに使うみたい
  • 標準で虚数とか複素数とか扱えるんだね
  • 数字のリテラルと数値のオブジェクトの違い
    • 3.real はダメで (3).real はイケるんだけど、この () は誰だろう?
  • interactive shell でだけ _ が使える
    • Perl, Ruby のように無制限で使えるわけじゃないのはいいと思う。
  • 文字列の repeat は意外にあるよ
    • Perl は x
    • Ruby, Python は *
  • 文字列は連結以外に連接もできる
  • ヒアドキュメント == トリプルクォートという理解でいいの?
  • 文字列の中の変数展開はどうなるんだろう? ないのかな?
  • スライスの記法は便利だけど、実際どう使うんだろう? 固定長フォーマットかな?
  • リストって配列じゃないの?
    • なんか勝手に単方向リストのことかと思ってスルーしたけど、よく分からないっちゃ分からないな
    • どんな種類のデータも突っ込めるのは非 LL 組からすると気持ち悪いらしい
    • リストのリテラルは [] しかないのかと思ったら list() ってのがあるらしい。tuple からリストを作るのに使える
  • Unicodeオブジェクトが少し謎

Unicode オブジェクトについては u記法がとても目につくので、Perl の utf プラグマのように utf フラグをユーザーが意識する必要があるのかと思ったけどそうではなくて、むしろ Python 3 の時点では Ruby の扱いに近くて、内部的には Unicode で統一されている、と考えると自分的にしっくりきそう。

逆に u記法のない Py3 時代には euc-jp で書かれたコード資産を活用するのは難しいのかなぁとか思った。Perl は昔のコードにはほぼ手を入れずに動くというメリットがある代わりにおまじないが増えた。Python, Ruby は積極的に非互換を作っている感じだろうか。個人的には Ruby の方がなんとなく古いコードを活かしやすそうに見えるんだけど、実際どうなんかなぁ。

地味にエスケープ地獄にならない ur 記法が評価されていたが、分かったような分からないような。Java 書いてないからかもしんない。elisp ではエスケープ地獄うざいと思うが、あまり他の言語で気になったことがない。

  1. 一部の席で使える 

  2. さすがに少しだけ余計に歩く 

  3. サンドイッチ食べたしコーヒーおかわりしたし、駐車場が800円掛かった。 

phpdoc の出力が使いにくい

phpdoc と自分が呼ぶ場合は

phpDocumentor: The complete documentation solution for PHP

のこと。

pear install PhpDocumentor

で入るもの。

で、これを毎日回して trunk の code の apidoc を自動生成してるんだけど、この際、どのファイルを parse してるとか書き出してるといった様子が全部 stdout に吐かれる。そう、全部。

  • Hidden(読み込まないファイル)
  • Reading
  • Proccessing
  • Sorting
  • Formatting
  • Building
  • Writing
  • WARNING
  • ERROR

全部ぜーんぶ。鬱陶しい? オプションを紹介しよう。

 -q    --quiet                   do not display parsing/conversion messages.
                                 Useful for cron jobs on/off default off

だけ。on か off しかないの。

こういう情報って、とりあえず ERROR だけ欲しいよねぇ。ERROR 起きてたらそれを通知するとか、そういう使い方するもんでしょ? この吐き方はどうも、作ってる人もこの情報活用してない感じがする。これじゃー開発時にも自分の欲しい情報をすぐ取り出せないっしょ。

うーん。どうしようね。awk とか絡めて何か作るしかないのか…。いや、普段自分は気をつけて commit しているのであんまり ERROR 起きないんだけど、気をつけなきゃいけないのがダサイし、自分と同じ気をつけレベルを他人に強要、いや期待する段階でダサイし、現状はとにかく不確実である。

確実な動作を検証する方法があるはずの不確実なものに人間の注意で対応するのは我々の仕事としてはダメでしょう。

cf. Twitter / wtnabe: phpdoc の log ってこれ絶対作った人使って …

My First Pragger

とりあえず trunk の HEAD を落としてくる。UsingPragger - PRagger - Trac に従って Pragger の Trac の Feed を取得する。追加モジュールを何もインストールしなくても使えるのがいいよね。

……。思ったより重いのは Q 上の Debian だからか? 結構 CPU パワーを食うなぁ。

ちょっと中を覗いてみる

pragger.rb の最後の行を以下のようにして

if ( __FILE__ == $0 )
  eval_pragger( YAML.load( File.read(configFile).
                           toutf8.gsub( /base64::([\w+\/]+=*)/ ) {
                             Base64.decode64( $1 )
                           } ),
                [] )
end

irb 上で

require 'pragger'

する。$plugins の中身はこんな感じ。

> pp $plugins.keys.sort
["Feed::custom_feed",
 "Feed::lirs",
 "Filter::Translations::yahoo",
 "Filter::apply_text_html",
 "Filter::average",
 "Filter::deduped",
 "Filter::find_num",
 "Filter::find_regex",
 "Filter::fresh",
 "Filter::get_html",
 "Filter::grep",
 "Filter::invert",
 "Filter::sort",
 "Filter::subs",
 "Filter::to_integer",
 "Publish::lingr",
 "RSS::load",
 "RSS::save",
 "Yaml::load",
 "Yaml::save",
 "argv",
 "concat",
 "const_list",
 "first",
 "head",
 "load_path",
 "plugin_from_uri",
 "pluginbase",
 "print",
 "reverse",
 "send_msg",
 "stdin",
 "stdout"]
=> nil

ディレクトリ構造に応じて自動的に名前を付けられた plugin 群が並んでいる。Mechanize とか入れてないのでそういうものに依存している plugin は出てきていない。はず。

もうちょっと読む

先ほどの $plugins の値の方には何が入っているのかと言うと Plugin オブジェクトが入っていて、それぞれ @source には File.read() した中身が入っている。で、class Plugin を読むと全部 instance_eval もしている。この @source 無駄じゃね?とか思うと実は -u で Usage を表示する際にこの中の行頭の ## で始まる行をそのまま利用しているので必要らしい。うーん。うーーん。うーーーーーーん。すげぇ富豪だ。要するに全コードを文字列で保持しつつ instance_eval しまくりだよ。オレには真似できない。

とまぁ変わった作りなので、plugin をどんな名前にしても通常の Ruby の名前空間と衝突することはない。(呼び出しは常に eval_pragger() を使う。)そこはいいな。まぁグローバルに

  • $plugins
  • Plugin
  • opt
  • eval_pragger()

は思いっきり消費してんだけど。

既存のツールと相性悪いな

plugin は通常の Ruby のオブジェクトとは異なる構造をしているので、上のように pragger を irb で実行して中身を確認したりとか、補完を使って使い方の当たりをつけようとか思ってもあんまりうまくいかない。まぁ基本的にはファイル名と同じメソッドを呼び出すだけ、そいつには Hash と Array が渡ってくるだけ、なんだけれども。テストどうすんだ、これ。完全に閉じている plugin ならグローバルなメソッドにそのまま Hash と Array を投げればいいだけだけど、他の plugin を利用するタイプのものをテストするのはしんどそうだ。

とりあえず起動が重いのはちょっとなんとかなんないかなぁ。この構造じゃ起動重くて当然なんだよな。せめて YAML のパースを先にやって必要な plugin だけ読むとかすればまだマシだろうけど、この pragger.rb、あまりにスキがない。

あと起動の重さを気にしなくていいように daemon 動作させるって手もあるかと思ったけど、pragger.rb はそれも考えてなさげだ。

うーむ。どう料理したらいいかなぁ。まーまずは使ってみんとな。

PHP 5 への移行はどうしたものかと確かに悩むことはある

PHP is dead … long live PHP! | Dries Buytaert

5 へ完全に移行できるなら絶対に 5 の方が幸せになれるだろうなぁと思いつつ、現状 4 で動いているものを 5 に対応させるのがホネだなぁとか言うのがまずあるわけです。

両対応のフレームワークを使っているとか、ユニットテスト + Selenium で自動化してますということならかなり話は簡単になってくるんだけど、そうじゃない場合は人海戦術しかない。アップグレードでお金が取れる仕事ならともかく、そうでないものは頭痛の種でしかない。

が。

まぁここら辺まではどの言語でも同じような話だろうなぁと思うんだけど。

PHP 5 の数が増えてこないのはみんな単に様子見してるんだと思うんだな。Apache 1.3 がいつまで経っても蔓延っているのと状況が似ていると言うか。Apache は個人的にはもう 2.2 でいいよなと思うんだけど、PHP に関してはみんな 4 のマイナーバージョン間の互換性のなさ、矢継ぎ早のバージョンアップで疲れたんですよ。で、どうしても 5 が信用できない。これが絶対いちばんの理由だと思う。やっと落ち着いた 4 でしばらく安住したいと思ってるの。5 も出だしがよくなかったし。5.2 からは信用できるのかな?という雰囲気はあるんで、そろそろみんな重い腰を上げるかもなーってとこじゃないかな。ただまぁほら、PHP ってロードマップ分かんないから。やっぱ信用しきれないかな。

個人的にはこの間書いたように最近の Web アプリ開発の流れからすると PHP のメリットはかなり少ないんで、これを機に Rails に行っちゃったら幸せになれるんじゃないかとも思っているところ。Rails 風のフレームワークを使うなら Ruby で書けるホンモノの Rails の方が気持ちいいに決まっているもの。ポイントは既存のコードに使えるものがどんだけあるか、ですな。

ほんとに 5GB になってた

NIFTYのメール、5GB? - K2さんの雑記(2007-06-05)

なんか他のところでも見かけた気がするけど、@nifty のメールボックスが 5GB になったんだそうな。えーマジでーと思っていたが、今見たらセカンドメールもほんとに 5GB 分あるな。ついこの間まで 20MB デフォルトで、それ以上の容量はオプションだったような気がするんだけど、もう一律 5GB ということになったようだ。そうか、Gmail よりあるんだな。なんか妙な感じだ。

調べると nifty のアカウントを持ってなくてもサービスを利用できるように PLEASY という認証(サービス)を別途用意して、それを使った無料のメールサービスが始まったらしい。そっちの容量が 3GB. で、たぶん無料で 3GB なのに有料がそれ以下じゃまずいって判断になったのかな?

どれ冷やかしてみるかと思って nifmail.jp にアクセスしたら Camino が弾かれた。しょーがねーなぁ、UA じゃなくてオブジェクトモデルで判断してよと思いながら Firefox でアクセスしても弾かれた。

マジすか。

イマドキ Firefox で使えない Web メールのサービスってナニ?

[追記] どうやら Mac を弾いているようです。どっちにしろナニソレ?ですけど。

しゃっくりが止まったり止まらなかったり

昨日、週頭なのに飲み会でビールを2リットルほど飲んだせいなのか、夜中からしゃっくりが出始めた。寝て起きて、しばらく出なかったのだが、なぜかまたぶり返した。

「豆腐の原料は?」「大豆…」

と答えたら止まった。マジで!?と思ったけど、コーヒーを飲んだらまた始まった。どないやねん。朝止まっていたのが出始めたのもたぶんコーヒーを飲んだせいなんだろうな。

結局、昼すぎまでしゃっくりに苦しめられていたが、なんとか午後からは回復した。いやそれにしても不思議。

コマンドラインだからって黒バックとは限らないぜ

ピンク少佐の秘密工作室/Tech総研:ピンク少佐と「エンジニアのモテシーン」

わたしゃ OSX 標準配色のまま Terminal の2枚開き。Osaka-等幅で XGA の画面に 80 * 59 * 2 でちょうどいい。alias 設定しまくりでランチャ代わりにも使う。なんか、もうこういう使い方しかできなくなってるなぁ。

「罫線のような table」完成

めでたい。

午前中は普通に仕事もしちゃったし。昼寝もしたし、超充実。

Mozilla Firebird 0.6 for Win の意外なバグ

印刷時に 10pt って指定しても一回りでかい 12, 13pt くらいで印刷されます。うーーーーーーーん。

About

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