REST API - 将资源表示为主资源和子资源

时间:2015-12-10 15:13:16

标签: ruby-on-rails rest

在我的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)。例如,响应中的一个parking_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编辑或检索parking_location,而不是特定于某个位置。

在这种情况下,不应包含来自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

在这种情况下,如果有人想将现有停车位添加到某个位置怎么办?在这种情况下,他们可能应该投入/locations/1/parking_locations/{parking_location_id}

1 个答案:

答案 0 :(得分:1)

路线

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

resources :locations do
  resources :parking_locations
end

resources :parking_locations

如果您现在检查rake routes,则会看到两组网址都连接到ParkingLocationsController,一组设置为/parking_locations,另一组设置为/locations/:location_id/parking_locations

ParkingLocationsController

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

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,因此您应该在Location中处理ParkingLocationsController#update个添加内容。为简单起见,您的控制器不应关心路由是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

此类请求会在Location 1上设置2 s ParkingLocation3

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

如果你想追加Location,你需要做一些编码来自己处理。如果您想在Location表单中创建ParkingLocation(反之亦然),则需要查看Rails的nested form功能。