mirror of
https://github.com/aasm/aasm
synced 2023-03-27 23:22:41 -04:00
Add Sequel transactions and locking
* Add aasm features of ActiveRecord to Sequel! :) * All specs pass for sequel, activerecord, and mongoid. * Refactor Sequel models to be namespaced under “Sequel”. We should do something similar for ActiveRecord in the specs to avoid collisions. Addresses #474 (cherry picked from commit df97ce754f47864abbcb837227da5c4d0ff77289)
This commit is contained in:
parent
2c49078c70
commit
0c4a25a8d4
11 changed files with 663 additions and 96 deletions
|
@ -1,8 +1,10 @@
|
|||
require 'aasm/persistence/orm'
|
||||
module AASM
|
||||
module Persistence
|
||||
module SequelPersistence
|
||||
def self.included(base)
|
||||
base.send(:include, AASM::Persistence::Base)
|
||||
base.send(:include, AASM::Persistence::ORM)
|
||||
base.send(:include, AASM::Persistence::SequelPersistence::InstanceMethods)
|
||||
end
|
||||
|
||||
|
@ -17,42 +19,42 @@ module AASM
|
|||
super
|
||||
end
|
||||
|
||||
# Returns the value of the aasm.attribute_name - called from <tt>aasm.current_state</tt>
|
||||
#
|
||||
# If it's a new record, and the aasm state column is blank it returns the initial state
|
||||
#
|
||||
# class Foo < Sequel::Model
|
||||
# include AASM
|
||||
# aasm :column => :status do
|
||||
# state :opened
|
||||
# state :closed
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# foo = Foo.new
|
||||
# foo.current_state # => :opened
|
||||
# foo.close
|
||||
# foo.current_state # => :closed
|
||||
#
|
||||
# foo = Foo[1]
|
||||
# foo.current_state # => :opened
|
||||
# foo.aasm_state = nil
|
||||
# foo.current_state # => nil
|
||||
#
|
||||
# NOTE: intended to be called from an event
|
||||
#
|
||||
# This allows for nil aasm states - be sure to add validation to your model
|
||||
def aasm_read_state(name=:default)
|
||||
state = send(self.class.aasm(name).attribute_name)
|
||||
if new? && state.to_s.strip.empty?
|
||||
aasm(name).determine_state_name(self.class.aasm(name).initial_state)
|
||||
elsif state.nil?
|
||||
nil
|
||||
else
|
||||
state.to_sym
|
||||
def aasm_raise_invalid_record
|
||||
raise Sequel::ValidationFailed.new(self)
|
||||
end
|
||||
|
||||
def aasm_new_record?
|
||||
new?
|
||||
end
|
||||
|
||||
# Returns nil if fails silently
|
||||
# http://sequel.jeremyevans.net/rdoc/classes/Sequel/Model/InstanceMethods.html#method-i-save
|
||||
def aasm_save
|
||||
!save(raise_on_failure: false).nil?
|
||||
end
|
||||
|
||||
def aasm_read_attribute(name)
|
||||
send(name)
|
||||
end
|
||||
|
||||
def aasm_write_attribute(name, value)
|
||||
send("#{name}=", value)
|
||||
end
|
||||
|
||||
def aasm_transaction(requires_new, requires_lock)
|
||||
self.class.db.transaction(savepoint: requires_new) do
|
||||
if requires_lock
|
||||
# http://sequel.jeremyevans.net/rdoc/classes/Sequel/Model/InstanceMethods.html#method-i-lock-21
|
||||
requires_lock.is_a?(String) ? lock!(requires_lock) : lock!
|
||||
end
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def aasm_update_column(attribute_name, value)
|
||||
this.update(attribute_name => value)
|
||||
end
|
||||
|
||||
# Ensures that if the aasm_state column is nil and the record is new
|
||||
# that the initial state gets populated before validation on create
|
||||
#
|
||||
|
@ -72,39 +74,10 @@ module AASM
|
|||
AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |state_machine_name|
|
||||
aasm(state_machine_name).enter_initial_state if
|
||||
(new? || values.key?(self.class.aasm(state_machine_name).attribute_name)) &&
|
||||
send(self.class.aasm(state_machine_name).attribute_name).to_s.strip.empty?
|
||||
send(self.class.aasm(state_machine_name).attribute_name).to_s.strip.empty?
|
||||
end
|
||||
end
|
||||
|
||||
# Writes <tt>state</tt> to the state column and persists it to the database
|
||||
#
|
||||
# foo = Foo[1]
|
||||
# foo.aasm.current_state # => :opened
|
||||
# foo.close!
|
||||
# foo.aasm.current_state # => :closed
|
||||
# Foo[1].aasm.current_state # => :closed
|
||||
#
|
||||
# NOTE: intended to be called from an event
|
||||
def aasm_write_state state, name=:default
|
||||
aasm_column = self.class.aasm(name).attribute_name
|
||||
update_fields({aasm_column => state.to_s}, [aasm_column], missing: :skip)
|
||||
end
|
||||
|
||||
# Writes <tt>state</tt> to the state column, but does not persist it to the database
|
||||
#
|
||||
# foo = Foo[1]
|
||||
# foo.aasm.current_state # => :opened
|
||||
# foo.close
|
||||
# foo.aasm.current_state # => :closed
|
||||
# Foo[1].aasm.current_state # => :opened
|
||||
# foo.save
|
||||
# foo.aasm.current_state # => :closed
|
||||
# Foo[1].aasm.current_state # => :closed
|
||||
#
|
||||
# NOTE: intended to be called from an event
|
||||
def aasm_write_state_without_persistence state, name=:default
|
||||
send("#{self.class.aasm(name).attribute_name}=", state.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
db = Sequel.connect(SEQUEL_DB)
|
||||
db = Sequel::DATABASES.first || Sequel.connect(SEQUEL_DB)
|
||||
|
||||
# if you want to see the statements while running the spec enable the following line
|
||||
# db.loggers << Logger.new($stderr)
|
||||
|
@ -8,8 +8,8 @@ db.create_table(:complex_sequel_examples) do
|
|||
String :right
|
||||
end
|
||||
|
||||
class ComplexSequelExample < Sequel::Model(db)
|
||||
set_dataset(:complex_sequel_examples)
|
||||
module Sequel
|
||||
class ComplexExample < Sequel::Model(:complex_sequel_examples)
|
||||
|
||||
include AASM
|
||||
|
||||
|
@ -43,3 +43,4 @@ class ComplexSequelExample < Sequel::Model(db)
|
|||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
52
spec/models/sequel/invalid_persistor.rb
Normal file
52
spec/models/sequel/invalid_persistor.rb
Normal file
|
@ -0,0 +1,52 @@
|
|||
db = Sequel::DATABASES.first || Sequel.connect(SEQUEL_DB)
|
||||
|
||||
[:invalid_persistors, :multiple_invalid_persistors].each do |table_name|
|
||||
db.create_table(table_name) do
|
||||
primary_key :id
|
||||
String "name"
|
||||
String "status"
|
||||
end
|
||||
end
|
||||
|
||||
module Sequel
|
||||
class InvalidPersistor < Sequel::Model(:invalid_persistors)
|
||||
plugin :validation_helpers
|
||||
|
||||
include AASM
|
||||
aasm :column => :status, :skip_validation_on_save => true do
|
||||
state :sleeping, :initial => true
|
||||
state :running
|
||||
event :run do
|
||||
transitions :to => :running, :from => :sleeping
|
||||
end
|
||||
event :sleep do
|
||||
transitions :to => :sleeping, :from => :running
|
||||
end
|
||||
end
|
||||
|
||||
def validate
|
||||
super
|
||||
validates_presence :name
|
||||
end
|
||||
end
|
||||
|
||||
class MultipleInvalidPersistor < Sequel::Model(:multiple_invalid_persistors)
|
||||
plugin :validation_helpers
|
||||
|
||||
include AASM
|
||||
aasm :left, :column => :status, :skip_validation_on_save => true do
|
||||
state :sleeping, :initial => true
|
||||
state :running
|
||||
event :run do
|
||||
transitions :to => :running, :from => :sleeping
|
||||
end
|
||||
event :sleep do
|
||||
transitions :to => :sleeping, :from => :running
|
||||
end
|
||||
end
|
||||
def validate
|
||||
super
|
||||
validates_presence :name
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
db = Sequel.connect(SEQUEL_DB)
|
||||
db = Sequel::DATABASES.first || Sequel.connect(SEQUEL_DB)
|
||||
|
||||
# if you want to see the statements while running the spec enable the following line
|
||||
# db.loggers << Logger.new($stderr)
|
||||
|
@ -6,20 +6,20 @@ db.create_table(:multiples) do
|
|||
primary_key :id
|
||||
String :status
|
||||
end
|
||||
module Sequel
|
||||
class Multiple < Sequel::Model(:multiples)
|
||||
include AASM
|
||||
|
||||
class SequelMultiple < Sequel::Model(db)
|
||||
set_dataset(:multiples)
|
||||
include AASM
|
||||
attr_accessor :default
|
||||
|
||||
attr_accessor :default
|
||||
|
||||
aasm :left, :column => :status
|
||||
aasm :left do
|
||||
state :alpha, :initial => true
|
||||
state :beta
|
||||
state :gamma
|
||||
event :release do
|
||||
transitions :from => [:alpha, :beta, :gamma], :to => :beta
|
||||
aasm :left, :column => :status
|
||||
aasm :left do
|
||||
state :alpha, :initial => true
|
||||
state :beta
|
||||
state :gamma
|
||||
event :release do
|
||||
transitions :from => [:alpha, :beta, :gamma], :to => :beta
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
db = Sequel.connect(SEQUEL_DB)
|
||||
db = Sequel::DATABASES.first || Sequel.connect(SEQUEL_DB)
|
||||
|
||||
# if you want to see the statements while running the spec enable the following line
|
||||
# db.loggers << Logger.new($stderr)
|
||||
|
@ -7,19 +7,20 @@ db.create_table(:simples) do
|
|||
String :status
|
||||
end
|
||||
|
||||
class SequelSimple < Sequel::Model(db)
|
||||
set_dataset(:simples)
|
||||
include AASM
|
||||
module Sequel
|
||||
class Simple < Sequel::Model(:simples)
|
||||
include AASM
|
||||
|
||||
attr_accessor :default
|
||||
attr_accessor :default
|
||||
|
||||
aasm :column => :status
|
||||
aasm do
|
||||
state :alpha, :initial => true
|
||||
state :beta
|
||||
state :gamma
|
||||
event :release do
|
||||
transitions :from => [:alpha, :beta, :gamma], :to => :beta
|
||||
aasm :column => :status
|
||||
aasm do
|
||||
state :alpha, :initial => true
|
||||
state :beta
|
||||
state :gamma
|
||||
event :release do
|
||||
transitions :from => [:alpha, :beta, :gamma], :to => :beta
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
50
spec/models/sequel/silent_persistor.rb
Normal file
50
spec/models/sequel/silent_persistor.rb
Normal file
|
@ -0,0 +1,50 @@
|
|||
db = Sequel::DATABASES.first || Sequel.connect(SEQUEL_DB)
|
||||
|
||||
[:silent_persistors, :multiple_silent_persistors].each do |t|
|
||||
db.create_table(t) do
|
||||
primary_key :id
|
||||
String "name"
|
||||
String "status"
|
||||
end
|
||||
end
|
||||
|
||||
module Sequel
|
||||
class SilentPersistor < Sequel::Model(:silent_persistors)
|
||||
plugin :validation_helpers
|
||||
|
||||
include AASM
|
||||
aasm :column => :status, :whiny_persistence => false do
|
||||
state :sleeping, :initial => true
|
||||
state :running
|
||||
event :run do
|
||||
transitions :to => :running, :from => :sleeping
|
||||
end
|
||||
event :sleep do
|
||||
transitions :to => :sleeping, :from => :running
|
||||
end
|
||||
end
|
||||
def validate
|
||||
validates_presence :name
|
||||
end
|
||||
end
|
||||
|
||||
class MultipleSilentPersistor< Sequel::Model(:multiple_silent_persistors)
|
||||
plugin :validation_helpers
|
||||
|
||||
include AASM
|
||||
aasm :left, :column => :status, :whiny_persistence => false do
|
||||
state :sleeping, :initial => true
|
||||
state :running
|
||||
event :run do
|
||||
transitions :to => :running, :from => :sleeping
|
||||
end
|
||||
event :sleep do
|
||||
transitions :to => :sleeping, :from => :running
|
||||
end
|
||||
end
|
||||
|
||||
def validate
|
||||
validates_presence :name
|
||||
end
|
||||
end
|
||||
end
|
112
spec/models/sequel/transactor.rb
Normal file
112
spec/models/sequel/transactor.rb
Normal file
|
@ -0,0 +1,112 @@
|
|||
db = Sequel::DATABASES.first || Sequel.connect(SEQUEL_DB)
|
||||
|
||||
[:transactors, :no_lock_transactors, :lock_transactors, :lock_no_wait_transactors, :multiple_transactors].each do |table_name|
|
||||
db.create_table(table_name) do
|
||||
primary_key :id
|
||||
String "name"
|
||||
String "status"
|
||||
Fixnum "worker_id"
|
||||
end
|
||||
end
|
||||
|
||||
module Sequel
|
||||
class Transactor < Sequel::Model(:transactors)
|
||||
|
||||
many_to_one :worker, class: 'Sequel::Worker'
|
||||
|
||||
include AASM
|
||||
aasm :column => :status do
|
||||
state :sleeping, :initial => true
|
||||
state :running, :before_enter => :start_worker, :after_enter => :fail
|
||||
|
||||
event :run do
|
||||
transitions :to => :running, :from => :sleeping
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def start_worker
|
||||
worker.update(status: 'running')
|
||||
end
|
||||
|
||||
def fail
|
||||
raise StandardError.new('failed on purpose')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class NoLockTransactor < Sequel::Model(:no_lock_transactors)
|
||||
|
||||
many_to_one :worker, class: 'Sequel::Worker'
|
||||
|
||||
include AASM
|
||||
|
||||
aasm :column => :status do
|
||||
state :sleeping, :initial => true
|
||||
state :running
|
||||
|
||||
event :run do
|
||||
transitions :to => :running, :from => :sleeping
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class LockTransactor < Sequel::Model(:lock_transactors)
|
||||
|
||||
many_to_one :worker, class: 'Sequel::Worker'
|
||||
|
||||
include AASM
|
||||
|
||||
aasm :column => :status, requires_lock: true do
|
||||
state :sleeping, :initial => true
|
||||
state :running
|
||||
|
||||
event :run do
|
||||
transitions :to => :running, :from => :sleeping
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class LockNoWaitTransactor < Sequel::Model(:lock_no_wait_transactors)
|
||||
|
||||
many_to_one :worker, class: 'Sequel::Worker'
|
||||
|
||||
include AASM
|
||||
|
||||
aasm :column => :status, requires_lock: 'FOR UPDATE NOWAIT' do
|
||||
state :sleeping, :initial => true
|
||||
state :running
|
||||
|
||||
event :run do
|
||||
transitions :to => :running, :from => :sleeping
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class MultipleTransactor < Sequel::Model(:multiple_transactors)
|
||||
|
||||
many_to_one :worker, class: 'Sequel::Worker'
|
||||
|
||||
include AASM
|
||||
aasm :left, :column => :status do
|
||||
state :sleeping, :initial => true
|
||||
state :running, :before_enter => :start_worker, :after_enter => :fail
|
||||
|
||||
event :run do
|
||||
transitions :to => :running, :from => :sleeping
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def start_worker
|
||||
worker.update(:status, 'running')
|
||||
end
|
||||
|
||||
def fail
|
||||
raise StandardError.new('failed on purpose')
|
||||
end
|
||||
|
||||
end
|
||||
end
|
93
spec/models/sequel/validator.rb
Normal file
93
spec/models/sequel/validator.rb
Normal file
|
@ -0,0 +1,93 @@
|
|||
db = Sequel::DATABASES.first || Sequel.connect(SEQUEL_DB)
|
||||
|
||||
db.create_table(:validators) do
|
||||
primary_key :id
|
||||
String "name"
|
||||
String "status"
|
||||
Fixnum "worker_id"
|
||||
end
|
||||
|
||||
module Sequel
|
||||
class Validator < Sequel::Model(:validators)
|
||||
plugin :validation_helpers
|
||||
|
||||
attr_accessor :after_all_transactions_performed,
|
||||
:after_transaction_performed_on_fail,
|
||||
:after_transaction_performed_on_run,
|
||||
:before_all_transactions_performed,
|
||||
:before_transaction_performed_on_fail,
|
||||
:before_transaction_performed_on_run,
|
||||
:invalid
|
||||
|
||||
def validate
|
||||
super
|
||||
errors.add(:validator, "invalid") if invalid
|
||||
validates_presence :name
|
||||
end
|
||||
|
||||
include AASM
|
||||
|
||||
aasm :column => :status, :whiny_persistence => true do
|
||||
before_all_transactions :before_all_transactions
|
||||
after_all_transactions :after_all_transactions
|
||||
|
||||
state :sleeping, :initial => true
|
||||
state :running
|
||||
state :failed, :after_enter => :fail
|
||||
|
||||
event :run, :after_commit => :change_name! do
|
||||
after_transaction do
|
||||
@after_transaction_performed_on_run = true
|
||||
end
|
||||
|
||||
before_transaction do
|
||||
@before_transaction_performed_on_run = true
|
||||
end
|
||||
|
||||
transitions :to => :running, :from => :sleeping
|
||||
end
|
||||
|
||||
event :sleep do
|
||||
after_commit do |name|
|
||||
change_name_on_sleep name
|
||||
end
|
||||
transitions :to => :sleeping, :from => :running
|
||||
end
|
||||
|
||||
event :fail do
|
||||
after_transaction do
|
||||
@after_transaction_performed_on_fail = true
|
||||
end
|
||||
|
||||
before_transaction do
|
||||
@before_transaction_performed_on_fail = true
|
||||
end
|
||||
|
||||
transitions :to => :failed, :from => [:sleeping, :running]
|
||||
end
|
||||
end
|
||||
|
||||
def change_name!
|
||||
self.name = "name changed"
|
||||
save(raise_on_failure: true)
|
||||
end
|
||||
|
||||
def change_name_on_sleep name
|
||||
self.name = name
|
||||
save(raise_on_failure: true)
|
||||
end
|
||||
|
||||
def fail
|
||||
raise StandardError.new('failed on purpose')
|
||||
end
|
||||
|
||||
def after_all_transactions
|
||||
@after_all_transactions_performed = true
|
||||
end
|
||||
|
||||
def before_all_transactions
|
||||
@before_all_transactions_performed = true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
12
spec/models/sequel/worker.rb
Normal file
12
spec/models/sequel/worker.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
db = Sequel::DATABASES.first || Sequel.connect(SEQUEL_DB)
|
||||
|
||||
db.create_table(:workers) do
|
||||
primary_key :id
|
||||
String "name"
|
||||
String "status"
|
||||
end
|
||||
|
||||
module Sequel
|
||||
class Worker < Sequel::Model(:workers)
|
||||
end
|
||||
end
|
|
@ -8,7 +8,7 @@ if defined?(Sequel)
|
|||
end
|
||||
|
||||
before(:all) do
|
||||
@model = SequelMultiple
|
||||
@model = Sequel::Multiple
|
||||
end
|
||||
|
||||
describe "instance methods" do
|
||||
|
@ -93,7 +93,7 @@ if defined?(Sequel)
|
|||
|
||||
describe "complex example" do
|
||||
it "works" do
|
||||
record = ComplexSequelExample.new
|
||||
record = Sequel::ComplexExample.new
|
||||
expect(record.aasm(:left).current_state).to eql :one
|
||||
expect(record.left).to be_nil
|
||||
expect(record.aasm(:right).current_state).to eql :alpha
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
require 'spec_helper'
|
||||
|
||||
if defined?(Sequel)
|
||||
describe 'sequel' do
|
||||
|
||||
|
||||
Dir[File.dirname(__FILE__) + "/../../models/sequel/*.rb"].sort.each do |f|
|
||||
require File.expand_path(f)
|
||||
end
|
||||
|
||||
before(:all) do
|
||||
@model = SequelSimple
|
||||
@model = Sequel::Simple
|
||||
end
|
||||
|
||||
describe "instance methods" do
|
||||
|
@ -91,5 +90,279 @@ if defined?(Sequel)
|
|||
end
|
||||
end
|
||||
|
||||
describe 'transitions with persistence' do
|
||||
|
||||
it "should work for valid models" do
|
||||
valid_object = Sequel::Validator.create(:name => 'name')
|
||||
expect(valid_object).to be_sleeping
|
||||
valid_object.status = :running
|
||||
expect(valid_object).to be_running
|
||||
end
|
||||
|
||||
it 'should not store states for invalid models' do
|
||||
validator = Sequel::Validator.create(:name => 'name')
|
||||
expect(validator).to be_valid
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.name = nil
|
||||
expect(validator).not_to be_valid
|
||||
expect { validator.run! }.to raise_error(Sequel::ValidationFailed)
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.reload
|
||||
expect(validator).not_to be_running
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.name = 'another name'
|
||||
expect(validator).to be_valid
|
||||
expect(validator.run!).to be_truthy
|
||||
expect(validator).to be_running
|
||||
|
||||
validator.reload
|
||||
expect(validator).to be_running
|
||||
expect(validator).not_to be_sleeping
|
||||
end
|
||||
|
||||
it 'should not store states for invalid models silently if configured' do
|
||||
validator = Sequel::SilentPersistor.create(:name => 'name')
|
||||
expect(validator).to be_valid
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.name = nil
|
||||
expect(validator).not_to be_valid
|
||||
expect(validator.run!).to be_falsey
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.reload
|
||||
expect(validator).not_to be_running
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.name = 'another name'
|
||||
expect(validator).to be_valid
|
||||
expect(validator.run!).to be_truthy
|
||||
expect(validator).to be_running
|
||||
|
||||
validator.reload
|
||||
expect(validator).to be_running
|
||||
expect(validator).not_to be_sleeping
|
||||
end
|
||||
|
||||
it 'should store states for invalid models if configured' do
|
||||
persistor = Sequel::InvalidPersistor.create(:name => 'name')
|
||||
expect(persistor).to be_valid
|
||||
expect(persistor).to be_sleeping
|
||||
|
||||
persistor.name = nil
|
||||
expect(persistor).not_to be_valid
|
||||
expect(persistor.run!).to be_truthy
|
||||
expect(persistor).to be_running
|
||||
|
||||
persistor = Sequel::InvalidPersistor[persistor.id]
|
||||
persistor.valid?
|
||||
expect(persistor).to be_valid
|
||||
expect(persistor).to be_running
|
||||
expect(persistor).not_to be_sleeping
|
||||
|
||||
persistor.reload
|
||||
expect(persistor).to be_running
|
||||
expect(persistor).not_to be_sleeping
|
||||
end
|
||||
|
||||
describe 'pessimistic locking' do
|
||||
let(:worker) { Sequel::Worker.create(:name => 'worker', :status => 'sleeping') }
|
||||
|
||||
subject { transactor.run! }
|
||||
|
||||
context 'no lock' do
|
||||
let(:transactor) { Sequel::NoLockTransactor.create(:name => 'no_lock_transactor', :worker => worker) }
|
||||
|
||||
it 'should not invoke lock!' do
|
||||
expect(transactor).to_not receive(:lock!)
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'a default lock' do
|
||||
let(:transactor) { Sequel::LockTransactor.create(:name => 'lock_transactor', :worker => worker) }
|
||||
|
||||
it 'should invoke lock!' do
|
||||
expect(transactor).to receive(:lock!).and_call_original
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'a FOR UPDATE NOWAIT lock' do
|
||||
let(:transactor) { Sequel::LockNoWaitTransactor.create(:name => 'lock_no_wait_transactor', :worker => worker) }
|
||||
|
||||
it 'should invoke lock! with FOR UPDATE NOWAIT' do
|
||||
# TODO: With and_call_original, get an error with syntax, should look into it.
|
||||
expect(transactor).to receive(:lock!).with('FOR UPDATE NOWAIT')# .and_call_original
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'transactions' do
|
||||
let(:worker) { Sequel::Worker.create(:name => 'worker', :status => 'sleeping') }
|
||||
let(:transactor) { Sequel::Transactor.create(:name => 'transactor', :worker => worker) }
|
||||
|
||||
it 'should rollback all changes' do
|
||||
expect(transactor).to be_sleeping
|
||||
expect(worker.status).to eq('sleeping')
|
||||
|
||||
expect {transactor.run!}.to raise_error(StandardError, 'failed on purpose')
|
||||
expect(transactor).to be_running
|
||||
expect(worker.reload.status).to eq('sleeping')
|
||||
end
|
||||
|
||||
context "nested transactions" do
|
||||
it "should rollback all changes in nested transaction" do
|
||||
expect(transactor).to be_sleeping
|
||||
expect(worker.status).to eq('sleeping')
|
||||
|
||||
Sequel::Worker.db.transaction do
|
||||
expect { transactor.run! }.to raise_error(StandardError, 'failed on purpose')
|
||||
end
|
||||
|
||||
expect(transactor).to be_running
|
||||
expect(worker.reload.status).to eq('sleeping')
|
||||
end
|
||||
|
||||
it "should only rollback changes in the main transaction not the nested one" do
|
||||
# change configuration to not require new transaction
|
||||
AASM::StateMachineStore[Sequel::Transactor][:default].config.requires_new_transaction = false
|
||||
|
||||
expect(transactor).to be_sleeping
|
||||
expect(worker.status).to eq('sleeping')
|
||||
Sequel::Worker.db.transaction do
|
||||
expect { transactor.run! }.to raise_error(StandardError, 'failed on purpose')
|
||||
end
|
||||
expect(transactor).to be_running
|
||||
expect(worker.reload.status).to eq('running')
|
||||
end
|
||||
end
|
||||
|
||||
describe "after_commit callback" do
|
||||
it "should fire :after_commit if transaction was successful" do
|
||||
validator = Sequel::Validator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.run!
|
||||
expect(validator).to be_running
|
||||
expect(validator.name).to eq("name changed")
|
||||
|
||||
validator.sleep!("sleeper")
|
||||
expect(validator).to be_sleeping
|
||||
expect(validator.name).to eq("sleeper")
|
||||
end
|
||||
|
||||
it "should not fire :after_commit if transaction failed" do
|
||||
validator = Sequel::Validator.create(:name => 'name')
|
||||
expect { validator.fail! }.to raise_error(StandardError, 'failed on purpose')
|
||||
expect(validator.name).to eq("name")
|
||||
end
|
||||
|
||||
it "should not fire :after_commit if validation failed when saving object" do
|
||||
validator = Sequel::Validator.create(:name => 'name')
|
||||
validator.invalid = true
|
||||
expect { validator.run! }.to raise_error(Sequel::ValidationFailed, 'validator invalid')
|
||||
expect(validator).to be_sleeping
|
||||
expect(validator.name).to eq("name")
|
||||
end
|
||||
|
||||
it "should not fire if not saving" do
|
||||
validator = Sequel::Validator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
validator.run
|
||||
expect(validator).to be_running
|
||||
expect(validator.name).to eq("name")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'before and after transaction callbacks' do
|
||||
[:after, :before].each do |event_type|
|
||||
describe "#{event_type}_transaction callback" do
|
||||
it "should fire :#{event_type}_transaction if transaction was successful" do
|
||||
validator = Sequel::Validator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
expect { validator.run! }.to change { validator.send("#{event_type}_transaction_performed_on_run") }.from(nil).to(true)
|
||||
expect(validator).to be_running
|
||||
end
|
||||
|
||||
it "should fire :#{event_type}_transaction if transaction failed" do
|
||||
validator = Sequel::Validator.create(:name => 'name')
|
||||
expect do
|
||||
begin
|
||||
validator.fail!
|
||||
rescue => ignored
|
||||
end
|
||||
end.to change { validator.send("#{event_type}_transaction_performed_on_fail") }.from(nil).to(true)
|
||||
expect(validator).to_not be_running
|
||||
end
|
||||
|
||||
it "should not fire :#{event_type}_transaction if not saving" do
|
||||
validator = Sequel::Validator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
expect { validator.run }.to_not change { validator.send("#{event_type}_transaction_performed_on_run") }
|
||||
expect(validator).to be_running
|
||||
expect(validator.name).to eq("name")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'before and after all transactions callbacks' do
|
||||
[:after, :before].each do |event_type|
|
||||
describe "#{event_type}_all_transactions callback" do
|
||||
it "should fire :#{event_type}_all_transactions if transaction was successful" do
|
||||
validator = Sequel::Validator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
expect { validator.run! }.to change { validator.send("#{event_type}_all_transactions_performed") }.from(nil).to(true)
|
||||
expect(validator).to be_running
|
||||
end
|
||||
|
||||
it "should fire :#{event_type}_all_transactions if transaction failed" do
|
||||
validator = Sequel::Validator.create(:name => 'name')
|
||||
expect do
|
||||
begin
|
||||
validator.fail!
|
||||
rescue => ignored
|
||||
end
|
||||
end.to change { validator.send("#{event_type}_all_transactions_performed") }.from(nil).to(true)
|
||||
expect(validator).to_not be_running
|
||||
end
|
||||
|
||||
it "should not fire :#{event_type}_all_transactions if not saving" do
|
||||
validator = Sequel::Validator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
expect { validator.run }.to_not change { validator.send("#{event_type}_all_transactions_performed") }
|
||||
expect(validator).to be_running
|
||||
expect(validator.name).to eq("name")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when not persisting" do
|
||||
it 'should not rollback all changes' do
|
||||
expect(transactor).to be_sleeping
|
||||
expect(worker.status).to eq('sleeping')
|
||||
|
||||
# Notice here we're calling "run" and not "run!" with a bang.
|
||||
expect {transactor.run}.to raise_error(StandardError, 'failed on purpose')
|
||||
expect(transactor).to be_running
|
||||
expect(worker.reload.status).to eq('running')
|
||||
end
|
||||
|
||||
it 'should not create a database transaction' do
|
||||
expect(transactor.class).not_to receive(:transaction)
|
||||
expect {transactor.run}.to raise_error(StandardError, 'failed on purpose')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue