1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
class Test @@tries=0 attr_reader :num def initialize( num ) @num = num end def do @@tries+=1 puts "Doing #{@num}" @@tries<3 end def undo puts "Undoing #{@num}" end end def transact( objs, actions ) done = [] objs.each { |obj| done.unshift obj if !obj.send actions[0] puts "Unrolling" done.each { |d| d.send actions[1] } break end } end actions = [ :do, :undo ] objs = (1..5).collect { |n| Test.new n } transact( objs, actions )
Refactorings
No refactoring yet !
John Sietsma
August 20, 2008, August 20, 2008 07:25, permalink
This is slightly neater and perhaps more idiomatic Ruby.
I forgot I needed a return code for "if transact...". I don't like repeating the length test, but didn't want the extra line: "ok = done.length==objs.length". Any other ways to do this?
1 2 3 4 5 6 7 8 9
def transact( objs, actions ) done = [] objs.each { |obj| done.unshift obj break unless obj.send actions[0] } done.each { |d| d.send actions[1] } unless done.length==objs.length done.length==objs.length end
John Sietsma
August 20, 2008, August 20, 2008 07:37, permalink
Well, this covers it but is less readable.
Hmmm, 2 posts in response to my own code! This is like laughing at your own jokes...
1 2 3 4 5 6 7 8
def transact( objs, actions ) done = [] objs.each { |obj| done.unshift obj break unless obj.send actions[0] } !( done.each { |d| d.send actions[1] } unless done.length==objs.length ) end
Wolfbyte
August 20, 2008, August 20, 2008 08:19, permalink
Even less readable, but how about this one line recursive version. It relies on short-circuit evaluation to get the job done. Being recursive it naturally does the roll/unroll that the iterative solution provides
1 2 3
def transact( objs, actions ) (objs.empty? or (objs[0].send(actions[0]) and transact(objs[1..-1],actions) )) or (objs[0].send(actions[1]) and false) end
Wolfbyte
August 20, 2008, August 20, 2008 09:01, permalink
This version behaves in much the same way but it is easier to figure out what it is doing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
def transact( objs, actions ) return true if objs.empty? forward, backward = actions head, *tail = objs succeeded = false if head.send forward if transact tail, actions succeeded = true end end head.send backward unless succeeded succeeded end
Adam
August 20, 2008, August 20, 2008 14:06, permalink
1 2 3 4 5 6 7 8 9 10 11 12
require 'transaction/simple/group' def transact(objects, action) group = Transaction::Simple::Group.new(*objects) group.start_transaction if objects.all? { |object| object.send(action) } group.commit_transaction else group.abort_transaction end end
In a Rails controller I'm saving a number of model objects at once, but I want to be able to revert changes if there is an error part way through.
What do you think of this approach?