LoginSignup
7
7

More than 5 years have passed since last update.

Railsアンチパターン<コントローラー編>④,⑤,⑥,⑦Resourceとは

Posted at

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
この辺りについても読んだ上で、柔軟にアプリケーション構成を考えられるようになればいいなと思う。

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7