Rails:ネストされたリソースの競合、呼び出されたルートに応じてインデックスアクションをスコープする方法
-
06-07-2019 - |
質問
2つの定義されたルートがあるとします:
map.resources articles
map.resources categories, :has_many => :articles
ヘルパー/パスでアクセス可能
articles_path # /articles
category_articles_path(1) # /category/1/articles
/ articles
にアクセスすると、 ArticlesController
からの index
アクションが実行されます。
/ category / 1 / articles
にアクセスすると、 ArticlesController
からの index
アクションも実行されます。
では、呼び出しルートに応じてスコープ付き記事のみを条件付きで選択するための最良のアプローチは何ですか?
#if coming from the nested resource route
@articles = Articles.find_by_category_id(params[:category_id])
#else
@articles = Articles.all
解決
ここには、ロジックとビューがスコープに関連付けられている度合いに応じて、2つの選択肢があります。 さらに説明させてください。
最初の選択肢は、他の応答で既に説明したように、コントローラー内のスコープを決定することです。通常、テンプレートでいくつかの追加の利点を得るために@scope変数を設定します。
class Articles
before_filter :determine_scope
def index
@articles = @scope.all
# ...
end
protected
def determine_scope
@scope = if params[:category_id]
Category.find(params[:category_id]).articles
else
Article
end
end
end
@scope変数の理由は、単一のアクション以外のリクエストのスコープを知る必要があるかもしれないからです。ビューにレコードの数を表示したいとします。カテゴリでフィルタリングしているかどうかを知る必要があります。この場合、 params [:category_id]のチェックを毎回繰り返すのではなく、単に
。 @ scope.count
または @ scope.my_named_scope.count
を呼び出すだけです。
このアプローチは、カテゴリのあるビューとカテゴリのないビューが非常に似ている場合にうまく機能します。しかし、カテゴリーでフィルターされたリストがカテゴリーのないリストと比較して完全に異なる場合はどうなりますか?これは非常に頻繁に発生します。カテゴリセクションはカテゴリに焦点を合わせたウィジェットを提供し、記事セクションは記事に関連するウィジェットとフィルタを提供します。また、Articleコントローラーには使用したい特別なbefore_filterがいくつかありますが、記事リストがカテゴリーに属している場合は使用する必要はありません。
この場合、アクションを分離することもできます。
map.resources articles
map.resources categories, :collection => { :articles => :get }
articles_path # /articles and ArticlesController#index
category_articles_path(1) # /category/1/articles and CategoriesController#articles
カテゴリでフィルタリングされたリストは CategoriesController
で管理され、すべてのコントローラーフィルター、レイアウト、設定を継承します...フィルタリングされていないリストは ArticlesController
。
これは通常、追加のアクションを使用すると、大量の条件チェックでビューとコントローラーを乱雑にする必要がないため、私のお気に入りの選択です。
他のヒント
これらのアクションを分離したいことがよくあります。結果のアクションが非常に似ている場合、params [:category_id] が存在するかどうかを確認することで、コントローラー内のスコープを簡単に分離できます(@SimoneCarlettiの回答を参照)。
通常、カスタムルートを使用してコントローラー内のアクションを分離すると、柔軟性と明確な結果が得られます。次のコードは通常のルートヘルパー名になりますが、ルートはコントローラーの特定のアクションに向けられます。
routes.rb 内:
resources categories do
resources articles, :except => [:index] do
get :index, :on => :collection, :action => 'index_articles'
end
end
resources articles, :except => [:index] do
get :index, :on => :collection, :action => 'index_all'
end
[ ArticlesController.rb ]に追加できます
def index_all
@articles = @articles = Articles.all
render :index # or something else
end
def index_categories
@articles = Articles.find_by_category_id(params[:category_id])
render :index # or something else
end
ネストされた単一のリソースのみを持ち、paramsに基づく条件を使用してスコープを決定するのが最も簡単なアプローチです。これはおそらくあなたの場合に行く方法です。
if params[:category_id]
@articles = Category.find(params[:category_id]).articles
else
@articles = Article.all
end
ただし、モデルにネストされた他のリソースの種類によっては、このアプローチを続けるのは非常に面倒です。その場合、 resource_controller または make_resourceful は、これをより簡単にします。
class ArticlesController < ResourceController::Base
belongs_to :category
end
これは実際にあなたが期待するすべてを行います。すべての標準RESTfulアクションを提供し、 / categories / 1 / articles
のスコープを自動的にセットアップします。
if params[:category_id].blank?
# all
else
# find by category_id
end
ルートから独立したアクションを検討したいと思います。どうやってそこにたどり着いたとしても、何をすべきかについて合理的な決定を下してください。