RubyKaigi 2013 で発表しました

RubyKaigi 2013 の最終日、6/1に「Rails Gems realize RESTful modeling patterns」というタイトルで発表させていただきました。

Ustream RubyKaigi Channel 2

相変わらずコードの実例に乏しく、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的手法は、大クラス主義のRubyRailsに慣れた身としてはちょっともやっとするところもあるのだが、手法としては理解しておく必要があるし、バランスを考えて適用していくべきだろうな。

@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に対する操作

Methods That Change Position and Reorder List
  • list_item.insert_at(2)
  • list_item.move_lower will do nothing if the item is the lowest item
  • list_item.move_higher will do nothing if the item is the highest item
  • list_item.move_to_bottom
  • list_item.move_to_top
  • list_item.remove_from_list
https://github.com/swanandp/acts_as_list
基本的な考え方
  • 操作がCRUDにおさまらないように見えるときは、そこに新たなリソースが隠れている
    • 名詞、形容詞、または動詞の現在分詞、過去分詞に着目
    • position, order, moved(moving), high(higher), top などが新たなリソースの候補
  • 新たなリソースをつくるとき、GETして意味があるかどうかを考える
    • GETする意味のないものはリソースとして不適格*1
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に含まれていればうれしいですよね。
僕が目指しているのはそういう方向です。

*1:これはかなり原理主義的なスタンスではあります

最近つくった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を返せば成功です。

既知の問題

X-editableは内部で jQuery.ajax() メソッドを使っているのですが、jQuery 1.9.0で仕様が変わり、 dataType: json を指定したときにレスポンスボディが空だと、不正なJSONとみなして失敗するようになりました。
これにより、成功時に 204 No Content を返すことができません。
これはjQueryのバグで、すでに修正されているので次のjQueryのリリースでは正常になるはずですが、それまでは何らかの回避策を講じる必要があります。

  • ステータス204を使わず、200 OKでレスポンスボディを返す
  • dataType: null を設定する(ただし、デフォルトでJSONを返すようにする必要があります)
  • jquery-rails 2.2.0 を使わず 2.1.4 を使用する

次回予告(?)

ということで今、もう1つgemをつくっています。GitHubに途中のものはあるのですが、こんなことができます。

Rails 4 で strong parameters を使うようになって、クエリパラメータで絞りこみたい、というときの定番gemにしたいと思っています。詳しくはまたこんど。

ABC予想とフェルマーの最終定理

ちょっと前に流れたこのニュースを見て、おおっと思いながらわからないのにまたWikipediawikipedia:ABC予想)を眺めたりしていたのだが、やはり気になるのはこの部分。

整数論の代表的難問であり、解決に約350年かかった「フェルマーの最終定理」も、この予想を使えば一気に証明できてしまう

ググったりしていると、これについてとてもわかりやすい説明があったので書き出してみる。

用語の定義

abc-triple

正の整数 a, b, c について、a + b = c かつ a, b は互いに素である三つ組 (a, b, c)

rad(n)

正の整数 n の、互いに異なる素因数の積
例:\mbox{rad}(504) = \mbox{rad}(2^3 \cdot 3^2 \cdot 7) = 2 \cdot 3 \cdot 7 = 42

ABC予想

(a,b,c)が abc-triple ならば、c < \mbox{rad}(abc)^2 が成り立つ。
(これは強い形の予想で、今回証明されたのはもうちょっと精密で弱い形のようだけど)

フェルマーの最終定理

3以上の整数 n について、 x^n + y^n = z^n を満たす正の整数 (x, y, z) の組は存在しない。

ABC予想フェルマーの最終定理

背理法による。 x^n + y^n = z^n を満たす正の整数 (x, y, z) が存在すると仮定する。
a = x^n,\: b = y^n,\: c = z^nとすると、ABC予想より
\LARGE z^n \lt \mbox{rad}(x^n y^n z^n)^2 = \mbox{rad}(xyz)^2 \le (xyz)^2 \lt z^6
よって n < 6 であるが、n = 3, 4, 5 についてのフェルマーの最終定理オイラーフェルマー、ディリクレによって証明されているので矛盾する。□

「リソースモデリングパターンの提案」の先

RailsにおけるRESTfulなURL設計勉強会」で、僕は「リソースモデリングパターンの提案」という発表をさせてもらいました。はてブなどではいいコメントをいただいていてありがとうございます。

やりたいこと

パターン提案自体の目的はスライド5ページ目に書いたのですが、とくにまとめというものもなくて、ほんとに列挙しただけになっています…。
Railsには“resources”というすばらしい「パターン」があるのですが、やっぱりそれだけではどうリソースを設計していいかわからなくなることが多いです。
リソース設計にももっと「レール(具体的なパターン)」があってもいいんじゃないか、少なくともRailsには。だってRailsはそういう思想だから。
実際、スライドの中で触れている meta_searchkaminari は、「リソース設計」とは言っていないけれど、その具現化です。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として開催させてもらいました。

発表内容・スライド

思いがけず t_wada さんと moro さんに話していただけることになって、ものすごく内容の濃い発表になりました。後半にみんなで議論をしようという予定で、そんなに大きくない部屋で15人という人数だったのですが、発表が盛り上がって議論の時間がなくなってしまいました…。
この勉強会の2回目として、議論会・相談会をやる予定ですので、よろしくお願いします。また、来週のSendagaya.rbの定例で復習の会もやります。

参加者のみなさんありがとうございました。

個人的結果(?)

REST厨だった自分撃沈…というわけではないんですが、いい意味でREST厨でありたいと思います。

リソースの部分更新 の草稿

だいぶ前に「次期RailsがPATCHメソッドを採用」という記事を書いて、

ただし、上記ではPATCH採用のもう1つの理由である「リソースの部分更新」あたりの説明をあえて省略しました。これはちょっと個人的に思うところがあるからです。

という感じなのですが、この詳細は次回に続くということで…。

と言ってそのまま放置していたのですが、途中まで書きかけのものをたまたま見つけたので、ついでに載せてみます。


前回の続きです。
前回省略したのですが、PUTの特徴的な性質は実はもう1つあります。

9.6 PUT

PUT メソッドは、同封されたエンティティを供給される Request-URI の元に保存するように要求する。 Request-URI が既に存在するリソースを参照している場合は、同封されるエンティティはオリジンサーバにあるそれの修正版とみなされるべきである。

http://www.studyinghttp.net/cgi-bin/rfc.cgi?2616#Sec9.6

新たなメソッドは、相互互換性を改善し、エラーを排除するために必要である。既にPUTメソッドが、完全に新たな本体{body}をもってリソースを上書きするために定義されているが、このメソッドでは部分的な変更を行うために再利用することはできない。
(中略)
PUTリクエストでは、同封されたエンティティはオリジンサーバ上に保存されるリソースの修正バージョンであるとみなされ、クライアントは保存されるバージョンが置き換えられることを要求している。

http://www.studyinghttp.net/rfc_ja/rfc5789

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の名が示す通り、実際にやりとりされるのはリソースではなくリソースの表現なので、形式は変わってもよい