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?
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...
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
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.
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
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?