rails: insert_all throws `ActiveRecord::NotNullViolation` for timestamps
Steps to reproduce
# frozen_string_literal: true
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "rails", github: "rails/rails"
gem "sqlite3"
end
require "active_record"
require "minitest/autorun"
require "logger"
# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
create_table :posts, force: true do |t|
t.string :content
t.timestamps
end
end
class Post < ActiveRecord::Base
end
class BugTest < Minitest::Test
def test_insert_all
Post.insert_all([
{ content: "first" },
{ content: "second" }
])
end
end
Expected behavior
I think insert_all should auto set the timestamp value unless explicitly given.
Actual behavior
If a Model has t.timestamps
then it by default add NOT NULL
constraint on created_at
and updated_at
due to which insert_all fails unless created_at
and updated_at
values are given.
System configuration
Rails version: 6.0.0.beta1
Ruby version: v2.6.0-dev
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 25
- Comments: 19 (16 by maintainers)
For this use-case, I would suggest giving the timestamp columns a default value: (c.f. #20005)
I think
insert_all
shouldn’t autofill timestamps for a couple of reasons:update_all
, which does notinsert_all
overcreate
for the latest version of the rails, you can use the create_with option for default attributes.
Post.create_with(created_at: Time.now, updated_at: Time.now).insert_all([ { content: "first" }, { content: "second" } ])
refer to this: https://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-insert_all@rusikf no, that feature was rejected
Ahh, thanks for clarifying! …and congrats on 6.0
My thinking is that a great use case for
insert_all
is importing data from a CSV which might not have columns for created_at/updated_at, so I think it does make sense for Rails to have your back there as it does forcreate
/update
viaActiveRecord::Timestamp
.On the other hand, if people are using upserts we don’t know if a record exists in the db beforehand, so we don’t know if we should provide created_at too. But that’s perhaps a larger problem with
upsert_all
.Having a way to provide defaults ala
insert_all(data, defaults: { created_at: Time.now, updated_at: Time.now })
could also work.How does
insert_all
fare as a relation? For instance, doesauthor.posts.insert_all(data)
infer:author_id
?Hmm, tried the
where(created_at: Time.now)
but still had to supplycreated_at: Time.now
in the attributes array.No, I don’t think we should support automatic inference of timestamps and prefer
Post.where(created_at: Time.now).insert_all(posts)
as mentioned in the comment.That suggests the documentation needs to be updated. Please do investigate.
I have prepared solution for this based on https://github.com/rails/rails/pull/35631 and https://github.com/rails/rails/pull/35635 (merged).
With those code changes it should be easier, but still transparent how to handle
created_at
/updated_at
columns duringupsert
/upsert_all
.It looks similar to me to
update_all
where you need to specifyupdated_at
value manually as well if you need to update it:We can update documentation to make it clear and also include this in rails guides as well.
Thanks for your response @boblail . Adding timestamps feels necessary to me because
insert_all
overcreate
then we might need to write down migration just to adddefault: -> { 'CURRENT_TIMESTAMP' }
which does not feel right to me.'CURRENT_TIMESTAMP'
to timestamp. I did face a weird issue. Default was only added toupdated_at
and not tocreated_at
. Might be I am doing something wrong here 😄Here the timestamp not null constraint is already satisfied so even if it doesn’t update it won’t fail but in case of
insert_all
it would until set default. If setting timestamp is not option may good error message when NotNullViolation is thrown for timestamps.Maybe Rails team member could guide us here. cc/ @sikachu @kaspth