Skip to content

Instantly share code, notes, and snippets.

@marcelorxaviers
Last active November 28, 2018 15:31
Show Gist options
  • Save marcelorxaviers/dfc24ddffe5d923f89f694c0b098c710 to your computer and use it in GitHub Desktop.
Save marcelorxaviers/dfc24ddffe5d923f89f694c0b098c710 to your computer and use it in GitHub Desktop.
class BatchCreate
class << self
def perform(active_records:, conflict_fields:)
return if active_records.empty?
ActiveRecord::Base.connection.execute(
<<-SQL.squish
#{insert_statement(active_records.first)}
#{values(active_records)}
#{on_conflict_statement(active_records.first, conflict_fields)}
SQL
)
end
private
INSERT_REGEX = /(INSERT INTO .* VALUES) (.*)/
def insert_statement(active_record)
sql = active_record.class.arel_table.create_insert.tap do |insert_manager|
insert_manager.insert(
active_record.send(:arel_attributes_with_values_for_create, active_record.attribute_names.sort)
)
end.to_sql
INSERT_REGEX.match(sql).captures[0]
end
def on_conflict_statement(active_record, conflict_fields)
return ';' if conflict_fields.blank?
columns = active_record.attributes.keys.reject { |key| key.to_sym == :id }
values = columns.map { |column| "EXCLUDED.#{column}" }.join(', ')
"ON CONFLICT (#{conflict_fields.join(', ')}) DO UPDATE SET (#{columns.join(', ')}) = (#{values})"
end
def values_for(active_record)
active_record.updated_at = Time.now if active_record.respond_to?(:updated_at)
active_record.created_at ||= Time.now if active_record.respond_to?(:created_at)
sql = active_record.class.arel_table.create_insert.tap do |insert_manager|
insert_manager.insert(
active_record.send(:arel_attributes_with_values_for_create, active_record.attribute_names.sort)
)
end.to_sql
INSERT_REGEX.match(sql).captures[1]
end
def values(active_records)
active_records.map { |active_record| values_for(active_record) }.join(",")
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment