您可以通过使用活动记录内部创建 SQL 来实现。您可以使用 arel_attributes_with_values_for_create 提取具有 Rails 属性的插入 sql 的每个 active_record 的值,例如:
INSERT_REGEX = /(INSERT INTO .* VALUES) (.*)/
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
对于 SQL 的插入部分,您可以使用与用于值提取的代码类似的代码:
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(conflict_fields)
return ';' if conflict_fields.blank?
"ON CONFLICT (#{conflict_fields.join(', ')}) DO NOTHING"
end
最后只需将它们连接在一起执行:
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(conflict_fields)}
SQL
)
end
您可以查看here最终解决方案。
这是我的输出 - 使用我的 balance 活动记录模型并删除执行 SQL(ActiveRecord::Base.connection.execute...) 的代码:
> puts BatchCreate.perform(active_records: [balance], conflict_fields: [:bank_account_id, :date])
INSERT INTO "balances" ("amount", "created_at", "currency", "date", "id", "bank_account_id", "updated_at") VALUES (0, '2018-11-28 15:42:44.312954', 'MXN', '2018-11-28', 15169, 26300, '2018-11-28 16:05:07.465402') ON CONFLICT (bank_account_id, date) DO NOTHING