【问题标题】:Rails 3 - Best way to handle nested resource queries in your controllers?Rails 3 - 在控制器中处理嵌套资源查询的最佳方式?
【发布时间】:2011-09-08 02:20:18
【问题描述】:

如果我了解到关于 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:澄清一下,因为我得到了一些建议我更改模型关系的答案。我正在使用一个遗留系统,此时我无法更改关系。清理数据库和关系最终是我的目标,但现在我做不到。所以我需要找到一个适用于这种配置的解决方案。

【问题讨论】:

    标签: ruby-on-rails ruby ruby-on-rails-3 activerecord nested-resources


    【解决方案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 是干净处理此类场景的绝佳选择。由 Jose Valim(Rails)撰写。我相信它应该适用于 HABTM,但老实说,如果我曾经尝试过它,我并不肯定。

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

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

    【讨论】:

    • 我真的很喜欢你用这个去哪里。我将看看是否可以将其推断到我更复杂的模型中。我没有用它作为例子,因为我想在不压倒人们的情况下解决我的问题。我一定会报告我的结果。谢谢!
    【解决方案2】:

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

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

    resources :accounts
    resources :contacts
    resources :locations
    

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

    resources :locations do
        resources :accounts
        resources :contacts
    end
    

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

    【讨论】:

    • 我不同意你的观点。但是你没有回答我的问题。我的问题是关于控制器应该如何处理您的上述情况。我正在使用一个遗留系统,这些关系现在确实存在,直到我可以清理它们。因此,在您的示例中,我仍然试图弄清楚如何在位置#index 收到 account_id 与 contact_id 时处理
    • 那么你就完成了。如果您无法修复应用程序,则无法修复它。您的控制器与底层路由所允许的一样“正确”。
    【解决方案3】:

    我的看法是,由于帐户和联系人似乎具有相似的行为,因此使用单表继承 (STL) 并拥有另一个资源(例如用户)是有意义的。

    这样你就可以做到...

    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
    

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

    希望对你有帮助!

    【讨论】:

    • 顺便说一句,我绝对同意 meagar。如果你想让你的应用保持 RESTful,你不应该有多个路由来实现同样的事情。
    • 我也同意微不足道的观点,但我不能改变现在的方式,所以我仍然需要找到一种在系统内工作的方法。至于你的回答,我可以说它们服务于两种完全不同的行为。他们唯一的共同点就是地点。当我开始谈论帐户如何拥有子帐户并且它们具有多对多关系的计费位置、主要位置、附属位置时,情况变得更加复杂。
    • 考虑到你不能修改模型,我想说这是处理嵌套资源的唯一方法。我能给你的唯一建议是永远不要让资源嵌套超过 2 层。否则你的 url 和你的控制器都会看起来很讨厌
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多