如何在 API-only Rails 中保存嵌套的多对多关系?
How to save a nested many-to-many relationship in API-only Rails?
在我的 Rails(仅限 api)学习项目中,我有 2 个模型,Group 和 Artist,它们与加入模型 Role 具有多对多关系,具有有关关系的其他信息。之前我已经能够通过自己保存连接模型来保存 m2m 关系,但在这里我试图将关系保存为嵌套关系。我正在使用 jsonapi-serializer gem,但没有与之结合,也没有绑定到 JSON api 规范。让它发挥作用比遵循最佳实践更重要。
使用此设置,我在尝试保存时遇到 500 错误并出现以下错误:
Unpermitted parameters: :artists, :albums
和 ActiveModel::UnknownAttributeError (unknown attribute 'relationships' for Group.)
我怀疑我的问题出在强参数 and/or 和 json 有效负载上。
型号
class Group < ApplicationRecord
has_many :roles
has_many :artists, through: :roles
accepts_nested_attributes_for :artists, :roles
end
class Artist < ApplicationRecord
has_many :groups, through: :roles
end
class Role < ApplicationRecord
belongs_to :artist
belongs_to :group
end
控制器#create
def create
group = Group.new(group_params)
if group.save
render json: GroupSerializer.new(group).serializable_hash
else
render json: { error: group.errors.messages }, status: 422
end
end
控制器#group_params
def group_params
params.require(:data)
.permit(attributes: [:name, :notes],
relationships: [:artists])
end
序列化程序
class GroupSerializer
include JSONAPI::Serializer
attributes :name, :notes
has_many :artists
has_many :roles
end
class ArtistSerializer
include JSONAPI::Serializer
attributes :first_name, :last_name, :notes
end
class RoleSerializer
include JSONAPI::Serializer
attributes :artist_id, :group_id, :instruments
end
示例JSON有效负载
{
"data": {
"attributes": {
"name": "Pink Floyd",
"notes": "",
},
"relationships": {
"artists": [{ type: "artist", "id": 3445 }, { type: "artist", "id": 3447 }]
}
}
附加信息
知道我能够使用以下 json 和强参数的组合保存另一个模型可能会有所帮助。
# Example JSON
"data": {
"attributes": {
"title": "Wish You Were Here",
"release_date": "1975-09-15",
"release_date_accuracy": 1
"notes": "",
"group_id": 3455
}
}
# in albums_controller.rb
def album_params
params.require(:data).require(:attributes)
.permit(:title, :group_id, :release_date, :release_date_accuracy, :notes)
end
从 https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html 来看,我认为 Rails 通常期望的数据格式类似于:
{
"group": {
"name": "Pink Floyd",
"notes": "",
"roles_attributes": [
{ "artist_id": 3445 },
{ "artist_id": 3447 }
]
}
}
许可声明类似于(注意许可移动前的 .):
params.require(:group).
permit(:name, :notes, roles_attributes: [:artist_id])
我想你在这里有几个选择:
- 更改进入操作的数据格式。
- 制作一个适用于您当前数据的许可声明(不确定这有多棘手),您可以在控制台中测试您的当前版本:
params = ActionController::Parameters.new({
"data": {
"attributes": {
"name": "Pink Floyd",
"notes": "",
},
"relationships": {
"artists": [{ type: "artist", "id": 3445 }, { type: "artist", "id": 3447 }]
}
}
})
group_params = params.require(:data).
permit(attributes: [:name, :notes],
relationships: [:artists])
group_params.to_h.inspect
然后将数据重组为模型可以接受的形式;或
- 在您尝试允许之前重组数据,例如类似于:
def group_params
params_hash = params.to_unsafe_h
new_params_hash = {
"group": params_hash["data"]["attributes"].merge({
"roles_attributes": params_hash["data"]["relationships"]["artists"].
map { |a| { "artist_id": a["id"] } }
})
}
new_params = ActionController::Parameters.new(new_params_hash)
new_params.require(:group).
permit(:name, :notes, roles_attributes: [:artist_id])
end
但是......我有点希望我完全错了,其他人会提出更好的解决方案。
在我的 Rails(仅限 api)学习项目中,我有 2 个模型,Group 和 Artist,它们与加入模型 Role 具有多对多关系,具有有关关系的其他信息。之前我已经能够通过自己保存连接模型来保存 m2m 关系,但在这里我试图将关系保存为嵌套关系。我正在使用 jsonapi-serializer gem,但没有与之结合,也没有绑定到 JSON api 规范。让它发挥作用比遵循最佳实践更重要。
使用此设置,我在尝试保存时遇到 500 错误并出现以下错误:
Unpermitted parameters: :artists, :albums
和 ActiveModel::UnknownAttributeError (unknown attribute 'relationships' for Group.)
我怀疑我的问题出在强参数 and/or 和 json 有效负载上。
型号
class Group < ApplicationRecord
has_many :roles
has_many :artists, through: :roles
accepts_nested_attributes_for :artists, :roles
end
class Artist < ApplicationRecord
has_many :groups, through: :roles
end
class Role < ApplicationRecord
belongs_to :artist
belongs_to :group
end
控制器#create
def create
group = Group.new(group_params)
if group.save
render json: GroupSerializer.new(group).serializable_hash
else
render json: { error: group.errors.messages }, status: 422
end
end
控制器#group_params
def group_params
params.require(:data)
.permit(attributes: [:name, :notes],
relationships: [:artists])
end
序列化程序
class GroupSerializer
include JSONAPI::Serializer
attributes :name, :notes
has_many :artists
has_many :roles
end
class ArtistSerializer
include JSONAPI::Serializer
attributes :first_name, :last_name, :notes
end
class RoleSerializer
include JSONAPI::Serializer
attributes :artist_id, :group_id, :instruments
end
示例JSON有效负载
{
"data": {
"attributes": {
"name": "Pink Floyd",
"notes": "",
},
"relationships": {
"artists": [{ type: "artist", "id": 3445 }, { type: "artist", "id": 3447 }]
}
}
附加信息
知道我能够使用以下 json 和强参数的组合保存另一个模型可能会有所帮助。
# Example JSON
"data": {
"attributes": {
"title": "Wish You Were Here",
"release_date": "1975-09-15",
"release_date_accuracy": 1
"notes": "",
"group_id": 3455
}
}
# in albums_controller.rb
def album_params
params.require(:data).require(:attributes)
.permit(:title, :group_id, :release_date, :release_date_accuracy, :notes)
end
从 https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html 来看,我认为 Rails 通常期望的数据格式类似于:
{
"group": {
"name": "Pink Floyd",
"notes": "",
"roles_attributes": [
{ "artist_id": 3445 },
{ "artist_id": 3447 }
]
}
}
许可声明类似于(注意许可移动前的 .):
params.require(:group).
permit(:name, :notes, roles_attributes: [:artist_id])
我想你在这里有几个选择:
- 更改进入操作的数据格式。
- 制作一个适用于您当前数据的许可声明(不确定这有多棘手),您可以在控制台中测试您的当前版本:
params = ActionController::Parameters.new({
"data": {
"attributes": {
"name": "Pink Floyd",
"notes": "",
},
"relationships": {
"artists": [{ type: "artist", "id": 3445 }, { type: "artist", "id": 3447 }]
}
}
})
group_params = params.require(:data).
permit(attributes: [:name, :notes],
relationships: [:artists])
group_params.to_h.inspect
然后将数据重组为模型可以接受的形式;或
- 在您尝试允许之前重组数据,例如类似于:
def group_params
params_hash = params.to_unsafe_h
new_params_hash = {
"group": params_hash["data"]["attributes"].merge({
"roles_attributes": params_hash["data"]["relationships"]["artists"].
map { |a| { "artist_id": a["id"] } }
})
}
new_params = ActionController::Parameters.new(new_params_hash)
new_params.require(:group).
permit(:name, :notes, roles_attributes: [:artist_id])
end
但是......我有点希望我完全错了,其他人会提出更好的解决方案。