ExcelNote iframe sample
This is ExcelNote sample.
The table displayed below is an Excel file attached in Evernote.
Excelをブラウザ上に表示する技術は何かにてきようできないかなぁと考えてEvernoteアプリに仕立ててみました。
興味のある方はご覧になってください。
This is ExcelNote sample.
The table displayed below is an Excel file attached in Evernote.
Excelをブラウザ上に表示する技術は何かにてきようできないかなぁと考えてEvernoteアプリに仕立ててみました。
興味のある方はご覧になってください。
こんにちは。小西です。
前回の予告どおり今回はPlay1とPlay2のコントローラの比較をやってみたいと思います。
予想ですがPlay1を使い慣れた人がPlay2にやってきた場合最初にこのコントローラの違いにとまどい「Play1の方が便利じゃん。。。」と思うような気がします。
Play1の頃のやり方からの推測で「多分こうだろ」と勘で使おうとすると確実にはまります。
<Play1のコントローラ>
まず僕がPlay1でコントローラを書く場合の典型的なコードを示します。
pubilc static void process(String s1, String s2, String s3, int n1, boolean b1) { if (s1 == null || s2 == null || s3 == null) { badRequest(); } ResultObject obj = app.process(s1, s2, s3, n1, b1); render(obj); }
特に説明の必要はないかと思いますがコントローラを作る際のパターンとしては以下になります。
アプリケーションロジック自体はフレームワークに依存したくないのでほとんどの場合ApplicationオブジェクトはPlay非依存で別に作成します。(なのでPlayのモデルクラス群は実はほとんど使ったことがなく、この比較記事にもモデル編はありません。)
そのため、コントローラのコードはほぼ100%ワンパターンに上のようなコードになります。
Play1では引数の追加などの際にただ単純にコントローラの引数を追加するだけで良く、intやbooleanへの型変換も自動でやってくれるあたりが素晴らしいです。
また、routesで
* /{controller}/{action} {controller}.{action}
を定義しておけばメソッドの追加の際にも他の設定ファイルを変更する必要がなくいきなりコントローラにメソッドを追加できまるのも楽で良いですね。
<Play2のコントローラ - 駄目な例>
さて、先のサンプルコードを次はPlay2で書き直してみます。
ろくにドキュメントも読まずに僕は最初こう書きました
def process(s1: String, s2: String, s3: String, n1: Int, b1: Boolean) = Action { if (s1 == null || s2 == null || s3 == null) { BadRequest } else { ResultObject obj = app.process(s1, s2, s3, n1, b1) Ok(view.html.process(obj)) } }
ほぼPlay1で書いたバージョンの逐語訳です。
これ、動くには動きますがかなり問題があります。
routesの問題
まず最初に単純にコントローラにメソッドを追加しただけではそのメソッドをHttpリクエストで実行できるようにはなりません。
Play2のroutesにはワイルドカード構文がないのでメソッド追加時には必ずroutesにもメソッド定義を追加する必要があります。
routesの構文はPlay1の時と同じく空白区切りで先頭からHTTPメソッド、URIパターン、実行するActionの順で指定しますが、HTTPメソッドでもワイルドカードは使えないのでGETとPOSTは区別して定義する必要があります。
GET /test/process controllers.Test.process
書式としては上のような感じでPlay1とほとんど変わりませんが、先のメソッド定義の場合このroutes定義ではまだエラーになります。
Play2ではroutesのメソッド定義は引数まで正確に定義する必要があるからです。
GET /test/process controllers.Test.process(s1: String, s2: String, s3: String, n1: Int, b1: Boolean)
これで一応先のメソッドはブラウザから実行できるようになります。
ブラウザのURLで「http:.../test/process?s1=aaa...」のようにパラメータをつけて実行すると正常に実行できていることが確認できます。
次のテストとして「/test/process」のようにパラメータを外して実行すると「400 BadRequest」が返ってきますが、これは実は自前のnullチェックが機能したからではありません。
Play1ではコントローラの引数に指定したパラメータが見つからない場合はnullまたは0,falseなどのデフォルト値でメソッドが実行されましたが、Play2ではメソッド実行前にBadRequestが返ります。
また、routesのHTTPメソッド定義をPOSTに変更して、パラメータを設定したPOSTリクエストを発行してもやはりBadRequestが返ってきます。
何故かと言うとroutesのメソッド定義で指定した引数にマップされるのはURLパラメータまたはURIパターン内で定義した変数だけでPOSTパラメータは対象外だからです。
ここまでに書いた内容ではPlay2に良いところはありません。
Play1に慣れた人ならば「いくらなんでもそれはないんじゃないの?」と思うことでしょう。
その通りです。
実際にはPlay2ではURIパターンを使用する場合以外はパラメータをこのように引数で指定することはありません。
ただ、routesのドキュメントにも例つきでこのやり方が載っているしPlay1を使ってた人ならば、ほとんどこの道を通ると思うんですよね。。。(--
余談ですが何故引数まで正確に必要かと言うとPlay2のroutingはreflectionではなく静的にメソッドをバインドしたクラスをジェネっているからだと思います。(つまりroutingのコストはPlay1よりもはるかに小さいと思われます)
<Play2のコントローラ - 正しい例>
Play2のコントローラでパラメータを処理する書き方は実際には以下のようになります。
def process = Action { implicit request => val form = Form( tuple( "s1" -> text, "s2" -> nonEmptyText, "s3" -> nonEmptyText, "n1" -> number(min = 0, max = 100), "b1" -> boolean ) ) form.bindFromRequest.fold( e => BadRequest(e.errors.head.message), p => { val obj = app.process(p._1, p._2, p._3, p._4, p._5) Ok(views.html.process(obj)) } ) }
細かい文法は置いておいて雰囲気はわかるでしょうか?
Formとしてパラメータを定義してそれをリクエストからバインドしています。
サンプル的にFormの中でnumberのminやmaxを定義していますがFormには簡単なガード条件を記述するだけのスペックがあります。(僕はあまり使いませんでしたがPlay1でもAnnotationやValidationで同じようなことができます。)
Form#foldメソッドは第1引数にエラーがあった場合に実行する関数、第2引数にエラーがない場合に実行する関数を指定できるのでここでガード条件の分岐を行うことができます。
routesでのパラメータ定義も不要になるので、これならば十分にシンプルでメソッドの仕様変更などの場合にもあまりストレスはなさそうです。
ここではパラメータのマッピングにTupleを使用していますが、それ以外にもcase classを使用してオブジェクトにマッピングできるので、自由度という点ではPlay2の方がPlay1よりも優れていると思います。
ただ、慣れの問題もあるにしても、どちらがわかりやすいかと言われればPlay1の方じゃないかとは思いますね。
<ちなみにFileアップロードを扱う場合は?>
FormではFileを扱うことはできないようです。
ファイルとその他のパラメータを両方扱う場合のコードがいまいち綺麗に書けないなーと思ってるんですが、今のところアップロードを扱う場合のコードは以下のように書いています。
def upload = Action(parse.multipartFormData) { implicit request => val form = Form ( tuple( "sheet" -> text, "download" -> boolean ) ).bindFromRequest val file = request.body.file("file") if (file.isEmpty || form.hasErrors) { BadRequest } else { doUpload(file.get, form.get._1, form.get._2) } }
Actionに謎の引数が増えてますがMultipartFormDataを扱う場合は使用するパーサーをそれ用に変更しなければなりません。
このあたりをちゃんと理解しようとするとソースのかなり深いところまで読む羽目になるのである程度の所で挫折しました。(--
いずれにせよPlay1のメソッドの引数をFileクラスで宣言すれば良かっただけというお手軽さとは比べるべくもない感じです。
ざっと見た感じだとFileもFormで扱えるようにすることはおそらく可能だと思うので、ここはPlay2の開発チームにもうちょっとがんばってほしいところです。
こんにちは。小西です。
今回はPlay1とPlay2のテンプレートについて考察してみようと思います。
有志の方が翻訳してくれているのでPlay2についても日本語ドキュメントが徐々に揃いつつあり、最近では情報収集も大文楽になりました。
<とりあえず比較>
私的に両者の違いをまとめると以下のようになります。
Play1
Play2
いかがでしょう?
僕はこの二つのリストから機能よりもむしろむしろPlay開発者の意図の違いのようなものを感じます。
どういうことかというと、
というような違いがあるように感じるのです。
あるいは、Javaの頃は色々と作りこまないと不便だったのがScalaにするとそんなことする必要がなくなったということかもしれません。
<Play2テンプレート>
Play2のテンプレートはほとんどScalaそのものであり、実際にscalaファイルに変換されてコンパイルされます。
ちなみに変換されたscalaファイルは「target/scala-x.x.x/src_managed」の下にあります。
(今回はとりあげませんがroutesファイルも変換されてここに置かれます。つまりroutesファイルもScalaです。)
生成されたメソッドの中身がどういうものであるかはこの際どうでも良いんですが、以下のことをチェックしておくとテンプレートの動作が理解しやすいと思います。
要するにテンプレートの実行やその中で行っている処理は通常のScalaコード以外の何者でもないということです。
<Play2テンプレートの注意点>
さて、上に書いたようなことは別に知らなくても良いことではあるんですが、何故それを書いたかというとPlay2のテンプレートにはちょっとした問題点があるからです。
それが何かというと。。。。
実はPlay2ではこんなテンプレートは書けません。
{
はい。開き中括弧がひとつあるだけです。
Play2のテンプレートでは中に中括弧が含まれているとそれがどのような形であれ整合していない場合はエラーになります。
これはおそらく外部からパラメータとして渡す以外に回避する方法はありません。試した限り「""」で文字列として括っても一度変数に代入してもどうやってもエラーになりました。
まぁ整合しない中括弧が書けなくても実用上は特に問題はないですけれど、個人的にはこれはバグだと思っています。
<まとめ>
以上、ちょっと偏った視点からですがPlay1とPlay2のテンプレートについて考察してみました。
こうしてみるとPlay2のテンプレートは良く考えられていると思うし非常に面白いです。
しかし実際に使うとなるとPlay1のテンプレートの方が使いやすいと感じています。
何故そう思うんだろう?と考えてみたところどうも意識の差が大きいようです。
Play1の場合は「テキストを書いている」という気分で書けるのに対して、Play2の場合はどこまでも「Scalaを書いている」という気がします。まぁ僕だけかもしれませんけど。
中括弧の問題もJavaScriptを書く際にはちょっと気になります。(実際にはむしろバグの検出タイミングは早くなるはずなんですけどね。。。)
ここまで書いておいて何ですが、どれだけテンプレートエンジンが高機能でもテンプレートには可能な限りロジックは持ち込まない、ということが一番重要だよなと改めて感じました。
テンプレートは開発者以外の人がメンテすることもありますし。
まとめると
という感じですかね。
それでは、また。
次回はコントローラの比較をやります。
こんにちは。小西です。
今回Play1とPlay2の比較記事を書くにあたり、Play2をJavaで使用するという選択肢はアリなのか?という点についても調べてみました。
なにしろJavaの方がScalaよりも圧倒的に技術者の数が多いので、Play2がScalaだけでなくJavaからも(まともに)使用できるならばそれはとても価値のあることだと思うからです。
で、とりあえずExcel2CanvasのサイトをJavaに移植してみたのですが。。。。
う~ん、ないですね。これは。。。
Play2をJavaで使おうなどとは未来永劫考えない方が良いでしょう。(--
一応できるはできるんですが、Scalaをまったく知らないJava技術者がこれを使うのは正直かなり苦しいと思います。
そして当たり前ですが、Scalaを知っているなら絶対にScalaを使った方が素直にコードを書けるわけです。
以下、ScalaコードをJavaに移植してみて感じたことをいくつかのポイントに分けて書いてみます。
1、Play2のベースコードはScala
Play2自体のコードにはJavaで書かれたコードとScalaで書かれたコードの両方が含まれています。
パッケージで言うと
・play.api.xxxがScalaコード
・play.xxx(「api」パッケージ以外)がJavaコード
となっており、自前のコントローラを書く際にもそれぞれのクラスをimportして使用することになります。
Java、Scalaのどちらを使う場合でもコントローラは「Controller」クラスを継承したクラスであり、コード中では「Request」とか「Response」などのオブジェクトを扱いますが、それらはパッケージも書かれた言語さえも異なる全く別のクラスです。
そして、Play2のベースとなるコードはおそらく全てScalaで書かれておりJavaAPIでは内部的にScalaAPIをコールしたり、それが不可能な場合は新たに書き直されたりしています。
Play2のドキュメントには「JavaをベースとしながらScalaのパワーを解放するのは難しい」というようなことが書かれていますが、「ScalaをベースにJavaでも使用できるライブラリを作成する」ということはもっと難しいだろうと感じましたね。。
ソースを見ていたのは1日2日ですが、それでもそこかしこにいびつさを感じます。(主たる原因はやはりJavaにはない関数リテラルがScalaでは多用されるためかと思います。)
2、テンプレートはどちらもScala
Scalaを使用する場合でもJavaを使用する場合でもテンプレートの仕様は共通でどちらもScalaになります。(厳密に言えばScalaベースの独自仕様で、テンプレート内でScalaコードを自由に記述することができます)
実際、今回ScalaコードをJavaに移植してみたわけですが、テンプレートのコードは1行たりとも修正していません。
Scalaベースといっても単純に文字列内に埋め込まれたパラメータを置き換えたいだけのようなケースではScalaの知識は特に必要なく、 ここまでならJavaから使う分にも特に問題はないんですが、Listなどのコレクションを使用してテンプレート内で繰り返し処理を行いたいような場合はちょっと困ったことになります。
そう。ベースがScalaなのでコレクションもScalaのコレクションにしないとJavaのままでは使いにくいのです。
もちろんテンプレートの内側でも外側でもScalaのJavaConversionsを使用して変換することはできるんですが、かなり「なんだかな。。。」という気分になります。(--
また、細かいところですがScalaではデフォルト引数とパラメータの名前渡しが使えるのでテンプレートのパラメータで下記のようにデフォルト値を宣言しておけば選択的にパラメータを渡せますがJavaから使う場合は全てのパラメータを明示的に指定する必要があります。
@(name: String, data: String, download: Boolean = false, version: String = "", strs: Seq[String] = Nil, pictures: Seq[(String, String)] = Nil)
3、バグ多し
これは1とも関係することですが、ScalaのコードとJavaのコードは別に書かれているので、Scala側に問題がなくてもJava側でバグが混入する可能性はあります。というより、現状ではかなり多そうな印象です。
わずか1日の移植作業で遭遇しただけでも、
・WS(HttpClientラッパー)のレスポンスからバイナリデータを取得する方法がない
(Scala版のWSにあるメソッドがJava版にない)
・日本語を使っているわけでもないのに何故かエラーメッセージがもれなく文字化け
・割と頻繁にエラーメッセージに表示される行番号が正しくない。
などがあります。
さらっと書いてますがわりと致命的です。(--
Scalaでコードを書いている時にはそれほど困った記憶がないので、これらはおそらくJavaだけにある問題です。
開発者の力のかけ方の差を感じます。
本筋とは関係ありませんがPlay2のWSではPlay1の時にあった同期APIがなくなり非同期APIのみになっています。
今回バグ回避のためにWSがラップしているningのHttpClientを直接使用しましたが、同期処理の場合は多分そうした方が素直に作れます。
4、Scalaはやっぱり便利
今回ScalaのコードをJavaに書き換えるにあたって面倒を感じたのは以下の点です。
・Tupleが使えないのが少し。。。
・List処理を全部forに修正するのがかなり。。。
・XMLリテラルでやってたことをStringベースにするのはとても。。。
これらは色々なところでScalaの利点として語られているので、ここでは多くを述べませんが一度Scalaに慣れてしまった技術者がJavaをおっくうに感じる気持ちはとてもよくわかります。
以上が、Play2のScalaコードをJavaに移植して感じたことです。
こうして改めて書いてみるとPlay2はまだまだこなれていない印象を強くします。
バグは直せば良いだけですけど、1や2は根源的な問題なのでそう簡単には解決しないでしょう。
また、現在Play2を開発している人たちの意識もそこには向いていないと思います。
個人的にはもうJavaのサポートは止めちゃっても良いぐらいだと思いますがこれからどうなっていくでしょうね。
いずれにせよPlay2を使うのであればがんばってScalaを覚えましょう!(^^)/