开发者

Rails Way to handle action that's available on multiple routes

I have the following routes:

resources :users do
  # List reviews made by user开发者_开发技巧
  resources :reviews, :only => [ :index ]
end

resources :products do
  # List reviews by product, and provide :product_id for creation
  resources :reviews, :only => [ :index, :new, :create ]
end

# Other actions don't depend on other resources
resources :reviews, :except => [ :index, :new, :create ]

Everything looks right, except ReviewsController#index:

def index
  if params[:user_id]
    @reviews = Review.find_all_by_user_id params[:user_id]
  else
    @reviews = Review.find_all_by_product_id params[:product_id]
  end
  respond_with @reviews
end

I was wondering if there's a standard solution to the problem above, or if there is a better way to do it.


What you have there is fine, but if you want you can use two different actions as well. This approach should allow you to more easily change the view later on, and is a bit safer.

match '/products/:product_id/reviews' => 'reviews#product_index'
match '/users/:user_id/reviews' => 'reviews#user_index'

It will also keep your controller code a little cleaner and less susceptible to odd queries like /products/10/reviews?user_id=100 which would result in the user's reviews being shown instead of the product's reviews.

def product_index
  @reviews = Review.find_all_by_product_id params[:product_id]
  respond_with @reviews
end

def user_index
  @reviews = Review.find_all_by_user_id params[:user_id]
  respond_with @reviews
end

The other alternative is to use different controllers as well:

match '/products/:product_id/reviews' => 'product_reviews#index'
match '/users/:user_id/reviews' => 'user_reviews#index'


Some plugins have ways to load resources for you, such as declarative_authorization or cancan, I'm sure there are others.

Other solutions I have seen is to make a private method in the controller to load the object, and in that method is essentially the same logic you have here; it just moves it out of the index action itself. The metod can also then be called as a before filter.

One other way to do your logic is to start with the parent object (nice if you need the parent object too:

before_filter :load_collection, :only => :index

private
def load_collection
  if params[:user_id]
    @user = @parent = User.find(params[:user_id])
  else
    @product = @parent = Product.find(params[:product_id])
  end
  @reviews = @parent.reviews
end


def index
  key = [:user_id, :product_id].find{|k| params[k]}
  @reviews = Review.where(key => params[key]).first
  respond_with @reviews
end
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜