4

如果我从 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

所以只是为了缩短一点,假设我想要一个帐户所有位置的列表。上述路线大概是 account/1/locations。因此将我降落在位置#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:澄清一下,因为我得到了一些建议我改变我的模型关系的答案。我正在使用一个遗留系统,此时我无法更改关系。清理数据库和关系最终是我的目标,但现在我做不到。所以我需要找到一个适用于这种配置的解决方案。

4

3 回答 3

6

您当前的方法不是 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

inherit_resources是一个很好的宝石,可以干净地处理这样的场景。由 Jose Valim(Rails)撰写。我相信它应该适用于 HABTM,但老实说,如果我尝试过它,我并不肯定。

上面的例子本质上是inherited_resources 的工作原理,但大多数情况下它在幕后发挥了它的魔力,你只需要在需要时覆盖方法。如果它适用于 HABTM(我认为应该),您可以编写您当前的控制器,如下所示:

class LocationController < InheritedResources::Base
  belongs_to :contact, :account, :polymorphic => true, :optional => true
end
于 2011-09-08T04:33:03.990 回答
4

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

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

resources :accounts
resources :contacts
resources :locations

REST 的全部意义在于每个资源都有一个唯一的地址。如果您真的只想公开给定位置内的帐户/联系人,请执行以下操作:

resources :locations do
    resources :accounts
    resources :contacts
end

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

于 2011-09-08T02:29:11.877 回答
0

我的看法是,由于帐户和联系人似乎具有相似的行为,因此使用单表继承 (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

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

希望能帮助到你!

于 2011-09-08T02:33:44.883 回答