Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support duck typed commits and switch to Rspec #1

Merged
merged 7 commits into from
Jan 12, 2015
Merged
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
2 changes: 2 additions & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--require spec_helper
--color
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,16 @@ Or install it yourself as:
Create a Transaction which contains all of your commits. Note that the Transaction will run as soon as it is
initialized.

UpAndAtThem::Transaction.new([
UpAndAtThem::Commit.new { puts "do something!" }.on_rollback { "undo something!" },
UpAndAtThem::Commit.new { puts "do something else!" }.on_rollback { "undo something else!" },
])
UpAndAtThem::Transaction[
UpAndAtThem::Commit.new { puts "do something!" }.on_rollback { "undo something!" },
UpAndAtThem::Commit.new { puts "do something else!" }.on_rollback { "undo something else!" }
]

The `UpAndAtThem::Transaction` array can contain any classes that respond to `#call` and `#rollback`. The
`UpAndAtThem::Commit` class allows you to define those methods easily:

UpAndAtThem::Commit.new { "this block will execute on #call" }.on_rollback { "this block will execute on #rollback" }
UpAndAtThem::Commit.new { "calling on_rollback is not necessary" }

See the tests for other example Commit actions within a Transaction.

Expand Down
50 changes: 3 additions & 47 deletions lib/up_and_at_them.rb
Original file line number Diff line number Diff line change
@@ -1,47 +1,3 @@
require "up_and_at_them/version"

module UpAndAtThem
class Commit
def initialize(&block)
raise LocalJumpError unless block_given?
@block = block
end
def on_rollback(&block)
raise LocalJumpError unless block_given?
@rollback = block
self
end
def call
@block.call
end
def rollback
@rollback.call if @rollback
end
end

class Transaction
def initialize(tasks)
tasks = Array(tasks)
tasks.each do |t|
Commit === t or raise TypeError
end
@tasks = tasks
run
end

def run
finished_tasks = []
begin
@tasks.each do |task|
task.call
finished_tasks << task
end
rescue => err
finished_tasks.reverse_each do |task|
task.rollback
end
raise err
end
end
end
end
require 'up_and_at_them/version'
require 'up_and_at_them/commit'
require 'up_and_at_them/transaction'
24 changes: 24 additions & 0 deletions lib/up_and_at_them/commit.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module UpAndAtThem
class Commit

def initialize(&block)
raise LocalJumpError unless block_given?
@block = block
end

def on_rollback(&block)
raise LocalJumpError unless block_given?
@rollback = block
self
end

def call
@block.call
end

def rollback
@rollback.call if @rollback
end

end
end
25 changes: 25 additions & 0 deletions lib/up_and_at_them/transaction.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module UpAndAtThem
class Transaction

def self.[](*tasks)
new(tasks)
end

def initialize(tasks)
@tasks = Array(tasks)
run
end

def run
finished_tasks = []
@tasks.each do |task|
task.call
finished_tasks << task
end
rescue => err
finished_tasks.reverse_each(&:rollback)
raise err
end

end
end
9 changes: 9 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ENV["RAILS_ENV"] ||= 'test'

require 'rubygems'
require 'bundler/setup'
require 'up_and_at_them'

RSpec.configure do |config|
config.order = :random
end
80 changes: 80 additions & 0 deletions spec/transaction_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
describe UpAndAtThem::Transaction do

it 'rolls back past commits' do
flags = { 1 => :pending, 2 => :pending, 3 => :pending, 4 => :pending }
commits = (1...4).map do |i|
UpAndAtThem::Commit.new { flags[i] = :committed; if i==3 ; raise 'uh oh!'; end }.on_rollback { flags[i] = :rolled_back }
end
expect { UpAndAtThem::Transaction.new commits }.to raise_error(RuntimeError)
expect(flags[1]).to eq :rolled_back
expect(flags[4]).to eq :pending
expect(flags[3]).to eq :committed
expect(flags[2]).to eq :rolled_back
end

it 'rolls back in reverse commit order' do
flags = { 1 => -1, 2 => -1, 3 => -1, 4 => -1 }
commits = (1...4).map do |i|
UpAndAtThem::Commit.new { flags[i] = -1; if i==3 ; raise 'uh oh!'; end }.on_rollback { flags[i] = Time.now; sleep 1 }
end
expect { UpAndAtThem::Transaction.new commits }.to raise_error(RuntimeError)
expect(flags[4].to_f).to eq -1
expect(flags[3].to_f).to be < flags[2].to_f
expect(flags[2].to_f).to be < flags[1].to_f
end

it 'allows no-rollback commits' do
flags = {1 => :pending, 2 => :pending, 3 => :pending, 4 => :pending}
commits = (1...4).map do |i|
UpAndAtThem::Commit.new { flags[i] = :committed; if i==3 ; raise 'fail!'; end }
end
expect { UpAndAtThem::Transaction.new commits }.to raise_error(RuntimeError)
expect(flags[4]).to eq :pending
expect(flags[3]).to eq :committed
expect(flags[2]).to eq :committed
expect(flags[1]).to eq :committed
end

describe '.[]' do
it 'instantiates a new Transaction' do
commit = -> { raise 'fail!' }
expect { UpAndAtThem::Transaction[commit] }.to raise_error(RuntimeError)
end
end

describe 'duck typed Commits' do
class TestCommit
attr_reader :state

def initialize(fail = false)
@fail = fail
@state = :pending
end

def call
@state = :committed
raise if @fail
end

def rollback
@state = :rolled_back
end
end

it 'runs #call on each commit' do
commit = TestCommit.new
UpAndAtThem::Transaction[commit]
expect(commit.state).to eq :committed
end

it 'runs #rollback when the commit fails' do
commit1 = TestCommit.new
commit2 = TestCommit.new(true)
expect { UpAndAtThem::Transaction[commit1, commit2] }.to raise_error
expect(commit1.state).to eq :rolled_back
expect(commit2.state).to eq :committed
end

end

end
54 changes: 0 additions & 54 deletions test/test_transaction.rb

This file was deleted.

2 changes: 2 additions & 0 deletions up_and_at_them.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ Gem::Specification.new do |spec|

spec.add_development_dependency "bundler", "~> 1.6"
spec.add_development_dependency "rake"
spec.add_development_dependency "rspec"

end