【问题标题】:Null value in column "created_at" violates not-null constraint when using upsert使用 upsert 时,“created_at”列中的空值违反非空约束
【发布时间】:2021-04-09 05:56:39
【问题描述】:

为什么created_at 为空,我将如何解决这个问题?

 ↳ app/controllers/projects_controller.rb:28:in `create'
  Tag Upsert (5.6ms)  INSERT INTO "tags" ("category","name") VALUES ('topic', 'career') ON CONFLICT ("id") DO UPDATE SET "type"=excluded."type","name"=excluded."name" RETURNING "id"
  ↳ app/controllers/projects_controller.rb:32:in `block in create'
Completed 500 Internal Server Error in 62ms (ActiveRecord: 31.0ms | Allocations: 22999)



ActiveRecord::NotNullViolation (PG::NotNullViolation: ERROR:  null value in column "created_at" violates not-null constraint
DETAIL:  Failing row contains (5, topic, career, null, null).
):

app/controllers/projects_controller.rb:32:in `block in create'
app/controllers/projects_controller.rb:31:in `each'
app/controllers/projects_controller.rb:31:in `create'
# projects_controller.rb
def create
    @project = Project.create(project_params)
    if @project.valid?
      # tags
      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
    end
  end

【问题讨论】:

    标签: ruby-on-rails upsert strong-parameters


    【解决方案1】:

    upsert 直接使用 SQL,几乎没有 ActiveRecord 参与:

    在单个 SQL INSERT 语句中更新或插入(更新插入)单个记录到数据库中。它不会实例化任何模型,也不会触发 Active Record 回调或验证。

    所以 AR 不会像往常那样触及 updated_atcreated_at

    最简单的做法是添加迁移以在数据库中为 created_atupdated_at 添加默认值:

    change_column_default :tags, :created_at, from: nil, to: ->{ 'now()' }
    change_column_default :tags, :updated_at, from: nil, to: ->{ 'now()' }
    

    或者您可以使用 current_timestamp 作为默认值(适用于 PostgreSQL 和 MySQL):

    change_column_default :tags, :created_at, from: nil, to: ->{ 'current_timestamp' }
    change_column_default :tags, :updated_at, from: nil, to: ->{ 'current_timestamp' }
    

    然后数据库将处理这些列。

    迁移中需要注意的两点:

    1. 传递 :from:to 选项而不是仅传递新的默认选项可为您提供可逆迁移。
    2. 您必须为 :to 值使用 lambda,以便使用 PostgreSQL now() 函数而不是字符串 'now()'。同样,如果您使用 current_timestamp 而不是 now()

    【讨论】:

    • 谢谢!我不知何故无法理解 upsert 的功能。它不应该更新以前设置为nil 的列吗?不知何故,我在迁移后遇到了一个新错误:NoMethodError (undefined method 'keys' for [:category, "topic"]:Array)(对于 upsert)
    • Upserts 是使用您在日志中看到的 SQL 在直接 SQL 中完成的,AR 做的很少,所以您必须安排一切,您所有的 created_atupdated_at 列都应该在数据库中具有默认值无论如何(海事组织)。您已将代码从 Tag.upsert 更改为 Tag.upsert_all,对吧?
    • 改回来了,没有任何帮助。我觉得这里太离谱了,甚至不知道从哪里开始寻找。我认为最好从这里开始打开一个新线程。再次感谢。
    • 不幸的是,这是一个特定于 DB 的解决方案,因此 MySQL 必须使用 something different,同样适用于 Sqlite 等。
    • @BryanH ->{ 'current_timestamp' } 作为默认值应该适用于 PostgreSQL 和 MySQL,但不确定 SQLite。
    猜你喜欢
    • 2018-08-14
    • 2021-11-02
    • 1970-01-01
    • 1970-01-01
    • 2016-02-14
    • 2017-02-18
    • 2020-08-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多