How it works:
The persistence module define create
: https://github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/persistence.rb#L46
def create!(attributes = nil, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| create!(attr, &block) }
else
object = new(attributes, &block)
object.save!
object
end
end
It create an object and call #save!
It is not documented in the public api, but calls https://github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/transactions.rb#L290
def save!(*) #:nodoc:
with_transaction_returning_status { super }
end
At this point the transaction wrap the save (super), which is at Persistence module again: https://github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/persistence.rb#L141
def save!(*)
create_or_update || raise(RecordNotSaved.new(nil, self))
end
Let's hack this with some new methods:
module ActiveRecord
module Persistence
module ClassMethods
def atomic_create!(attributes = nil, &block)
if attributes.is_a?(Array)
raise "An array of records can't be atomic"
else
object = new(attributes, &block)
object.atomic_save!
object
end
end
end
alias_method :atomic_save!, :save!
end
end
module ActiveRecord
module Transactions
def atomic_save!(*)
super
end
end
end
Perhaps you want to use the standard create!
method, then you need to redefine it. I define a first optional parameter :atomic
, and when it's present means you want to use the atomic_save!
method.
module ActiveRecord
module Persistence
module ClassMethods
def create_with_atomic!(first = nil, second = nil, &block)
attributes, atomic = second == nil ? [first, second] : [second, first]
if attributes.is_a?(Array)
create_without_atomic!(attributes, &block)
else
object = new(attributes, &block)
atomic == :atomic ? object.atomic_save! : object.save!
object
end
end
alias_method_chain :create!, :atomic
end
end
end
With this in config/initializers/<any_name>.rb
it can work.
How it runs at console:
~/rails/r41example (development) > Product.atomic_create!(name: 'atomic_create')
SQL (99.4ms) INSERT INTO "products" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", "2015-03-22 03:50:07.558473"], ["name", "atomic_create"], ["updated_at", "2015-03-22 03:50:07.558473"]]
=> #<Product:0x000000083b1340> {
:id => 1,
:name => "atomic_create",
:created_at => Sun, 22 Mar 2015 03:50:07 UTC +00:00,
:updated_at => Sun, 22 Mar 2015 03:50:07 UTC +00:00
}
~/rails/r41example (development) > Product.create!(name: 'create with commit')
(0.1ms) begin transaction
SQL (0.1ms) INSERT INTO "products" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", "2015-03-22 03:50:20.790566"], ["name", "create with commit"], ["updated_at", "2015-03-22 03:50:20.790566"]]
(109.3ms) commit transaction
=> #<Product:0x000000082f3138> {
:id => 2,
:name => "create with commit",
:created_at => Sun, 22 Mar 2015 03:50:20 UTC +00:00,
:updated_at => Sun, 22 Mar 2015 03:50:20 UTC +00:00
}
~/rails/r41example (development) > Product.create!(:atomic, name: 'create! atomic')
SQL (137.3ms) INSERT INTO "products" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", "2015-03-22 03:51:03.001423"], ["name", "create! atomic"], ["updated_at", "2015-03-22 03:51:03.001423"]]
=> #<Product:0x000000082a0bb8> {
:id => 3,
:name => "create! atomic",
:created_at => Sun, 22 Mar 2015 03:51:03 UTC +00:00,
:updated_at => Sun, 22 Mar 2015 03:51:03 UTC +00:00
}
Caveat: You will lose after_rollback and after_commit callbacks!
Note: on 4.1 the methods create! and save! are in module Validations. On Rails 4.2 are in Persistence.
Edit: Perhaps you think you can earn the transaction elapsed time. In my examples the commit time goes to the inserts (I have a standard HD and I think you have an SSD).