我正在使用JSON作为前端和后端之间的格式来构建Rails 5+ API。
我希望能够创建一个或多个记录,这取决于是否发送了JSON对象数组。
请注意,我没有使用JSON:API规范,而是将JSON对象作为根属性来使用。
# Create a single child object for the associated parent
POST api/v1/parents/1/children PARAMS: { child_name: "Alpha" }
# Create multiple children objects for the associated parent
POST api/v1/parents/1/children PARAMS: [{ child_name: "Alpha" }, { child_name: "Bravo" }]
在控制器中,我必须区分是发送单个对象还是数组。如果设置了params["_json"]
标头,Rails似乎会自动将JSON数据转换为Content-Type="application/json"
密钥,我正在使用它来判断是否传递了数组。
class ChildrenController < ApplicationController
def create
@parent = Parent.find(params[:parent_id])
if params["_json"] && params["_json"].is_a?(Array)
@children = []
params["_json"].each do |child_attributes|
@children << @parent.children.create!(child_attributes)
end
render json: @children
else
@child = @parent.children.create!(child_params)
render json: @child
end
end
def child_params
params.permit(:child_name)
end
end
问题
params["_json"]
判断是否传递数组的标准方法?似乎很骇人,但我不确定是否有更好的方法。child_params
方法?当前,如果用户传递一个JSON对象数组,他们可以放置所需的任何属性,而我不会将其过滤掉。update
操作创建单个端点,该端点也可以接受单个或多个对象。这是不好的做法吗?如果您将JSON数组(例如[{ child_name: "Alpha" }, { child_name: "Bravo" }]
)发送到端点,Rails会将其存储到params哈希的_json
中。这样做的原因是,与单个JSON对象不同,如果不引入另一个键,则无法将数组提取到哈希中。
例如,我们将{ child_name: "Alpha" }
发布到端点。参数看起来像:
{"child_name"=>"Alpha", "format"=>:json, "controller"=>"api/children", "action"=>"create"}
现在,如果我们发布数组[{ child_name: "Alpha" }, { child_name: "Bravo" }]
,并且将它像JSON对象那样被[[blindly提取到哈希中,结果将如下所示:
{[{ child_name: "Alpha" }, { child_name: "Bravo" }], "format"=>:json, "controller"=>"api/children", "action"=>"create"}
这是有效哈希!因此,Rails将您的JSON数组包装到不是
_json
中,它变成:{"_json" => [{ child_name: "Alpha" }, { child_name: "Bravo" }], "format"=>:json, "controller"=>"api/children", "action"=>"create"}
发生这种情况here in the actionpack。是的,这似乎有点hacker,但是唯一且可能更简洁的解决方案是将请求主体中的所有数据包装到一个键中,例如data
。因此,您的实际问题是:
如何在单个端点上管理JSON对象和JSON数组?
您可以使用强大的参数,例如..
def children_params model_attributes = [:child_name] if params.key? '_json' params.permit(_json: model_attributes)['_json'] else params.permit(*model_attributes) end end
..并且在create action中看起来像..
def create result = Children.create!(children_params) if children_params.kind_of? Array @children = result render :index, status: :created else @child = result render :show, status: :created end end
当然,您需要针对特定用例对其进行一些调整。从API的设计角度来看,我认为这样做是可以的。也许应该在一个单独的问题中提出。