Grails1.0.4以降でScaffold時のドメインモデルの変数サフィックスがキモくなった件の回避パッチを当ててもらったんだぜの巻

[追記 2016/2/19]

Grails 3.0からInstanceなしがデフォルトに返り咲きました。今後はInstanceがほしい人がapplication.ymlgrails.scaffolding.templates.domainSuffix='Instance'を指定する、ということになります。

何の話?

2009-02-12に書いた

今までは Hoge というドメインクラスからコントローラやビューをgenerateすると、ベースとなる変数名として"hoge"が使われてたんですが、1.0.4から"hogeInstance"に変更されたらしいです。

・・・すごく・・・キモいです。

なんでこんなことになってしまったんでしょうか。
1.0.4 の命名規約がキモい件 - 豆無日記

という件の後日談です。

それからどした?

JIRAにパッチを投稿しました。

結論

結論から書いておくと、修正パッチが適用されました。
Grails 1.2M4で反映されています。

追加された仕様

Config.groovyに以下のエントリが追加されました。

// scaffolding templates configuration
grails.scaffolding.templates.domainSuffix = 'Instance'

デフォルト値は今までの挙動と同じになるように"Instance"です。

これを""にすれば、1.0.3以前の時代と同じようにHogeドメインの変数は"hoge"になります!

もちろん、"_"にすれば、"hoge_"になったりもします。


Grailsユーザ(アプリ開発者)のお好みで自由なサフィックスを指定できます!!


ただし、以下のデメリットを意識した上でサフィックスを変更しましょう。

  • 元のドメインモデル名によっては、GroovyやGrailsの暗黙変数などと変数名がバッティングする可能性がある。


万が一、そのようなドメインモデルを使う場合は、適切なサフィックスを使うか、サイバラの原則(※)に基づき、そもそも問題となるようなドメインモデル名を採用しない、ということで対処しましょう。

サイバラの原則*1
複雑な問題を扱わなければならないような状況に自分を持っていかないようにする
ソフトウェア開発をシンプルにする考え方のコツ − @IT



以下は蛇足。流し読み推奨。

経緯などを徒然と

以前に「キモい件」のエントリを書いてからずいぶんと時間が経ってますが、なんで今頃なのか?というと、とある経緯によりここ最近Grailsソースコードを読んだり、JIRAをウォッチする機会がありまして、ふと、そういえばドメインモデルのサフィックスの件はだれか改善要望をあげてないのかな?と思いついて、検索してみたところ、そのものズバリ

というIssueを見つけたわけです。


内容はというと、

Grails uglifies generated code by adding the word "Instance" to every variable referencing a domain object. This is unnatural to read, it blows up the code and it is inconsistent, because the word "class" is not appended to class names. It reminds me of variables like quantityInt, nameStr ...

という感じで、キモいし読みづらいよー、というグチです。
まあもうちょっと穏便に書いた方がいいんじゃね?とか対案はないの?とは思いますが、感覚的には同意です。


コレに対するコミッタの反応は、割と冷静な感じで、変更の理由を述べている感じ。 (ただ、コメントにeditedってついてるから、あとからトーンを変更したのかも?)

This was done intentionally to avoid a domain class generating an invalid/conflicting groovy variable name e.g. a domain class called Delegate would generate delegate which clashes with groovy's built-in closure property.
It would be a breaking change to modify it now as existing views will expect it to be *Instance.

要は、「例えばDelegateというドメインクラスを作ってScaffoldingしたときに、変数名が"delegate"なったりすると、groovyの暗黙変数とかと名前衝突するから危ないので、絶対にぶつからないように"Instance"とかつけてみたんだよ」、ということですね。


なるほど。これでやっと意図がわかりました。


しかし、対応策としては後付感満載であり、この変数名がちらちらと視界入ってくるようでは、ちょっと心穏やかにGrailsを使い続けられないなぁといった感じです。


とはいえ、同じ過激路線でDISったところでアクセプトしてもらえる可能性は0に収束するだけです。


しかも、その後に続いてる人のコメントがまたちょっとどうかなーという雰囲気の感情的っぽいコメントなんですね。
若干バイアス混じりで超訳すると、

Grailsはstabilityが重要だろ?サフィックス変更なんて今更、あ・り・え・な・い!頼むからやめてくれ。まじで。

的な。
気持ちも分かるけど、ちょっともちつけ。



というわけで、その辺の反論可能性等をクリアしつつ、自分の要望を通す手段ということで、数秒黙考して思いついた案が、
Grailsユーザが自由にサフィックスを指定できればいいだけじゃね?
というもの。


思いついたら、早速試すのみ。


ソースコード一式をgithubからcloneして、git grepで探索すること数分、あっさりとみつかりました*2
DefaultGrailsTemplateGenerator.groovy というファイル内で、getPropertyNameというprivateメソッドがあり、そこで"Instance"サフィックスが文字列結合されてました。
超シンプル。
この辺はわかりやすくて良いコードですね。


grails-app/conf/Config.groovyにエントリを追加して、その値をサフィックスとして使うように書き換えたパッチを作ってみたら、期待通り動きます。ナイス。


というわけで、CodehausのJIRA初デビューの日がやってきました*3


修正案のポイントは以下の3点。

  • Config.groovyに設定エントリを追加する
  • Config.groovyに書いておくデフォルト値は"Instance"とする
    • 新しいアプリも特に設定変更しなければ今まで通り"Instance"になる
  • もし、Config.groovyに設定エントリがない場合は、ソースにハードコードされた"Instance"を使う
    • 既存アプリがある場合でも、今まで通りの振る舞いが保証される


ということで、みんなハッピー、Win-Win的な解決方法じゃね?的な勢いで。


最初はClosedなIssueにコメントして、パッチはGistに書いてみました。
そして、wktkすること数日。



・・・・反応がありません。


しかし、それぐらいでめげていては話が始まりません。



やっぱClosedにされたIssueにコメントしてもだめなのかな?ということで、サブタスクを作成。
これで自分自身がReporterです。


それだけではなく、ダメ押しと言うことで、適用しやすいように、github上でメインリポジトリをforkして、パッチ適用したコミットをpush。


そして、pull requestを送りつけて待つこと一晩*4



・・・・メインコミッタのGraeme氏からgithubメッセージが!!!



どうやら、nobeans/grailsをhttpプロトコル経由でcloneしたけど、あるコミットのfetchでこけてるらしい。



JIRAにファイルを添付したら修正するぜ!的な内容だったので、git format-patchで生成したファイルからメール用の先頭数行を削除したファイルをJIRAに添付。
で、githubの返信メッセージで添付しましたぜ、という話と、http経由のcloneはbuggyだからやめたほうがいいですぜ。corkscrewとかでgitプロトコル使った方が楽ですぜ、みたいな返事を送りました。


折しも刻は週末。向こうは昼仕事でGrails開発やってるみたいなので、どうも週末はお休みなのですね。
JIRAにコメントがついたのは、週があけてから。



・・・そのコメントには「直したんだぜ!!」という文字が!!!


感動の渦がオレを席巻し、全オレが泣いた!!!!!



のもつかの間。


動作確認したら、機能が足りないんです。
どうやらパッチをそのまま適用するのをやめて、自前で実装したようです。

getter時に処理を仕込んでいたのをコンストラクタサフィックスを決定するように変更されてました。まあ妥当ですね。


それはいいんですが、その実装では、こちらのそもそもの要望であるサフィックスを空文字指定して消し去る、ということができません。
エントリが存在しない場合の互換性担保の条件に誤ヒットしてしまって、"Instance"がつかわれてしまうのですね。
これはイカン。


というわけで、リコメント。

説明不足でごめんよぅ、とあくまで下手です。
(1)〜(4)までこういうことがやりたいんだ、と箇条書きで整理して、今は(4)がNGだからそこを直してくれ、と。


待つこと、3日。



・・・・反応がありません。


コメントが細かすぎてウザかった?とか、ちょっとキョドりながらもよくJIRAのUIをみてみると、Reopenの文字が。
そうか。そういえば、Closedになったままコメントしても見てくれなかったな。


というわけで、Reopen忘れてたぜHa,Ha,Ha、とReoepnを実行。


すると翌日には早速、直したから確認しろよベイベーという反応が。



ヒャッホィーー!!!という勢いで動作確認すると、



・・・・(4)直ってないよね?



修正コードを確認してみると、オレの元のPatchを適当にコピペしたね?そうしたね?設定エントリのキー名も古いし、結局最終行で台無しだよね?


ということで、若干クレーマー的な勢いで、まだなおってへんがな、パッチおくったるからよろしゅうな。というコメントをつけて、パッチファイルをアップロードしようとすると、


・・・Firefoxがフリーズ。


何度繰り返してもフリーズ。Safariでやってもフリーズ。
時間厳守の予定が後ろに詰まっている中、全然解決策がわかりません。

半ば発狂しながら、Gistにパッチを登録してそこをポイントすることで代替手段としました。


Twitterのログより:

ReOpen後、さくっとFixしてくれたのはいいんだけど、これじゃだめだってば・・・。再々コメントだな。確認は来週になってしまうなぁ。 posted at 10:38:05
時間ないのに、アップロードでまたブラウザがフリーズ。なんじゃこりゃぁあぁ posted at 11:09:11
safariでもできなくなってる。まじでなにこれ。 posted at 11:11:36
しょうがないからgist経由でパッチをみてもらうことにした。虚脱。 posted at 11:24:29


その日の深夜に、今度こそやってやったぜ、というコメントがあったので、出先だったんですがiPhoneでチラっとコードを読んでみたら、



・・・・コンパイルエラーなるやん、これ?


中括弧が足りてません。


でも、まさかそんな初歩的なエラーとしないよなぁ・・・とか思いながらも、動作確認してみる環境も無いまま数日間。



それで、今日です。動作確認しました。
git pullして最新版にしてからGrailsをビルドして、grailsコマンドをたたいてみたら



・・・・・実行時エラー???

$ grails help
Exception in thread "main" java.lang.NoClassDefFoundError: org/codehaus/groovy/grails/cli/support/GrailsStarter
Caused by: java.lang.ClassNotFoundException: org.codehaus.groovy.grails.cli.support.GrailsStarter
        at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:319)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:330)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:254)
        at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:399)

なんでしょうね、これ。
実は今も原因がよくわかってないんですが。


しょうがないので、とりあえずJIRAのコメントにあったコミットまでgit reset --hardで巻き戻したブランチを用意して、Grailsをリビルドして試したところ



・・・・予想通りコンパイルエラー。ですよねーー。


ということで、masterに戻って、対象ファイルをgit blameで除いてみたら、あれ、足りなかった中括弧が、すぐ後のコミットで追加されてました。

なおしたよー、のコミットから30分後にコミットされてました。
さすがにすぐ気づきますよね。


というわけで、そこのコミットをHEADとするブランチを作って試したみたところ



・・・・・やった!!!期待通りの動作が確認できました!!!!



というわけで、今に至る。



つかれたのでこの辺で終わります。


ともあれ、そのままではないにしろ、初のパッチを適用してもらえたので、とてもうれしい僕でした。


あと、念のために一応書いておきますが、本エントリにはGraeme氏をDISる意図はまったくこれっぽっちもなく、本当に心の底から感謝してますし、彼はすごくナイスガイに違いないんで(あったことないけど)、その辺は取り違えのないようお願いします。各位。


Nobeans the Chicken

*1:この原則大好き

*2:もちろん、Grailsソースコードgithubにあることを見つけたり、cloneしたり、Antでビルドする方法を試す、などの時間は別途かかってます。数分ってのはgit grep周りの時間ですね

*3:VotingやWatchingなら何度かしてるけど

*4:向こうの活動時間はどうやら日本時間の夕方あたりかららしい