Module: Transaction::Simple
- Included in:
- PDF::SimpleTable, PDF::Writer, ThreadSafe
- Defined in:
- lib/gems/transaction-simple-1.4.0/lib/transaction/simple.rb
Overview
Transaction::Simple for Ruby
Simple object transaction support for Ruby
Defined Under Namespace
Modules: ThreadSafe Classes: Group
Constant Summary collapse
- TRANSACTION_SIMPLE_VERSION =
'1.4.0'
- SKIP_TRANSACTION_VARS =
%w(@__transaction_checkpoint__ @__transaction_level__)
Class Method Summary collapse
- .__common_start(name, vars, &block) ⇒ Object
-
.debug_io ⇒ Object
Returns the Transaction::Simple debug object.
-
.debug_io=(io) ⇒ Object
Sets the Transaction::Simple debug object.
-
.debugging? ⇒ Boolean
Returns
true
if we are debugging. -
.start(*vars, &block) ⇒ Object
Start a named transaction in a block.
-
.start_named(name, *vars, &block) ⇒ Object
Start a named transaction in a block.
Instance Method Summary collapse
-
#abort_transaction(name = nil) ⇒ Object
Aborts the transaction.
-
#commit_transaction(name = nil) ⇒ Object
If
name
isnil
(default), the current transaction level is closed out and the changes are committed. -
#rewind_transaction(name = nil) ⇒ Object
Rewinds the transaction.
-
#start_transaction(name = nil) ⇒ Object
Starts a transaction.
-
#transaction(action = nil, name = nil) ⇒ Object
Alternative method for calling the transaction methods.
-
#transaction_exclusions ⇒ Object
Allows specific variables to be excluded from transaction support.
-
#transaction_name ⇒ Object
Returns the current name of the transaction.
-
#transaction_open?(name = nil) ⇒ Boolean
If
name
isnil
(default), then returnstrue
if there is currently a transaction open.
Class Method Details
.__common_start(name, vars, &block) ⇒ Object
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 |
# File 'lib/gems/transaction-simple-1.4.0/lib/transaction/simple.rb', line 346 def __common_start(name, vars, &block) raise Transaction::TransactionError, Transaction::Messages[:cannot_start_empty_block_transaction] if vars.empty? if block begin vlevel = {} vars.each do |vv| vv.extend(Transaction::Simple) vv.start_transaction(name) vlevel[vv.__id__] = vv.instance_variable_get(:@__transaction_level__) vv.instance_variable_set(:@__transaction_block__, vlevel[vv.__id__]) end yield(*vars) rescue Transaction::TransactionAborted vars.each do |vv| if name.nil? and vv.transaction_open? loop do tlevel = vv.instance_variable_get(:@__transaction_level__) || -1 vv.instance_variable_set(:@__transaction_block__, -1) break if tlevel < vlevel[vv.__id__] vv.abort_transaction if vv.transaction_open? end elsif vv.transaction_open?(name) vv.instance_variable_set(:@__transaction_block__, -1) vv.abort_transaction(name) end end rescue Transaction::TransactionCommitted nil ensure vars.each do |vv| if name.nil? and vv.transaction_open? loop do tlevel = vv.instance_variable_get(:@__transaction_level__) || -1 break if tlevel < vlevel[vv.__id__] vv.instance_variable_set(:@__transaction_block__, -1) vv.commit_transaction if vv.transaction_open? end elsif vv.transaction_open?(name) vv.instance_variable_set(:@__transaction_block__, -1) vv.commit_transaction(name) end end end else vars.each do |vv| vv.extend(Transaction::Simple) vv.start_transaction(name) end end end |
.debug_io ⇒ Object
Returns the Transaction::Simple debug object. It must respond to #<<.
75 76 77 78 |
# File 'lib/gems/transaction-simple-1.4.0/lib/transaction/simple.rb', line 75 def debug_io @tdi ||= "" @tdi end |
.debug_io=(io) ⇒ Object
Sets the Transaction::Simple debug object. It must respond to #<<. Debugging will be performed automatically if there’s a debug object.
58 59 60 61 62 63 64 65 66 67 |
# File 'lib/gems/transaction-simple-1.4.0/lib/transaction/simple.rb', line 58 def debug_io=(io) if io.nil? @tdi = nil @debugging = false else raise Transaction::TransactionError, Transaction::Messages[:bad_debug_object] unless io.respond_to?(:<<) @tdi = io @debugging = true end end |
.debugging? ⇒ Boolean
Returns true
if we are debugging.
70 71 72 |
# File 'lib/gems/transaction-simple-1.4.0/lib/transaction/simple.rb', line 70 def debugging? defined? @debugging and @debugging end |
.start(*vars, &block) ⇒ Object
Start a named transaction in a block. The transaction will auto-commit when the block finishes.
409 410 411 |
# File 'lib/gems/transaction-simple-1.4.0/lib/transaction/simple.rb', line 409 def start(*vars, &block) __common_start(nil, vars, &block) end |
.start_named(name, *vars, &block) ⇒ Object
Start a named transaction in a block. The transaction will auto-commit when the block finishes.
403 404 405 |
# File 'lib/gems/transaction-simple-1.4.0/lib/transaction/simple.rb', line 403 def start_named(name, *vars, &block) __common_start(name, vars, &block) end |
Instance Method Details
#abort_transaction(name = nil) ⇒ Object
Aborts the transaction. Rewinds the object state to what it was before the transaction was started and closes the transaction. If name
is specified, then the intervening transactions and the named transaction will be aborted. Otherwise, only the current transaction is aborted.
See #rewind_transaction for information about dealing with complex self-referential object graphs.
If the current or named transaction has been started by a block (Transaction::Simple.start), then the execution of the block will be halted with break
self
.
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
# File 'lib/gems/transaction-simple-1.4.0/lib/transaction/simple.rb', line 232 def abort_transaction(name = nil) raise Transaction::TransactionError, Transaction::Messages[:cannot_abort_no_transaction] if @__transaction_checkpoint__.nil? # Check to see if we are trying to abort a transaction that is outside # of the current transaction block. Otherwise, raise TransactionAborted # if they are the same. defined? @__transaction_block__ or @__transaction_block__ = nil if @__transaction_block__ and name nix = @__transaction_names__.index(name) + 1 raise Transaction::TransactionError, Transaction::Messages[:cannot_abort_transaction_before_block] if nix < @__transaction_block__ raise Transaction::TransactionAborted if @__transaction_block__ == nix end raise Transaction::TransactionAborted if @__transaction_block__ == @__transaction_level__ if name.nil? __abort_transaction(name) else raise Transaction::TransactionError, Transaction::Messages[:cannot_abort_named_transaction] % name.inspect unless @__transaction_names__.include?(name) __abort_transaction(name) while @__transaction_names__.include?(name) end self end |
#commit_transaction(name = nil) ⇒ Object
If name
is nil
(default), the current transaction level is closed out and the changes are committed.
If name
is specified and name
is in the list of named transactions, then all transactions are closed and committed until the named transaction is reached.
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/gems/transaction-simple-1.4.0/lib/transaction/simple.rb', line 264 def commit_transaction(name = nil) raise Transaction::TransactionError, Transaction::Messages[:cannot_commit_no_transaction] if @__transaction_checkpoint__.nil? @__transaction_block__ ||= nil # Check to see if we are trying to commit a transaction that is outside # of the current transaction block. Otherwise, raise # TransactionCommitted if they are the same. if @__transaction_block__ and name nix = @__transaction_names__.index(name) + 1 raise Transaction::TransactionError, Transaction::Messages[:cannot_commit_transaction_before_block] if nix < @__transaction_block__ raise Transaction::TransactionCommitted if @__transaction_block__ == nix end raise Transaction::TransactionCommitted if @__transaction_block__ == @__transaction_level__ if name.nil? ss = "" if Transaction::Simple.debugging? __commit_transaction Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " << "Commit Transaction#{ss}\n" if Transaction::Simple.debugging? else raise Transaction::TransactionError, Transaction::Messages[:cannot_commit_named_transaction] % name.inspect unless @__transaction_names__.include?(name) ss = "(#{name})" if Transaction::Simple.debugging? while @__transaction_names__[-1] != name Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " << "Commit Transaction#{ss}\n" if Transaction::Simple.debugging? __commit_transaction end Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " << "Commit Transaction#{ss}\n" if Transaction::Simple.debugging? __commit_transaction end self end |
#rewind_transaction(name = nil) ⇒ Object
Rewinds the transaction. If name
is specified, then the intervening transactions will be aborted and the named transaction will be rewound. Otherwise, only the current transaction is rewound.
After each level of transaction is rewound, if the callback method #_post_transaction_rewind is defined, it will be called. It is intended to allow a complex self-referential graph to fix itself. The simplest way to explain this is with an example.
class Child
attr_accessor :parent
end
class Parent
include Transaction::Simple
attr_reader :children
def initialize
@children = []
end
def << child
child.parent = self
@children << child
end
def valid?
@children.all? { |child| child.parent == self }
end
end
parent = Parent.new
parent << Child.new
parent.start_transaction
parent << Child.new
parent.abort_transaction
puts parent.valid? # => false
This problem can be fixed by modifying the Parent class to include the #_post_transaction_rewind callback.
class Parent
# Reconnect the restored children to me, instead of to the bogus me
# that was restored to them by Marshal::load.
def _post_transaction_rewind
@children.each { |child| child.parent = self }
end
end
parent = Parent.new
parent << Child.new
parent.start_transaction
parent << Child.new
parent.abort_transaction
puts parent.valid? # => true
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/gems/transaction-simple-1.4.0/lib/transaction/simple.rb', line 187 def rewind_transaction(name = nil) raise Transaction::TransactionError, Transaction::Messages[:cannot_rewind_no_transaction] if @__transaction_checkpoint__.nil? # Check to see if we are trying to rewind a transaction that is # outside of the current transaction block. defined? @__transaction_block__ or @__transaction_block__ = nil if @__transaction_block__ and name nix = @__transaction_names__.index(name) + 1 raise Transaction::TransactionError, Transaction::Messages[:cannot_rewind_transaction_before_block] if nix < @__transaction_block__ end if name.nil? checkpoint = @__transaction_checkpoint__ __rewind_this_transaction @__transaction_checkpoint__ = checkpoint ss = "" if Transaction::Simple.debugging? else raise Transaction::TransactionError, Transaction::Messages[:cannot_rewind_named_transaction] % name.inspect unless @__transaction_names__.include?(name) ss = "(#{name})" if Transaction::Simple.debugging? while @__transaction_names__[-1] != name @__transaction_checkpoint__ = __rewind_this_transaction Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " << "Rewind Transaction#{ss}\n" if Transaction::Simple.debugging? @__transaction_level__ -= 1 @__transaction_names__.pop end checkpoint = @__transaction_checkpoint__ __rewind_this_transaction @__transaction_checkpoint__ = checkpoint end Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " << "Rewind Transaction#{ss}\n" if Transaction::Simple.debugging? self end |
#start_transaction(name = nil) ⇒ Object
Starts a transaction. Stores the current object state. If a transaction name is specified, the transaction will be named. Transaction names must be unique. Transaction names of nil
will be treated as unnamed transactions.
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/gems/transaction-simple-1.4.0/lib/transaction/simple.rb', line 111 def start_transaction(name = nil) @__transaction_level__ ||= 0 @__transaction_names__ ||= [] name = name.dup.freeze if name.kind_of?(String) raise Transaction::TransactionError, Transaction::Messages[:unique_names] if name and @__transaction_names__.include?(name) @__transaction_names__ << name @__transaction_level__ += 1 if Transaction::Simple.debugging? ss = "(#{name.inspect})" ss = "" unless ss Transaction::Simple.debug_io << "#{'>' * @__transaction_level__} " << "Start Transaction#{ss}\n" end @__transaction_checkpoint__ = Marshal.dump(self) end |
#transaction(action = nil, name = nil) ⇒ Object
Alternative method for calling the transaction methods. An optional name can be specified for named transaction support. This method is deprecated and will be removed in Transaction::Simple 2.0.
- #transaction(:start)
-
#start_transaction
- #transaction(:rewind)
-
#rewind_transaction
- #transaction(:abort)
-
#abort_transaction
- #transaction(:commit)
-
#commit_transaction
- #transaction(:name)
-
#transaction_name
- #transaction
-
#transaction_open?
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
# File 'lib/gems/transaction-simple-1.4.0/lib/transaction/simple.rb', line 309 def transaction(action = nil, name = nil) _method = case action when :start then :start_transaction when :rewind then :rewind_transaction when :abort then :abort_transaction when :commit then :commit_transaction when :name then :transaction_name when nil then :transaction_open? else nil end if method warn "The #transaction method has been deprecated. Use #{method} instead." else warn "The #transaction method has been deprecated." end case method when :transaction_name __send__ method when nil nil else __send__ method, name end end |
#transaction_exclusions ⇒ Object
Allows specific variables to be excluded from transaction support. Must be done after extending the object but before starting the first transaction on the object.
vv.transaction_exclusions << "@io"
341 342 343 |
# File 'lib/gems/transaction-simple-1.4.0/lib/transaction/simple.rb', line 341 def transaction_exclusions @transaction_exclusions ||= [] end |
#transaction_name ⇒ Object
Returns the current name of the transaction. Transactions not explicitly named are named nil
.
97 98 99 100 101 102 103 104 105 |
# File 'lib/gems/transaction-simple-1.4.0/lib/transaction/simple.rb', line 97 def transaction_name raise Transaction::TransactionError, Transaction::Messages[:no_transaction_open] if @__transaction_checkpoint__.nil? Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " << "Transaction Name: #{@__transaction_names__[-1].inspect}\n" if Transaction::Simple.debugging? if @__transaction_names__[-1].kind_of?(String) @__transaction_names__[-1].dup else @__transaction_names__[-1] end end |
#transaction_open?(name = nil) ⇒ Boolean
If name
is nil
(default), then returns true
if there is currently a transaction open. If name
is specified, then returns true
if there is currently a transaction known as name
open.
84 85 86 87 88 89 90 91 92 93 |
# File 'lib/gems/transaction-simple-1.4.0/lib/transaction/simple.rb', line 84 def transaction_open?(name = nil) defined? @__transaction_checkpoint__ or @__transaction_checkpoint__ = nil if name.nil? Transaction::Simple.debug_io << "Transaction " << "[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" if Transaction::Simple.debugging? return (not @__transaction_checkpoint__.nil?) else Transaction::Simple.debug_io << "Transaction(#{name.inspect}) " << "[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" if Transaction::Simple.debugging? return ((not @__transaction_checkpoint__.nil?) and @__transaction_names__.include?(name)) end end |