RailsはRESTの哲学に基づいているのでそれを守ろうという話。だいたい関連していたので三つのパターンをまとめた。長い……
アンチパターン:Monolicic Controller
RESTfulではないと、たとえばこんなことが起こってくる。
- resourceではなく、actionを指定するようなパラメータ
- 独自のaction(index,new,create,edit,update,show,destroy以外のやつ)
def users
per_page = Variable::default_pagination_value
@users = User.find(:all)
# First, check to see if there
# was an operation performed
if not params[:operation].nil? then
if (params[:operation] == "reset_password") then
user = User.find(params[:id]) user.generate_password_reset_access_key
user.password_confirmation = user.password
user.email_confirmation = user.email
user.save!
flash[:notice] = user.first_name + " " + user.last_name + "'s password has been reset."
end
の例。 params[:operation]
というのが怪しいと。このまま方針進んでいくとさらにparam[:operation]
で条件分岐しまくってカオスになってくる未来が見える。
RESTfulなアプリケーションは動詞はHTTP requestメソッドで、対象をurlで指定する。urlで「何を」行うか指定するのはSOAPのやり方でRESTではない。
勘違いされやすいのだけど、RESTfulなリソースにするということとモデルを用意するということはイコールではない。あるものが操作可能であると捉えて、それをurlで指定できれば実際のモデルが存在していなくてもそれはRESTful resource足り得る。
たとえばuserが持つpasswowdカラムをRESTfulなリソースと捉えて、コントローラーを定義してみる。
class PasswordsController < ApplicationController
def create
user = User.find(params[:id])
user.generate_password_reset_access_key
user.password_confirmation = user.password
user.email_confirmation = user.email
user.save!
flash[:notice] = user.first_name + " " + user.last_name + "'s password has been reset."
end
end
もともとの悪い例ではこのpasswordの操作もUserControllerの一部であったが、リソースとして分離することでControllerを分割できた。
あらゆる操作を内包したコントローラーは次第に大きくなり、細かい条件分岐をするために謎パラメータが増えていく。そうなる前に適切にリソースを切り分けよう。という話だった。
結論:RESTfulなリソースとして扱うこととモデルを用意することは等価ではない。現状コントローラーで行っている処理対象を別リソースとして切り分けれないかどうか考えてみよう。
SRPの法則はControllerにも適用できる。というか適用しよう。
アンチパターン:Lost child controller
ある親子関連の適当なテーブルがあったとして、毎回、
(この例ではalbu has_many songs、という関連になっている)
class SongsController < ApplicationController
def new
@song = Song.new(:album_id => params[:album_id])
#...
などとやるのは、よろしくない。Railsの機能でResourceはネストできるのでそれを使おう。
ソリューション:resouceのネストを利用する。またネストしたか、していないかの文脈でContorollerを分ける。
MyApp::Application.routes.draw do resources :albums do
resources :songs end
これにより、便利な
GET /albums/:album_id/songs(.:format)
POST /albums/:album_id/songs(.:format)
GET /albums/:album_id/songs/new(.:format)
……
などのルーティングが提供される。
また、同じモデルが対象でも、ネストした場合とそうでないリソースについてはControllerを分けたほうがいいときがある。
この例なら、
controllers/messages_controller.rb
controllers/users/messages_controller.rb
というようにネストに合わせてサブディレクトリを切って、Contorollerを分ける。文脈によって行われるアクション(の動作)は異なってくるのだから多少DRYさを犠牲にしても、こうしたほうがいい場合もある。
結論:適切な粒度でコントローラーを分割しよう。DRYにこだわりすぎず、簡潔にかけることを重視したほうがいいときもある。
所感
RESTFulについて熱く語られてて、以前よりこの言葉について実感がわいた気がする。また、contorollers内にサブディレクトリを切るという発想は斬新だった。
ところでrailsがこの哲学を強要していることについては昔からかなりいろいろ議論があって、いろいろ調べてみると使いやすいようにいろいろ試してみたり、あるいは新しい機能があったり面白い。
http://qiita.com/kuboon/items/96bbd227f9497ed81f38
http://qiita.com/r7kamura/items/dea3c58b6fc81142a363
この辺りについても読んだ上で、柔軟にアプリケーション構成を考えられるようになればいいなと思う。