コントローラーアーキテクチャ:毎日の簡単なヒント

コントローラーは「薄く」なければならないことは誰もが知っていますが、機能が大きくなるにつれて、コントローラーをきれいに保つことがますます難しくなっています。 コードの品質を損なうことなく、コントローラーを可能な限りクリーンに保つ方法に関する推奨事項を提供したい思います。



1. inherited_resourcesを使用する



inherited_resourcesに基づいてすべてのコントローラーを構築します。これにより、一般的なCRUDコードを回避できます。 InheritedResources :: Baseから継承されたコントローラーの宣言の2行のみで、リソースですべての基本操作(作成/表示/更新/削除)を実行できます! すべては問題ありませんが、多くの場合、問題が発生します。



最初の問題を解決するために、適切なコントローラーメソッド(インデックス)をオーバーラップして選択に必要な変更を加えることができますが、inherited_resourcesのドキュメントで説明されているはるかに洗練されたメソッドがありますが、何らかの理由でそれを使用する人はほとんどいません。



2. has_scope拡張機能を使用します



このgemを使用すると、モデルに記述されているスコープをリソース選択/リソースコレクションに組み込むことができます。

どのように機能しますか? たとえば、すべてのブログ投稿を表示する必要があります。 ブログの投稿をフィルター:



class PostsController < InheritedResources::Base

has_scope :by_blog, :only => :index

end



class Post < ActiveRecord::Base

belongs_to :blog

scope :by_blog, lambda{|blog_id| where (:blog_id => blog_id)}

end



* This source code was highlighted with Source Code Highlighter .






現在、リクエスト/投稿の場合、By_blog = 1の場合、リソースコレクションはid = 1のブログによって自動的にフィルタリングされます。 しかし、あまり美しくないので、ルートに次のように書きます。



get "blogs/:blog_id(/page/:page)(.:format)" => "posts#index" , :constraints => { :page => /\d+/ }, :defaults => { :page => 1 }

resources :posts




* This source code was highlighted with Source Code Highlighter .






そして、同じ結果がURL /ブログ/ 1で得られます。



コレクションを絶えずソートする必要がある場合は、デフォルトのパラメーターを使用できます-スコープは常に指定されたデフォルト値で適用されます:



class PostsController < InheritedResources::Base

has_scope :ordered, : default => 'created_at DESC'

end



class Post < ActiveRecord::Base

scope :ordered, lambda{|field| order(field)} #

end




* This source code was highlighted with Source Code Highlighter .






同様に、N + 1クエリの問題を排除できます。



class PostsController < InheritedResources::Base

has_scope :eager_loading, : default => 'true' , :only => :index

end



class Post < ActiveRecord::Base

scope :eager_loading, preload(:blog, :user, :tags)

scope :eager_loading2, includes(:blog, :user)

end




* This source code was highlighted with Source Code Highlighter .




一度に複数のスコープを指定する必要がある場合、いくつかの追加条件をチェックする、または単にモデルでスコープを作成したくない場合は、ブロックでhas_scopeパラメーターを設定するときに直接スコープを指定できます。



class PostsController > InheritedResources::Base

has_scope :blog do |controller, scope, value |

value != "all" ? scope. where (:blog_id => value ) : scope

end

end




* This source code was highlighted with Source Code Highlighter .






has_scopeの詳細については、プロジェクトページをご覧ください 。 HAMLからの効果は、機能に慣れるのに少し時間を費やすことですが、多くの時間を節約できます。



3.ページネーションにはkaminari / will_paginateを使用します



これらの宝石は非常に人気があり、怠け者だけがそれらを使用しませんでした。 inherited_resourcesとどのように統合しますか? kaminariには何の問題もありません-前の例と同様に、コントローラーで通常のスコープ:ページとして使用できます。 しかし、will_paginateを使用すると、少し手を加える必要があります。 それが提供するpaginateメソッドは、モデルスコープではありません。 しかし、ここには非常に洗練されたソリューションがあります-次のようにコントローラーのコレクションメソッドをオーバーライドする必要があります:



class PostsController < InheritedResources::Base

protected

def collection

@posts ||= end_of_association_chain.paginate(:page => params [:page])

end

end




* This source code was highlighted with Source Code Highlighter .






このトリックは非常に頻繁に使用されるため、モジュールに取り出して、必要に応じて他のコントローラーに接続することができますが、次回はそれ以上になります。



4. 認証承認にそれぞれdevisecancanを使用ます



このバンドルを使用すると、管理パネル用の個別のコントローラーなしで完全に実行できます。 コントローラからリソースへのアクセスを共有するロジックを完全に削除し、宣言のみを残して、別の記事に値することができます。



5.可能であれば、標準のコントローラーメソッドの重複を避けます。



すべての追加チェック、パラメータの正規化、および標準操作のその他のアクションは、before_filterで実行できます。



class PostsController < InheritedResources::Base

before_filter lambda{ resource.user = current_user }, :only => :create

before_filter lambda { resource.thumb = nil if params [:thumb_delete] }, :only => :update

end




* This source code was highlighted with Source Code Highlighter .








6. RSSフィードの形成、AJAXコレクションのロードなど。 別のメソッドを作成する必要はありません



/posts.rssおよび/posts.jsonという必要な形式のコレクションを要求するだけで十分ですが、コントローラーでは登録するだけで十分です。

class PostsController < InheritedResources::Base

respond_to :html

respond_to :rss, :json, :only => :index

end




* This source code was highlighted with Source Code Highlighter .






さらに、RSSを作成するには、テンプレートにposts / index.rss.builderを登録する必要があります。

xml.instruct! :xml, :version => "1.0"

xml.rss :version => "2.0" do

xml.channel do

xml.title ""

xml.description ""

xml.link collection_url(:format => :rss)



for resource in collection

xml.item do

xml.title resource.title

xml.description "#{resource.annotation}\n#{link_to ' ...', resource_url(resource)}"

xml.pubDate resource.published_at.to_s(:rfc822)

xml.link resource_url(resource)

xml.guid resource_url(resource)

end

end

end

end




* This source code was highlighted with Source Code Highlighter .








次のjQueryコードを使用してJSONコレクションを処理できます。

$( '#blog_id' ).live( 'change' , function () {

$.ajax({

url: '/posts.json' ,

dataType: 'json' ,

data: { blog_id: $( this ).val() },

success: function (json) {

var options = '' ;

for ( var i = 0; i < json.length; i++) {

options += '<option value="' + json[i].id + '">' + json[i].title + '</option>' ;

}

$( '#destination' ).html(options);

}

});

});




* This source code was highlighted with Source Code Highlighter .






したがって、柔軟な機能、RESTアプローチ、およびコードの透過性を維持しながら、コントローラーで最小限のコードを実現することが可能です。 これらの原則はすべて実際のプロジェクトに実装されており、実際にうまく機能しています。 一部の統計:10か月の開発後、 SmartSourcingプロジェクトの最大のコントローラーは86行、最小-2行ですが、ほとんどのコントローラーは20 LOCを超えません。



All Articles