Skip to content

Commit 0c8faae

Browse files
committed
feat: add automatic timestamp handling
1 parent af4da59 commit 0c8faae

3 files changed

Lines changed: 73 additions & 0 deletions

File tree

lib/staging_table/bulk_inserter.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ def insert(records)
1818
raise RecordError, "All records must be hashes. If passing ActiveRecord objects, use Session#insert which normalizes them automatically."
1919
end
2020

21+
records = apply_timestamps(records)
22+
2123
columns = records.first.keys.map(&:to_s)
2224
quoted_columns = columns.map { |c| connection.quote_column_name(c) }.join(", ")
2325
quoted_table = connection.quote_table_name(model.table_name)
@@ -34,6 +36,32 @@ def insert(records)
3436

3537
private
3638

39+
TIMESTAMP_COLUMNS = %w[created_at updated_at].freeze
40+
41+
def apply_timestamps(records)
42+
return records if records.empty?
43+
44+
sample_record = records.first
45+
model_columns = model.column_names
46+
47+
missing_timestamps = TIMESTAMP_COLUMNS.select do |col|
48+
model_columns.include?(col) && !record_has_key?(sample_record, col)
49+
end
50+
51+
return records if missing_timestamps.empty?
52+
53+
now = Time.current
54+
records.map do |record|
55+
record = record.dup
56+
missing_timestamps.each { |col| record[col.to_sym] = now }
57+
record
58+
end
59+
end
60+
61+
def record_has_key?(record, key)
62+
record.key?(key.to_sym) || record.key?(key.to_s)
63+
end
64+
3765
def connection
3866
model.connection
3967
end

sig/staging_table/bulk_inserter.rbs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ module StagingTable
1010

1111
private
1212

13+
TIMESTAMP_COLUMNS: Array[String]
14+
15+
def apply_timestamps: (Array[Hash[String | Symbol, untyped]] records) -> Array[Hash[String | Symbol, untyped]]
16+
def record_has_key?: (Hash[String | Symbol, untyped] record, String key) -> bool
1317
def connection: () -> ActiveRecord::ConnectionAdapters::AbstractAdapter
1418
def quote: (untyped value) -> String
1519
end

spec/staging_table/bulk_inserter_spec.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,47 @@
8585
user = staging_model.first
8686
expect(user.created_at).to be_within(1.second).of(now)
8787
end
88+
89+
it "automatically sets created_at and updated_at when not provided" do
90+
records = [
91+
{name: "John", email: "john@example.com"}
92+
]
93+
94+
freeze_time = Time.current
95+
allow(Time).to receive(:current).and_return(freeze_time)
96+
97+
inserter.insert(records)
98+
99+
user = staging_model.first
100+
expect(user.created_at).to be_within(1.second).of(freeze_time)
101+
expect(user.updated_at).to be_within(1.second).of(freeze_time)
102+
end
103+
104+
it "does not override provided timestamps" do
105+
custom_time = 1.day.ago
106+
records = [
107+
{name: "John", email: "john@example.com", created_at: custom_time, updated_at: custom_time}
108+
]
109+
110+
inserter.insert(records)
111+
112+
user = staging_model.first
113+
expect(user.created_at).to be_within(1.second).of(custom_time)
114+
expect(user.updated_at).to be_within(1.second).of(custom_time)
115+
end
116+
117+
it "handles string keys for timestamps" do
118+
custom_time = 2.days.ago
119+
records = [
120+
{"name" => "John", "email" => "john@example.com", "created_at" => custom_time, "updated_at" => custom_time}
121+
]
122+
123+
inserter.insert(records)
124+
125+
user = staging_model.first
126+
expect(user.created_at).to be_within(1.second).of(custom_time)
127+
expect(user.updated_at).to be_within(1.second).of(custom_time)
128+
end
88129
end
89130
end
90131

0 commit comments

Comments
 (0)