ruby-on-rails - Rails upsert 返回值无法创建实例 | ActiveRecord::AssociationType 不匹配
问题描述
尝试使用 fetch 和 Rails 将表单数据保存到 Postgre DB,我在下一个之后被抛出一个神秘的错误,我完全不知道如何解决这个问题。我要做的就是获取输入值并将它们保存到两个不同的表中。
服务器向我抛出以下内容:
Tag Upsert (4.7ms) INSERT INTO "tags" ("category","name") VALUES ('topic', 'abc') ON CONFLICT ("id") DO UPDATE SET "category"=excluded."category","name"=excluded."name" RETURNING "id"
↳ app/controllers/projects_controller.rb:36:in `block in create'
Completed 500 Internal Server Error in 180ms (ActiveRecord: 54.1ms | Allocations: 28146)
ActiveRecord::AssociationTypeMismatch (Tag(#70111841352580) expected, got #<ActiveRecord::Result:0x00007f8861ca8410 @columns=["id"], @rows=[[12]], @hash_rows=nil, @column_types={"id"=>#<ActiveModel::Type::Integer:0x00007f885e5608e0 @precision=nil, @scale=nil, @limit=8, @range=-9223372036854775808...9223372036854775808>}> which is an instance of ActiveRecord::Result(#70111841508420)):
app/controllers/projects_controller.rb:40:in `block in create'
app/controllers/projects_controller.rb:31:in `each'
app/controllers/projects_controller.rb:31:in `create'
相关线路涉及upsert
和create
:
# projects_controller.rb
...
def create
@project = Project.new(project_params)
if @project.save
params[:tags].each do |tag|
@tag = Tag.upsert({
category: 'topic',
name: tag
})
ProjectTag.create(tag: @tag, project: @project)
end
respond_to do |format|
format.json { render json: { "message": "success!", status: :ok } }
end
else
respond_to do |format|
format.json { render json: { "errors": "Missing entries." } }
end
end
end
...
这是表单提交时启动的获取请求:
fetch(`../projects/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Transaction': 'POST Example',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': document.querySelector("[name='csrf-token']").content, // $('meta[name="csrf-token"]').attr('content'),
},
body: JSON.stringify({project: project, tags: tags}),
credentials: 'include'
})
.then(response => {
if (!response.ok) {
throw response;
}
return response.json();
})
.then(data => {
if (data.errors) {
alert(`${data.errors}`);
} else {
console.log('Success:', data);
alert('Saved.');
}
})
.catch(error => {
console.error('Error:', error);
alert('error', data.errors);
});
标签模型如下所示:
# tag.rb
class Tag < ApplicationRecord
has_many :project_tags
has_many :issue_tags
validates :name, uniqueness: true
end
更新以响应最大值
Started POST "/projects/" for ::1 at 2021-01-04 00:42:41 +0100
Processing by ProjectsController#create as JSON
Parameters: {"project"=>{"name"=>"Project 7", "tags_attributes"=>[{"name"=>"career_planning", "category"=>"topic"}, {"name"=>"angular", "category"=>"topic"}], "language"=>"Angular", "slogan"=>"xyz", "target"=>nil, "pain"=>nil, "solution"=>nil, "originality"=>nil, "vision"=>nil, "db_design_url"=>nil, "repo_url"=>nil, "proto_url"=>nil}, "tags"=>["career_planning", "angular"]}
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.2ms) BEGIN
↳ app/controllers/projects_controller.rb:35:in `block (2 levels) in create'
Tag Exists? (0.4ms) SELECT 1 AS one FROM "tags" WHERE "tags"."name" = $1 LIMIT $2 [["name", "career_planning"], ["LIMIT", 1]]
↳ app/controllers/projects_controller.rb:35:in `block (2 levels) in create'
Tag Exists? (0.3ms) SELECT 1 AS one FROM "tags" WHERE "tags"."name" = $1 LIMIT $2 [["name", "angular"], ["LIMIT", 1]]
↳ app/controllers/projects_controller.rb:35:in `block (2 levels) in create'
Project Exists? (0.2ms) SELECT 1 AS one FROM "projects" WHERE "projects"."name" = $1 LIMIT $2 [["name", "Project 7"], ["LIMIT", 1]]
↳ app/controllers/projects_controller.rb:35:in `block (2 levels) in create'
(0.2ms) ROLLBACK
↳ app/controllers/projects_controller.rb:35:in `block (2 levels) in create'
Completed 200 OK in 16ms (Views: 0.2ms | ActiveRecord: 1.7ms | Allocations: 9221)
解决方案
在 Rails 中,如果你想在同一个请求中创建记录和嵌套记录,你可以使用嵌套属性。
class Project < ApplicationRecord
has_many :tags
accepts_nested_attributes_for :tags
end
这将允许您通过简单地传递一组属性来创建项目和标签:
Project.new(
name: 'Learn Nested Attributes',
tags_attributes: [
{ name: 'Ruby' },
{ name: 'Ruby On Rails' }
]
)
然后,当您插入父项时,它将有效地创建单个插入查询而不是 n+1 查询问题,并且它还允许您以理智的方式处理嵌套记录中的验证错误。您可以使用该reject_if:
选项或自定义设置器来处理现有记录。
您的控制器应该看起来像:
def create
@project = Project.new(project_params)
respond_to do |format|
format.json do
# Check if the record is actually persisted! Not just `.valid?`
if @project.save
# You should return meaningful response codes instead of
# just using the "json messages" anti-pattern
render json: { "message": "success!" }, status: :created }
else
format.json { render json: { "errors": "Missing entries." }, status: :unprocessable_entity }
end
end
end
end
private
def project_params
params.require(:project)
.permit(:foo, :bar, tags_attributes: [:name] )
end
在 Rails 中,控制器不是复杂的地方,因为它们很难测试。
然而,嵌套属性实际上并不是最好的 UX 解决方案。如果您改为创建单独的端点来创建标签并发送单独的 AJAX 请求,您可以在用户输入标签名称时提供直接用户反馈,或者自动完成现有标签,或者在标签无效时提供直接验证反馈。
resources :tags, only: [:index, :create]
class TagsController < ApplicationController
# GET /tags
# GET /tags?search=foobarbaz
def index
@tags = if params[:search].present?
Tag.where('tags.name LIKE ?', "%#{params[:search]}%")
else
Tag.all
end
end
render json: @tags
end
# POST /tags
def create
@tag = Tag.new(tag_params)
if @tag.save
render json: @tag,
status: :created
else
render json: { errors: @tag.errors.full_messages },
status: :unprocessable_entity
end
end
private
def tag_params
params.require(:tag)
.permit(:name)
end
end
然后,您可以编写一个 AJAX 处理程序*,将请求发送/tags?search=foobarbaz
到自动完成并通过向POST /tags
. 控制器将在响应正文中返回新创建的标签的 id,您可以使用表单中的表单元素,例如复选框或选择来存储标签 id 或一组隐藏的输入(或自定义元素/组件)。
分配现有记录的 rails 机制非常简单。只需将数组传递给_ids=
setter:
def project_params
params.require(:project)
.permit(:foo, :bar, tag_ids: [])
end
推荐阅读
- angular - 除了 Angular 9 中的 LocalStorage 之外,在哪里存储当前用户?
- reactjs - 在 React 中加载更多按钮
- python - 连接列表 PYTHON
- node.js - Excel 中 CSV 文件的特殊字符问题,Sheetjs 为 utf8 CSV 文件添加 BOM
- javascript - Google Apps Script PropertiesService - 因不可靠的执行记录和编辑器调试而感到困惑
- azure-logic-apps - 逻辑应用部署 - 找不到集成帐户:工作流必须与集成帐户关联才能使用工作流运行操作
- wagtail - 如何在 Wagtail 中检索属于具有收藏权限的组的用户的图像?
- python - 使用 django 显示 404 错误处理页面
- node.js - 如何在 React 中使用 nodejs 模块
- django - Elasticsearch + django:未知的 mimetype,无法反序列化:text/html