Rails 3 – 在控制器中处理嵌套资源查询的最佳方法?

如果我对Rails 3有一点了解,那就是如果我很难做某事,我可能做错了。 所以我正在寻求帮助。

我有一些与多对多关系相关的模型。

我能够在没有问题的情况下在模型中创建关联。 我的问题在于如何构建控制器以使用这些关系。 如果你没看到我要去的地方,我会试着举个例子。

例如…

class Account < ActiveRecord::Base has_many :locations end class Contact < ActiveRecord::Base has_many :locations end class Location < ActiveRecord::Base has_and_belongs_to_many :accounts has_and_belongs_to_many :contacts end 

假设我有上述型号。 这将是我的资源……

 resources :accounts do resources :locations end resources :contacts do resources :locations end resources :locations do resources :accounts resources :contacts end 

所以只是为了缩短这一点,让我们说我想要一个帐户所有位置的列表。 上述路线可能是账号/ 1 /位置。 因此登陆我的位置#index。

希望我在这一点上没有搞砸我的例子,但是建立这个行动的最佳方法是什么,因为它确实有多个工作……至少是帐户,联系人和所有地点的位置。

所以我最终得到这样的东西……

 class LocationController < ApplicationController def index if params[:account_id] @locations = Location.find_all_by_account_id(params[:account_id]) elsif params[:contact_id] @locations = Location.find_all_by_contact_id(params[:account_id]) else @locations = Location.all end respond_with @locations end end 

更新#1:澄清一下,因为我得到的一些答案表明我改变了我的模型关系。 我正在使用遗留系统,在此系统中我无法改变关系。 最终我的目标是清理数据库和关系,但现在我不能。 所以我需要找到一个适用于此配置的解决方案。

你当前的方法不是DRY,如果你想要在索引上强加额外的范围,会让你头疼。 例如,按字段分页,排序或搜索。

考虑一个替代方案:注意你的if / elsif / else条件本质上是如何找到发送find的查找范围? 为什么不把这个责任转移到一个方法呢? 从而简化您的操作并删除冗余代码。

 def index respond_with collection end def show respond_with resource end protected # the collection, note you could apply other scopes here easily and in one place, # like pagination, search, order, and so on. def collection @locations ||= association.all #@locations ||= association.where(:foo => 'bar').paginate(:page => params[:page]) end # note that show/edit/update would use the same association to find the resource # rather than the collection def resource @location ||= association.find(params[:id]) end # if a parent exists grab it's locations association, else simply Location def association parent ? parent.locations : Location end # Find and cache the parent based on the id in params. (This could stand a refactor) # # Note the use of find versue find_by_id. This is to ensure a record_not_found # exception in the case of a bogus id passed, which you would handle by rescuing # with 404, or whatever. def parent @parent ||= begin if id = params[:account_id] Account.find(id) elsif id = params[:contact_id] Contact.find(id) end end end 

inherited_resources是一个很好的gem,可以干净地处理这样的场景。 由Jose Valim撰写(Rails)。 我相信它应该与HABTM一起工作,但说实话,如果我曾经尝试过,我并不积极。

上面的例子基本上是inherited_resources的工作原理,但大多数它在幕后工作,你只需要覆盖方法。 如果它适用于HABTM(我认为它应该),你可以写这样的当前控制器:

 class LocationController < InheritedResources::Base belongs_to :contact, :account, :polymorphic => true, :optional => true end 

您不应该提供多种方法来获取相同的资源。 不应该将资源的1对1映射到关联。

您的路线文件应如下所示:

 resources :accounts resources :contacts resources :locations 

REST的重点是每个资源都有一个唯一的地址。 如果您确实只想在给定位置公开帐户/联系人,请执行以下操作:

 resources :locations do resources :accounts resources :contacts end 

但绝对不应该同时提供嵌套帐户/位置和位置/帐户路由。

我看到它的方式是,由于帐户和联系人似乎有类似的行为,因此使用单表inheritance(STL)并拥有另一个资源(例如User)是有意义的。

那样你就可以做到这一点……

 class User < ActiveRecord::Base has_many :locations end class Account < User end class Contact < User end class Location < ActiveRecord::Base has_and_belongs_to_many :user end 

资源保持不变......

 resources :accounts do resources :locations end resources :contacts do resources :locations end resources :locations do resources :accounts resources :contacts end 

然后,您可以使用相同的方式访问位置,而与作业类型无关。

 class LocationController < ApplicationController def index if params[:user_id] @locations = Location.find_all_by_account_id(params[:user_id]) else @locations = Location.find_all_by_id(params[:account_id]) end respond_with @locations end end 

这样,您的代码就变得可重用,可扩展,可维护,而且我们被告知的所有其他好东西都很棒。

希望能帮助到你!