Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Different timestamps behaviour with InsertAll #51820

Closed
srozen opened this issue May 14, 2024 · 3 comments
Closed

Different timestamps behaviour with InsertAll #51820

srozen opened this issue May 14, 2024 · 3 comments

Comments

@srozen
Copy link

srozen commented May 14, 2024

Steps to reproduce

Try to manipulate time around insert_all or upsert_all to observe varying timestamps.

# frozen_string_literal: true

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  gem "rails"
  # If you want to test against edge Rails replace the previous line with this:
  # gem "rails", github: "rails/rails", branch: "main"

  gem "sqlite3", "~> 1.4"
  gem "timecop"
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.timestamps
  end
end

class Post < ActiveRecord::Base
  has_many :comments
end

class BugTest < Minitest::Test
  # Fails
  def test_upsert_all_timestamp
    Post.upsert_all [{ id: 101}]

    Timecop.freeze(Date.today + 3.days) do
      Post.upsert_all [{ id: 102}]
      alice = Post.find(101)
      bob = Post.find(102)
      assert (bob.created_at.to_date - alice.created_at.to_date).to_i >= 2
    end
  end

  # Succeeds
  def test_standard_creation
    alice = Post.create!(id: 103)
    Timecop.freeze(Date.today + 3.days) do
      bob = Post.create!(id: 104)
      assert (bob.created_at.to_date - alice.created_at.to_date).to_i >= 2
    end
  end
end

Expected behavior

I would expect the timestamps to be set and follow the same rules as the standard creation/update.

Actual behavior

Timestamps within insert_all, upsert_all are set using a DB function to get the current time, thus not behaving the same way nor being impacted by utilities like Timecop or travel_to.

Potential fix

If this behaviour is not intended, I think relying on the model current_time_from_proper_timezone to build timestamps in ActiveRecord::InsertAll could be a solution.
Would be happy to contribute!

System configuration

Rails version: 7.1.3.2

Ruby version: 3.3

@srozen srozen changed the title Different Timestamp behaviour with InsertAll Different timestamps behaviour with InsertAll May 14, 2024
@Dmoment
Copy link
Contributor

Dmoment commented May 19, 2024

I am not sure, but I think this is somewhat related to this issue #44933

@justinko
Copy link
Contributor

Well, you could do Post.upsert_all [{ id: 102, created_at: Time.current}].

For multiple reasons, it's so great that we have methods (e.g. upsert_all) that totally skip record instantiation and go straight to the DB and use native functions (NOW in this case).

If your logic depends on time, then I would use create (e.g. User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])) instead of insert_all.

@srozen
Copy link
Author

srozen commented May 21, 2024

Thanks for the explanation @justinko, makes sense indeed.

@srozen srozen closed this as completed May 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants