【问题标题】:REST API - Represent resource as primary and sub-resourceREST API - 将资源表示为主资源和子资源
【发布时间】:2015-12-10 15:13:15
【问题描述】:

在我的 Rails 4 应用程序中,我有以下模型:

class Location < ActiveRecord::Base
  has_many :location_parking_locations
  has_many :parking_locations, through: :location_parking_locations
end

class LocationParkingLocation < ActiveRecord::Base
  belongs_to :location
  belongs_to :parking_location
end

class ParkingLocation < ActiveRecord::Base
  has_many :location_parking_locations
  has_many :locations, through: :location_parking_locations
end

对路线/locations/1/parking_locations 的调用返回位置= 1 的停车位置集合。响应中的一些属性来自连接模型(location_parking_location)。例如,响应中的其中一个 park_locations 可能如下所示:

{
  id: 1
  name: 'Test Parking Location'
  description: 'Test description for the parking location'
  upvote_count: 10, <- this field comes from the join model
  downvote_count: 8, <- this field comes from the join model
}

如果用户想要更新直接来自停车位置的属性,例如名称,我觉得他们应该能够通过将停车位置视为主要资源的端点来执行此操作,而不是而不是必须将其作为特定位置的子资源来引用。例如,他们应该能够从端点/parking_locations/1 编辑或检索停车位置,而不是特定于某个位置。

在这种情况下,不应包含来自 location_parking_location 的字段,例如 upvote_countdownvote_count

是否有某种设计模式允许这种行为?还是我应该以不同的方式思考这个问题?例如,也许在检索端点locations/1/parking_locations 时,我不应该返回parking_locations 的集合,而是应该返回location_parking_locations 的集合,其中可以包含一个表示来自parking_location 的属性的对象。在这种情况下,端点是否应该更改为locations/1/location_parking_locations?例如,响应中的一个对象可能如下所示:

{
  id: 9
  upvote_count: 10,
  downvote_count: 8,
  parking_location: {
    id: 1
    name: 'Test Parking Location'
    description: 'Test description for the parking location'
  }
}

在这种情况下,有人会如何添加新的停车位?是否可以通过发布到/parking_locations 端点来完成?或者他们应该同时创建它并将其添加到一个位置,也许通过发布到/locations/1/parking_locations

在这种情况下,如果有人想将已经存在的停车位置添加到某个位置怎么办?在那种情况下,也许他们应该 PUT 到/locations/1/parking_locations/{parking_location_id}

【问题讨论】:

    标签: ruby-on-rails rest


    【解决方案1】:

    路线

    您可以通过一些简单的路由和许可控制器使您的ParkingLocation 模型既是主资源又是子资源。首先,路由 - 除了您已经将它作为 Location 的子资源之外,您还需要在顶层声明它。

    resources :locations do
      resources :parking_locations
    end
    
    resources :parking_locations
    

    如果您现在检查rake routes,您将看到两组都连接到ParkingLocationsController 的URL,一组在/parking_locations,一组嵌套在/locations/:location_id/parking_locations

    ParkingLocationsController

    您的控制器将始终收到包含ParkingLocation ID 的params[:id]。如果您访问嵌套路由,它还将收到包含Location ID 的params[:location_id]。任何表单提交等都将具有params[:parking_location],其中包含用于创建/更新/等的其余属性。一个简单的控制器可能如下所示:

    class ParkingLocationsController
      def index
        if params[:location_id]
          location = Location.find(params[:location_id])
          @parking_locations = location.parking_locations
        else
          @parking_locations = ParkingLocation.all
        end
      end
    
      def show
        @parking_location = ParkingLocation.find(params[:id])
      end
    
      # ...
    end
    

    注意index 的工作原理相同,无论location_id 参数来自哪里:

    /locations/1/parking_locations
    /parking_locations?location_id=1
    

    添加位置

    由于您的 RESTful 资源是 ParkingLocation,因此您应该在 ParkingLocationsController#update 中处理 Location 添加。为了简单和一致,您的控制器不应该关心路由是PUT /locations/:location_id/parking_locations/:id 还是只是PUT /parking_locations/:idhas_many 关联提供了ParkingLocation#location_ids=,您可以以任何您喜欢的形式收集它并传递给控制器​​。您的 update 操作看起来和往常一样:

    def update
      @parking_location = ParkingLocation.find(params[:id])
      if @parking_location.update(params[:parking_location])
        redirect_to @parking_location
      else
        render :edit
      end
    end
    

    这样的请求将在ParkingLocation 3 上设置Locations 12

    PUT https://your.server/parking_locations/3?parking_location[location_ids[]]=1&parking_location[location_ids[]]=2
    

    如果您想附加Locations,您需要做更多的编码来自己处理。如果您想在ParkingLocation 表单中创建Locations(反之亦然),您需要查看Rails 的nested form 功能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-09
      • 2016-02-15
      • 2015-07-02
      • 1970-01-01
      • 2019-12-29
      • 1970-01-01
      相关资源
      最近更新 更多