検索処理をスコープに入れる

元記事はこちら

翻訳する


BadSmell


class PostsController < ApplicationController
  def index
    @published_posts = Post.find(:all, :conditions => { :state => 'published' },
                                       :limit => 10,
                                       :order => 'created_at desc')

    @draft_posts = Post.find(:all, :conditions => { :state => 'draft' },
                                   :limit => 10,
                                   :order => 'created_at desc')
  end
end

コントローラー内で公開済み(published_posts)と下書き(draft_posts)の検索をするために、込み入った検索処理が書かれているが、以下の2点がよくない。

  1. コントローラー内に込み入った検索処理を書いてしまうと、たいていの場合長くなってしまい、読みづらい。
  2. 同じような込み入った検索処理は、他のコントローラーにも存在するだろうし、ロジックを変更させたくなった場合に、複数の箇所に手を入れないといけないという作りは、バグの温床になる。

Refactor((~3.0.9。Rails3.1~ はnamed_scope -> scope))

    class PostsController < ApplicationController
      def index
        @published_posts = Post.published
        @draft_posts = Post.draft
      end
    end
    
    class Post < ActiveRecord::Base
      named_scope :published, :conditions => { :state => 'published' },
                              :limit => 10,
                              :order => 'created_at desc'
      named_scope :draft, :conditions => { :state => 'draft' },
                              :limit => 10,
                              :order => 'created_at desc'
    end

込み入った検索処理をコントローラーからモデルに移動した。ご覧の通り、コントローラーのコードが非常にシンプルになった。 込み入った検索処理はモデルにだけ存在するので、ロジックが変わったら、モデル内のコードを変更するだけでいい。

updated: Rails3以降では、named_scopeの代わりにscopeを使いましょう。

感想

scopeだとこんな感じかな

 class Post < ActiveRecord::Base
      scope :published, :conditions => { :state => 'published' },
                              :limit => 10,
                              :order => 'created_at desc'
      scope :draft, :conditions => { :state => 'draft' },
                              :limit => 10,
                              :order => 'created_at desc'
    end

limit/orderの意味上の役割にもよるけど、ここを別scopeにして、mergeしてもいいのかな。

  class PostsController < ApplicationController
      def index
        @published_posts = Post.published.view_limit
        @draft_posts = Post.draft.view_limit
      end
    end

    class Post < ActiveRecord::Base
      scope :published, -> { where(:state, "published" }
                              
      scope :draft, -> { where(:state, "draft" }
      
      scope :view_limit, -> :conditions => :limit => 10,
                              :order => 'created_at desc'
                        
    end