RubyKaigi 2013 で発表しました
RubyKaigi 2013 の最終日、6/1に「Rails Gems realize RESTful modeling patterns」というタイトルで発表させていただきました。
相変わらずコードの実例に乏しく、idealisticなくせにemotionalでもない話になってしまい、自分の力のなさを痛感するばかりなのですが、まわりの方々には良かったという言葉をいただいて、とても感謝しています。
発表内容について
昨年の「RailsにおけるRESTfulなURL設計勉強会」で発表した「リソースモデリングパターンの提案」およびそれをまとめたブログ「リソースモデリングパターン」と基本的な論旨は変わりません。RubyKaigiなので、よりRuby(というかRails)に密接した内容ということで、gemからパターンを見出す、という流れにしました。思い返してみれば、自分がパターンを考えたときもそうしていたはずで、Railsプログラマにとっては自然な流れなのです。
とはいえ、ルーティングまでカバーしたgemの数はそれほど多くなく、有名なものをいくつか取り上げただけに終わってしまいました。そこでちゃんと自分で作ったものを紹介すべき(CFPにはそう書いた…)だったのですが、ほんの1ページCollectionFilterを紹介しただけになってしまったのは日頃の怠惰が原因です。反省。
最終日の朝11時というのは、慣れてない自分にとってはベストな枠で、他の人の発表を見て、脳内リハーサルを繰り返すこともできたし、最後までスライドや内容を手直しすることができました。
リハーサルでアドバイスをくれたSendagaya.rbのみなさん、スライドの英語をレビューしてくださった早坂さん鵜飼さん(+ネイティブの方々)、ありがとうございました。
また、ろくに口頭発表の原稿を出しておらず、直前打ち合わせでもグダグダだったのにプロフェッショナルに対応してくださった同時通訳のお二方、ありがとうございました。
参加者として
今回は発表に採択していただけたこともあって、仕事も気にせず迷わず全日参加しました(朝ちょっと寝坊したのを除く)。
正直なところ、チケット高いし(買ってないけど)インターナショナルになったので、少し入りづらいかなぁとも思っていたのですが、RubyKaigi 2011のときと同じような居心地のいい空間でした。これもTEAMのみなさんのお力だと思います。本当にありがとうございました。
ほかの方の発表を聞いて
@hsbt さん
仕事が幅広すぎ。すごい。
@shugomaeda さん
refinementsやっぱり使いたいので2.1期待ですね。
@kyanny さん
内容がすごく実践的で、自身の経験を挙げて根拠も示していて、僕の対極にあるようなすばらしい発表。
“Daily Pull Request gave me the chance”
いい言葉だなー。Open source contributionだからって大げさに思わなくても、普段使いでバグを直していけば大きな貢献なんですよね。そのチャンスは毎日来るんだよ、と。
@a_matsuda さん
最初は観光案内か?と思ったけど、すごい内容(しかも全部英語だしわかりやすいし)。やっぱりgem作るべきなんだよ、と改めて痛感しました。
@moro さん
ActiveSupport::Concern自体は自分もかなり使っていましたが、controllerにもできたconcernsってどう使うんだろうと思っていました。Nested Resourceで使うというのはとてもわかりやすい。
こういう発想ができるようになりたい。
@brynary さん
Sendagaya.rbでは何回かブログを読んだのでおなじみのCode Climate。こういう純OO的手法は、大クラス主義のRubyやRailsに慣れた身としてはちょっともやっとするところもあるのだが、手法としては理解しておく必要があるし、バランスを考えて適用していくべきだろうな。
@yotii23 さん
“Application give me a shape to the cultural system”
これもいい言葉だなー。
@sonots さん
fluentdはまだ興味段階でしかないけど、それのWeb UIというのがわくわくする。あと英語が流暢!
Committerトーク
いつもの。グダグダなんだけど楽しい。
@ktou さん
これが須藤ワールドかー。library developerになりたい。
TRICK
実は一番楽しみにしていました。裏のfluentdも聴きたかったので重なって残念だったのだけど。内容は期待通り抜群に面白かった。
$ruby.is_a?(Object){|oriented| language} # => true
とか美しい!
@tanaka_akr さん
すげえ、の一言。
あ、それから
RubyHirobaも楽しかった!
RailsのURL設計を考えてみる(7) リスト操作
リスト操作といえば昔からActsAsListが定番ですが、「上に移動」「下に移動」のリクエストをどのようなURLで実行していますか?
http://blog.takuyan.com/blog/2013/02/19/consideration-of-how-to-operate-the-list-on-rails/
この話を今週のSendagaya.rbで@kattonさんとしていました。そのときはあまり時間がなく、中途半端な答えしかできなかったのですが、せっかく@kattonさんがわかりやすい形でブログに書いているので、もうちょっと突っ込んで考えてみました。
久しぶりの「URL設計を考えてみる」シリーズ記事も書けたw @kattonさんありがとうございます!
モデルはこのようなものを考えます。
class List < ActiveRecord::Base has_many :items, :order => "position" end class Item < ActiveRecord::Base belongs_to :list acts_as_list :scope => :list end
itemに対する操作
https://github.com/swanandp/acts_as_listMethods That Change Position and Reorder List
list_item.insert_at(2)
list_item.move_lower
will do nothing if the item is the lowest itemlist_item.move_higher
will do nothing if the item is the highest itemlist_item.move_to_bottom
list_item.move_to_top
list_item.remove_from_list
基本的な考え方
HTTPメソッド
操作に対応するHTTPメソッドを考えます。冪等な操作かそうでないかを考えると見分けやすいです。(「冪等(べきとう)」についてはこちらの記事参照)
- insert_at(n) : PUT(追加の場合POSTだが考慮外)
- move_lower : POST
- move_higher : POST
- move_to_bottom : PUT
- move_to_top : PUT
- remove_from_list : DELETE
考え方 その1
例:move_to_top
単純に、位置をさす数値positionをリソースとして使う。
PUT /items/123/position position=0
もしくは
PUT /items/123 item[position]=0
これで insert_at(n) も実現可能。
ただし、move_to_bottomで破綻する(positionの下限がわからないため)。
考え方 その1-1
位置をさすpositionの値に、数値に加えて'bottom'を許す。
PUT /items/123/position position=bottom
あまりきれいではないし、エラーハンドリングなど扱いが面倒になる可能性があるが、だいたいうまくいきそう。
考え方 その1-2
例:move_to_top
topかどうかをさすリソース position_top をつくる。
(フラグの考え方。これもパターンっぽいので、あとで名前つける)
PUT /items/123/position_top position_top=true
ここからmove_to_bottomを考えると、topとbottomは排他的なため、リソースは1つにまとめられる。
PUT /items/123/position_edge position_edge=bottom
topでもbottomでもないときはGETすると'other'や'false'が返される。
ただここまで来ると「その1-1」とあまり変わらなくなっている気も。
考え方 その2
例:move_higher
これはもう、POSTなのでしょうがないw
itemへのPOST
POST /items/123 position=move_higher
positionへのPOST
POST /items/123/position move_higher=1
listの順序リソースordersをPOST(create)
POST /list/1/orders operations[move_higher]=123
いろいろ考えられますが、決定打がない。逆に言うと単純な場合ならどれでも問題ない。(ほんとはできる限り整合性のとれた形でfixしたい)
move_lowerも同様。
雑感
毎回考えるのは不毛だし、routesを書くのも面倒なので、こういうリソース設計も、ある程度パターン化してgemに含まれていればうれしいですよね。
僕が目指しているのはそういう方向です。
最近つくったgem resources_id_replace & bootstrap-editable-rails
最近、Railsのgem(プラグイン)をよくつくっています。そのことについて、たまにツイートぐらいしかしていないので、ちゃんと紹介しようと思って書いてみます。使ってもらえればうれしいです。
(ブログさぼりすぎ…)
resources_id_replace
これは何?
resources で /users/1 というURLのidの代わりに、名前を使って /users/tkawa などとしたいときがあります。
コード上は特に変更しなくても、 params[:id] に 'tkawa' が入ってくるのでそのまま処理はできるのですが、名前がidなのに中身がidじゃないので
@user = User.find_by_name!(params[:id])
のようになってしまいます。これは気持ち悪い。でも resources では :id の部分が変えられないんですよね。うーむ。
これを変更できるようにするgemです。
例
resources で :replace_id_with オプションが使えるようになります。
resources :users, :replace_id_with => 'name'
生成されるルーティングはこのようになります。
users GET /users(.:format) users#index POST /users(.:format) users#create new_user GET /users/new(.:format) users#new edit_user GET /users/:name/edit(.:format) users#edit user GET /users/:name(.:format) users#show PUT /users/:name(.:format) users#update DELETE /users/:name(.:format) users#destroy
無事、このように自然に書けるようになりました!
class UsersController < ApplicationController def show @user = User.find_by_name!(params[:name]) end end
あとは、url_forなどURL生成メソッドの対応のため to_param をオーバーライドしておくのがいいですね。
class User < ActiveRecord::Base def to_param name end end
FriendlyId との違い
同じ /users/tkawa のようなURLを実現するために FriendlyId というgemがあります。
使ったことはないので、以下間違っていたらすみません。
FriendlyIdはかなり内部に手を入れていて、User.find(params[:id])
のままで、 params[:id] が 'tkawa' でもOKなように拡張します。いわば「id自体を変える」というイメージです。
しかし、実際の user.id は数字のままなはずです。もちろんDBも。そのへんがわかりにくくなることもありそうです。(ほかにもhistoryとかいろいろ機能があるので楽ではあります)
resources_id_replace は、ソースを見てもらえばわかりますが、複雑なことは全くしていません。 resources で“id”とハードコードされている部分を変えられるようにしただけです。なので params[:id] ならidの数値、 params[:name] ならnameの文字列、という一貫性が保てます。
追記:Rails 4には同様の機能が実装されているので、このgemは不要になりました。
Rails 4では、resourcesにparamというオプションを使うことができ、これで:idに代わる名前を指定できます。
resources :users, param: :name
bootstrap-editable-rails
これは何?
X-editableというJavaScriptのライブラリがあります。テキストをその場で編集できる「インラインエディタ」とか「インプレイスエディタ」とか言われているもので、Twitter Bootstrapの見た目に合わせて作られています(Bootstrapじゃないのもある)。
機能も豊富なので、一度デモを見てみてください。
これをRailsで使おうと思ったのですが、Ajaxで送られるパラメータの仕様が全くRails的ではない…。
そのままでは使えないので、元のソースを書き換えずに、中の動作だけを変更してパラメータの仕様をRailsにあわせるgemです。
例
HTMLはこのように書きます(実際はerbやhamlで書くでしょう)。
<a href="#" id="username" data-type="text" data-resource="post" data-name="username" data-url="/posts/1" data-original-title="Enter username">superuser</a>
この要素を編集すると、 data-resource と data-name からパラメータが組み立てられ、
PUT /posts/1 Accept: application/json post[username]=superuser
のようなAjaxリクエストが発行されます。
Railsのコントローラ側では、updateアクションで更新処理をして、ステータス2xxを返せば成功です。
ABC予想とフェルマーの最終定理
ちょっと前に流れたこのニュースを見て、おおっと思いながらわからないのにまたWikipedia(wikipedia:ABC予想)を眺めたりしていたのだが、やはり気になるのはこの部分。
整数論の代表的難問であり、解決に約350年かかった「フェルマーの最終定理」も、この予想を使えば一気に証明できてしまう
ググったりしていると、これについてとてもわかりやすい説明があったので書き出してみる。
用語の定義
abc-triple
正の整数 a, b, c について、a + b = c かつ a, b は互いに素である三つ組 (a, b, c)
rad(n)
正の整数 n の、互いに異なる素因数の積
例:
ABC予想
が abc-triple ならば、 が成り立つ。
(これは強い形の予想で、今回証明されたのはもうちょっと精密で弱い形のようだけど)
フェルマーの最終定理
3以上の整数 n について、 を満たす正の整数 の組は存在しない。
ABC予想 ⇒ フェルマーの最終定理
背理法による。 を満たす正の整数 が存在すると仮定する。
とすると、ABC予想より
よって n < 6 であるが、n = 3, 4, 5 についてのフェルマーの最終定理はオイラー、フェルマー、ディリクレによって証明されているので矛盾する。□
出典
あれ、フェルマーの最終定理がなんてことないように見えちゃいますね。これはすごい。
「リソースモデリングパターンの提案」の先
「RailsにおけるRESTfulなURL設計勉強会」で、僕は「リソースモデリングパターンの提案」という発表をさせてもらいました。はてブなどではいいコメントをいただいていてありがとうございます。
やりたいこと
パターン提案自体の目的はスライド5ページ目に書いたのですが、とくにまとめというものもなくて、ほんとに列挙しただけになっています…。
Railsには“resources”というすばらしい「パターン」があるのですが、やっぱりそれだけではどうリソースを設計していいかわからなくなることが多いです。
リソース設計にももっと「レール(具体的なパターン)」があってもいいんじゃないか、少なくともRailsには。だってRailsはそういう思想だから。
実際、スライドの中で触れている meta_search や kaminari は、「リソース設計」とは言っていないけれど、その具現化です。gemを使ってURLの構造を決めていて、実際に動作するコードまで提供している。こういうのをやりたい。Conventional Routesもその一環ですが、ちょっと失敗ぎみ。
とはいえ、まだそんなに具体的な実装のアイデアはないのですが。パターン自体ももっとちゃんと整理しなきゃ。
Just Idea
例えば、スライド21ページ目の Partial Resource パターンを、素直に書けば
resources :users do resource :name, :only => [:show, :update] resource :email, :only => [:show, :update] # これでも複数は無理なので不完全 end
のようになるのですが、それがこう書けるとか。
resources :users, :partial => [:name, :email]
(というのを、Shibuya.rbのときのスライドの最後にちょっと書きました)
「RailsにおけるRESTfulなURL設計勉強会」を開催しました
きっかけ
ということで、主催している @ppworks さんと @fukajun さんに乗っかって、7/23に Sendagaya.rb の#12として開催させてもらいました。
発表内容・スライド
- RailsによるURLの描き方 (@fukajun)
- RESTful APIとしてのRailsとクライアントとしてのJavaScript (@ppworks)
- リソースモデリングパターンの提案 (@tkawa)
- RESTful Web アプリの設計レビューの話 (@t_wada)
- 1周めのリソース設計 (@moro)
思いがけず t_wada さんと moro さんに話していただけることになって、ものすごく内容の濃い発表になりました。後半にみんなで議論をしようという予定で、そんなに大きくない部屋で15人という人数だったのですが、発表が盛り上がって議論の時間がなくなってしまいました…。
この勉強会の2回目として、議論会・相談会をやる予定ですので、よろしくお願いします。また、来週のSendagaya.rbの定例で復習の会もやります。
参加者のみなさんありがとうございました。
個人的結果(?)
REST厨だった自分撃沈…というわけではないんですが、いい意味でREST厨でありたいと思います。リソースの部分更新 の草稿
だいぶ前に「次期RailsがPATCHメソッドを採用」という記事を書いて、
ただし、上記ではPATCH採用のもう1つの理由である「リソースの部分更新」あたりの説明をあえて省略しました。これはちょっと個人的に思うところがあるからです。
という感じなのですが、この詳細は次回に続くということで…。
と言ってそのまま放置していたのですが、途中まで書きかけのものをたまたま見つけたので、ついでに載せてみます。
前回の続きです。
前回省略したのですが、PUTの特徴的な性質は実はもう1つあります。
http://www.studyinghttp.net/cgi-bin/rfc.cgi?2616#Sec9.69.6 PUT
PUT メソッドは、同封されたエンティティを供給される Request-URI の元に保存するように要求する。 Request-URI が既に存在するリソースを参照している場合は、同封されるエンティティはオリジンサーバにあるそれの修正版とみなされるべきである。
新たなメソッドは、相互互換性を改善し、エラーを排除するために必要である。既にPUTメソッドが、完全に新たな本体{body}をもってリソースを上書きするために定義されているが、このメソッドでは部分的な変更を行うために再利用することはできない。
http://www.studyinghttp.net/rfc_ja/rfc5789
(中略)
PUTリクエストでは、同封されたエンティティはオリジンサーバ上に保存されるリソースの修正バージョンであるとみなされ、クライアントは保存されるバージョンが置き換えられることを要求している。
RFC2616には明確に書いてあるわけではありませんが、その後のRFC5789ではPUTはリソースを「上書き」「置き換える」メソッドであると規定されています。ちょうどFTPなどでファイルをサーバに転送してそのまま保存するのと同じ感覚です*1。
これに従うと、前回の例であるupdateアクションにcreated_at, updated_atのタイムスタンプを使用するのは、この意味でもまずいことになります。「置き換える」わけですから、PUTするデータにもcreated_atやupdated_atの内容が含まれていなければならないからです。
部分更新
もう1つの例として、invoice(送り状)リソースを考えます。このリソースには支払済みを表すためにpaidというフラグがあります。ある送り状を支払済みにするという操作は、従来のRailsではinvoiceのupdateですから、
PUT /invoices/:id
としてリクエストボディで paid=1 と送りたくなりますが、これも「置き換える」の規定に反しています。
子リソースを定義して
PUT /invoices/:id/paid
とすればOKですが、routes.rbに記述するのがめんどくさいですね。
それは現実的か?
やっぱりPATCHが正解だね、という結論なのですが、本当にそうでしょうか。
極端な例を出します。昔のホームページによくあった「アクセスカウンタ」です。今は廃れてしまった感がありますが、おかしな機能というわけではありません。
ユーザーページ /users/:id にどれぐらいのアクセスがあったかを数値で見せたいので、アクセス数の数値もリソースに含むことにします。GETすると数値が1増えるわけです。
すると、当然ですがGETするたび違うリソースが返ってくることになります。
前回引用したとおり、RFC2616にはGETも冪等と規定されています。これはまずいことになります。
こういう機能を実現するためには、これもPATCH(もしくはPOST)にしなければいけません。しかしそれでは、このURLに通常のリンクを張ることができなくなってしまいます。
これはたしかに極端な例でした。ではGETするたびに違う内容が返ってくるのはすべてダメかというと、そんなことはありません。
ものすごく単純ですが、「現在時刻リソース」を考えてみればわかります。これはGETするたびに違う内容を返しますが、「現在時刻」というリソースであることは変わりません。このGETは厳密な意味では冪等ではないのかもしれませんが、一般的には許容されていると思います。
これはPUTのタイムスタンプと大差ないと思いませんか?
部分更新も冪等
そこでinvoiceの例を振り返ってみると、
PUT /invoices/:id
で paid=1 を送ることは、完全に冪等です。実際これでほとんど問題ないと思います。
もし問題あるのであれば、
PUT /invoice/:id/paid
を簡単な記述で定義できるようにすればいいのでは?
「冪等」を捨てることのデメリット
全部のPUTをPATCHにしてしまうのは、「冪等」という制約を捨てることを意味します。前回挙げた冪等のメリットを捨ててしまうということです。
GET/POSTしかなかった世界に、RailsはなぜPUT/DELETEを導入したのでしょうか。PUTが外れるなら、PATCHじゃなくてPOSTでもいいのではないでしょうか。POSTも冪等ではないメソッドです。
一応conditional PATCHもあって冪等な形で適用することもできますが、ブラウザでは手動で制御することはできませんし、Railsがデフォルトでconditionalヘッダを発行するのはなかなか大変そうです。
PATCHによるdiff適用、インクリメントなど
(以下書けてません)
*1:ただし、RESTの名が示す通り、実際にやりとりされるのはリソースではなくリソースの表現なので、形式は変わってもよい