From cf4828f1096f5c05b2dbc1151ccdbebd1b0182ed Mon Sep 17 00:00:00 2001 From: Logan Serman Date: Mon, 12 Jan 2015 14:12:32 -0600 Subject: [PATCH 1/7] Move Commit/Transaction into separate files --- lib/up_and_at_them.rb | 50 ++----------------------------- lib/up_and_at_them/commit.rb | 19 ++++++++++++ lib/up_and_at_them/transaction.rb | 27 +++++++++++++++++ 3 files changed, 49 insertions(+), 47 deletions(-) create mode 100644 lib/up_and_at_them/commit.rb create mode 100644 lib/up_and_at_them/transaction.rb diff --git a/lib/up_and_at_them.rb b/lib/up_and_at_them.rb index 560e995..46e2cf9 100644 --- a/lib/up_and_at_them.rb +++ b/lib/up_and_at_them.rb @@ -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' \ No newline at end of file diff --git a/lib/up_and_at_them/commit.rb b/lib/up_and_at_them/commit.rb new file mode 100644 index 0000000..f575153 --- /dev/null +++ b/lib/up_and_at_them/commit.rb @@ -0,0 +1,19 @@ +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 \ No newline at end of file diff --git a/lib/up_and_at_them/transaction.rb b/lib/up_and_at_them/transaction.rb new file mode 100644 index 0000000..f58c6cb --- /dev/null +++ b/lib/up_and_at_them/transaction.rb @@ -0,0 +1,27 @@ +module UpAndAtThem + 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 \ No newline at end of file From b98dffea511cee0d8243ee19cae00e326db2efb4 Mon Sep 17 00:00:00 2001 From: Logan Serman Date: Mon, 12 Jan 2015 14:13:47 -0600 Subject: [PATCH 2/7] Make Transaction.[] for syntax sugar --- lib/up_and_at_them/commit.rb | 5 +++++ lib/up_and_at_them/transaction.rb | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/lib/up_and_at_them/commit.rb b/lib/up_and_at_them/commit.rb index f575153..2537135 100644 --- a/lib/up_and_at_them/commit.rb +++ b/lib/up_and_at_them/commit.rb @@ -1,19 +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 \ No newline at end of file diff --git a/lib/up_and_at_them/transaction.rb b/lib/up_and_at_them/transaction.rb index f58c6cb..72ec45a 100644 --- a/lib/up_and_at_them/transaction.rb +++ b/lib/up_and_at_them/transaction.rb @@ -1,5 +1,10 @@ module UpAndAtThem class Transaction + + def self.[](tasks) + new(tasks) + end + def initialize(tasks) tasks = Array(tasks) tasks.each do |t| @@ -23,5 +28,6 @@ def run raise err end end + end end \ No newline at end of file From 51424e84bd540ff33d3ec792e3b5dedf04408ada Mon Sep 17 00:00:00 2001 From: Logan Serman Date: Mon, 12 Jan 2015 14:14:28 -0600 Subject: [PATCH 3/7] Allow duck typing --- lib/up_and_at_them/transaction.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/up_and_at_them/transaction.rb b/lib/up_and_at_them/transaction.rb index 72ec45a..e1feb48 100644 --- a/lib/up_and_at_them/transaction.rb +++ b/lib/up_and_at_them/transaction.rb @@ -6,11 +6,7 @@ def self.[](tasks) end def initialize(tasks) - tasks = Array(tasks) - tasks.each do |t| - Commit === t or raise TypeError - end - @tasks = tasks + @tasks = Array(tasks) run end From bd53ed0664ad7f9583777aef0d494e9e43ab3b3f Mon Sep 17 00:00:00 2001 From: Logan Serman Date: Mon, 12 Jan 2015 14:15:53 -0600 Subject: [PATCH 4/7] Remove useless begin statement and use Symbol#to_proc while rolling back --- lib/up_and_at_them/transaction.rb | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/up_and_at_them/transaction.rb b/lib/up_and_at_them/transaction.rb index e1feb48..421202c 100644 --- a/lib/up_and_at_them/transaction.rb +++ b/lib/up_and_at_them/transaction.rb @@ -12,17 +12,13 @@ def initialize(tasks) 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 + @tasks.each do |task| + task.call + finished_tasks << task end + rescue => err + finished_tasks.reverse_each(&:rollback) + raise err end end From 3d60515c3325c1c8d6afe24178a4b339490f19de Mon Sep 17 00:00:00 2001 From: Logan Serman Date: Mon, 12 Jan 2015 14:32:10 -0600 Subject: [PATCH 5/7] Switch to RSpec --- .rspec | 2 ++ spec/spec_helper.rb | 9 +++++++ spec/transaction_spec.rb | 44 ++++++++++++++++++++++++++++++++ test/test_transaction.rb | 54 ---------------------------------------- up_and_at_them.gemspec | 2 ++ 5 files changed, 57 insertions(+), 54 deletions(-) create mode 100644 .rspec create mode 100644 spec/spec_helper.rb create mode 100644 spec/transaction_spec.rb delete mode 100644 test/test_transaction.rb diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..3687797 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--require spec_helper +--color diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..e6ded37 --- /dev/null +++ b/spec/spec_helper.rb @@ -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 diff --git a/spec/transaction_spec.rb b/spec/transaction_spec.rb new file mode 100644 index 0000000..227ca16 --- /dev/null +++ b/spec/transaction_spec.rb @@ -0,0 +1,44 @@ +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 +end \ No newline at end of file diff --git a/test/test_transaction.rb b/test/test_transaction.rb deleted file mode 100644 index e16867c..0000000 --- a/test/test_transaction.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'minitest/autorun' -require 'up_and_at_them' - -class TransactionTest < MiniTest::Test - def test_rollback_past_commits - flags = {1 => :pending, 2 => :pending, 3 => :pending, 4 => :pending} - - commits = Array.new - (1...4).each do |i| - commits << UpAndAtThem::Commit.new { flags[i] = :committed; if i==3 ; raise 'uh oh!'; end }.on_rollback { flags[i] = :rolled_back } - end - - assert_raises RuntimeError do - UpAndAtThem::Transaction.new commits - end - assert_equal :pending, flags[4] - assert_equal :committed, flags[3] - assert_equal :rolled_back, flags[2] - assert_equal :rolled_back, flags[1] - end - - def test_rollback_in_reverse_commit_order - flags = {1 => -1, 2 => -1, 3 => -1, 4 => -1} - - commits = Array.new - (1...4).each do |i| - commits << UpAndAtThem::Commit.new { flags[i] = -1; if i==3 ; raise 'uh oh!'; end }.on_rollback { flags[i] = Time.now; sleep 1 } - end - - assert_raises RuntimeError do - UpAndAtThem::Transaction.new commits - end - assert_equal -1, flags[4] - assert flags[3].to_f < flags[2].to_f - assert flags[2].to_f < flags[1].to_f - end - - def test_allow_no_rollback_blocks - flags = {1 => :pending, 2 => :pending, 3 => :pending, 4 => :pending} - - commits = Array.new - (1...4).each do |i| - commits << UpAndAtThem::Commit.new { flags[i] = :committed; if i==3 ; raise 'fail!'; end } - end - - assert_raises RuntimeError do - UpAndAtThem::Transaction.new commits - end - assert_equal :pending, flags[4] - assert_equal :committed, flags[3] - assert_equal :committed, flags[2] - assert_equal :committed, flags[1] - end -end \ No newline at end of file diff --git a/up_and_at_them.gemspec b/up_and_at_them.gemspec index f0b49d5..cafa63b 100644 --- a/up_and_at_them.gemspec +++ b/up_and_at_them.gemspec @@ -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 From a2122c1b2dd3527215fa8713d2d8c058485dd0c7 Mon Sep 17 00:00:00 2001 From: Logan Serman Date: Mon, 12 Jan 2015 14:44:20 -0600 Subject: [PATCH 6/7] Add test for duck typed Commits --- README.md | 14 ++++++++---- lib/up_and_at_them/transaction.rb | 2 +- spec/transaction_spec.rb | 36 +++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ccbd830..2c61647 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/lib/up_and_at_them/transaction.rb b/lib/up_and_at_them/transaction.rb index 421202c..38a486c 100644 --- a/lib/up_and_at_them/transaction.rb +++ b/lib/up_and_at_them/transaction.rb @@ -1,7 +1,7 @@ module UpAndAtThem class Transaction - def self.[](tasks) + def self.[](*tasks) new(tasks) end diff --git a/spec/transaction_spec.rb b/spec/transaction_spec.rb index 227ca16..0c6dca8 100644 --- a/spec/transaction_spec.rb +++ b/spec/transaction_spec.rb @@ -41,4 +41,40 @@ 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 \ No newline at end of file From d545eb366c11c628c451a9002316b627417492de Mon Sep 17 00:00:00 2001 From: Logan Serman Date: Mon, 12 Jan 2015 14:45:19 -0600 Subject: [PATCH 7/7] Fixing syntax error in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c61647..8ec4603 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ initialized. UpAndAtThem::Transaction[ UpAndAtThem::Commit.new { puts "do something!" }.on_rollback { "undo something!" }, - UpAndAtThem::Commit.new { puts "do something else!" }.on_rollback { "undo something else!" }, + 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