Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions spec/avram/insert_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ describe Avram::Insert do
describe "inserting" do
it "inserts with a hash of String" do
params = {:first_name => "Paul", :last_name => "Smith"}
insert = Avram::Insert.new(table: :users, params: params)
insert.statement.should eq "insert into users(first_name, last_name) values($1, $2) returning *"
insert = Avram::Insert.new(table: :users, params: [params])
insert.statement.should eq "insert into users(first_name, last_name) values ($1, $2) returning *"
insert.args.should eq ["Paul", "Smith"]
end

it "inserts with a hash of Nil" do
params = {:first_name => nil}
insert = Avram::Insert.new(table: :users, params: params)
insert.statement.should eq "insert into users(first_name) values($1) returning *"
insert = Avram::Insert.new(table: :users, params: [params])
insert.statement.should eq "insert into users(first_name) values ($1) returning *"
insert.args.should eq [nil]
end
end
Expand Down
75 changes: 75 additions & 0 deletions src/avram/bulk_insert.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
module Avram::BulkInsert(T)
macro included
define_import

macro inherited
define_import
end
end

macro define_import
def self.import(operations : Array(self))
operations.each(&.before_save)

if operations.all?(&.valid?)
now = Time.utc

insert_values = operations.map do |operation|
operation.created_at.value ||= now if operation.responds_to?(:created_at)
operation.updated_at.value ||= now if operation.responds_to?(:updated_at)
operation.values
end

insert_sql = Avram::Insert.new(T.table_name, insert_values, T.column_names)

transaction_committed = T.database.transaction do
T.database.query insert_sql.statement, args: insert_sql.args do |rs|
T.from_rs(rs).each_with_index do |record, index|
operation = operations[index]
operation.record = record
operation.after_save(record)
end
end

true
end

if transaction_committed
operations.each do |operation|
operation.save_status = OperationStatus::Saved
operation.after_commit(operation.record.as(T))

Avram::Events::SaveSuccessEvent.publish(
operation_class: self.class.name,
attributes: operation.generic_attributes
)
end

true
else
operations.each do |operation|
operation.mark_as_failed

Avram::Events::SaveFailedEvent.publish(
operation_class: self.class.name,
attributes: operation.generic_attributes
)
end

false
end
else
operations.each do |operation|
operation.mark_as_failed

Avram::Events::SaveFailedEvent.publish(
operation_class: self.class.name,
attributes: operation.generic_attributes
)
end

false
end
end
end
end
22 changes: 14 additions & 8 deletions src/avram/insert.cr
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
class Avram::Insert
alias Params = Hash(Symbol, String) | Hash(Symbol, String?) | Hash(Symbol, Nil)
alias Params = Array(Hash(Symbol, String)) | Array(Hash(Symbol, String?)) | Array(Hash(Symbol, Nil))

def initialize(@table : TableName, @params : Params, @column_names : Array(Symbol) = [] of Symbol)
end

def statement
"insert into #{@table}(#{fields}) values(#{values_placeholders}) returning #{returning}"
"insert into #{@table}(#{fields}) values #{values_sql_fragment} returning #{returning}"
end

private def returning : String
Expand All @@ -17,16 +17,22 @@ class Avram::Insert
end

def args
@params.values
@params.flat_map(&.values)
end

private def fields
@params.keys.join(", ")
@params.first.keys.join(", ")
end

private def values_placeholders
@params.values.map_with_index do |_value, index|
"$#{index + 1}"
end.join(", ")
private def values_sql_fragment
@params.map_with_index { |params, offset| values_placeholders(params, offset * params.size) }.join(", ")
end

private def values_placeholders(params, offset = 0)
String.build do |io|
io << "("
io << params.values.map_with_index { |_v, index| "$#{offset + index + 1}" }.join(", ")
io << ")"
end
end
end
8 changes: 6 additions & 2 deletions src/avram/save_operation.cr
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ abstract class Avram::SaveOperation(T)
include Avram::InheritColumnAttributes
include Avram::Upsert
include Avram::AddColumnAttributes
include Avram::BulkInsert(T)

enum OperationStatus
Saved
Expand Down Expand Up @@ -196,6 +197,10 @@ abstract class Avram::SaveOperation(T)
attributes_to_hash(column_attributes.select(&.changed?))
end

def values : Hash(Symbol, String?)
attributes_to_hash(column_attributes)
end

macro add_cast_value_methods(columns)
private def cast_value(value : Nil)
nil
Expand Down Expand Up @@ -307,8 +312,7 @@ abstract class Avram::SaveOperation(T)
end

private def insert_sql
insert_values = attributes_to_hash(column_attributes).compact
Avram::Insert.new(table_name, insert_values, T.column_names)
Avram::Insert.new(table_name, [values.compact], T.column_names)
end

private def attributes_to_hash(attributes) : Hash(Symbol, String?)
Expand Down