class DataMapper::Transaction

Attributes

state[RW]

@api private

Public Class Methods

new(*things) { |*block_args| ... } click to toggle source

Create a new Transaction

@see #link

In fact, it just calls link with the given arguments at the end of the constructor.

@api public

# File lib/dm-transactions.rb, line 38
def initialize(*things)
  @transaction_primitives = {}
  self.state = :none
  @adapters = {}
  link(*things)
  if block_given?
    warn "Passing block to #{self.class.name}.new is deprecated (#{caller[0]})"
    commit { |*block_args| yield(*block_args) }
  end
end

Private Class Methods

include_transaction_api() click to toggle source
# File lib/dm-transactions.rb, line 391
def self.include_transaction_api
  [ :Repository, :Model, :Resource ].each do |name|
    DataMapper.const_get(name).send(:include, const_get(name))
  end
  Adapters::AbstractAdapter.descendants.each do |adapter_class|
    Adapters.include_transaction_api(DataMapper::Inflector.demodulize(adapter_class.name))
  end
end

Public Instance Methods

begin() click to toggle source

Begin the transaction

Before begin is called, the transaction is not valid and can not be used.

@api private

# File lib/dm-transactions.rb, line 103
def begin
  unless none?
    raise "Illegal state for begin: #{state}"
  end

  each_adapter(:connect_adapter, [:log_fatal_transaction_breakage])
  each_adapter(:begin_adapter, [:rollback_and_close_adapter_if_begin, :close_adapter_if_none])
  self.state = :begin
end
begin?() click to toggle source

@api private

# File lib/dm-transactions.rb, line 16
def begin?
  state == :begin
end
commit() { |*block_args| ... } click to toggle source

Commit the transaction

If no block is given, it will simply commit any changes made since the
Transaction did #begin.

@param block<Block> a block (taking the one argument, the Transaction) to

execute within this transaction. The transaction will begin and commit
around the block, and roll back if an exception is raised.

@api private

# File lib/dm-transactions.rb, line 123
def commit
  if block_given?
    unless none?
      raise "Illegal state for commit with block: #{state}"
    end

    begin
      self.begin
      rval = within { |*block_args| yield(*block_args) }
    rescue Exception => exception
      if begin?
        rollback
      end
      raise exception
    ensure
      unless exception
        if begin?
          commit
        end
        return rval
      end
    end
  else
    unless begin?
      raise "Illegal state for commit without block: #{state}"
    end
    each_adapter(:commit_adapter, [:log_fatal_transaction_breakage])
    each_adapter(:close_adapter, [:log_fatal_transaction_breakage])
    self.state = :commit
  end
end
commit?() click to toggle source

@api private

# File lib/dm-transactions.rb, line 26
def commit?
  state == :commit
end
method_missing(method, *args, &block) click to toggle source

@api private

Calls superclass method
# File lib/dm-transactions.rb, line 204
def method_missing(method, *args, &block)
  first_arg = args.first

  return super unless args.size == 1 && first_arg.kind_of?(Adapters::AbstractAdapter)
  return super unless match = method.to_s.match(/\A(.*)_(if|unless)_(none|begin|rollback|commit)\z/)

  action, condition, expected_state = match.captures
  return super unless respond_to?(action, true)

  state   = state_for(first_arg).to_s
  execute = (condition == 'if') == (state == expected_state)

  send(action, first_arg) if execute
end
none?() click to toggle source

@api private

# File lib/dm-transactions.rb, line 11
def none?
  state == :none
end
primitive_for(adapter) click to toggle source

@api private

# File lib/dm-transactions.rb, line 220
def primitive_for(adapter)
  unless @adapters.include?(adapter)
    raise "Unknown adapter #{adapter}"
  end

  unless @transaction_primitives.include?(adapter)
    raise "No primitive for #{adapter}"
  end

  @transaction_primitives[adapter]
end
rollback() click to toggle source

Rollback the transaction

Will undo all changes made during the transaction.

@api private

# File lib/dm-transactions.rb, line 160
def rollback
  unless begin?
    raise "Illegal state for rollback: #{state}"
  end
  each_adapter(:rollback_adapter_if_begin, [:rollback_and_close_adapter_if_begin, :close_adapter_if_none])
  each_adapter(:close_adapter_if_open, [:log_fatal_transaction_breakage])
  self.state = :rollback
end
rollback?() click to toggle source

@api private

# File lib/dm-transactions.rb, line 21
def rollback?
  state == :rollback
end
within() { |self| ... } click to toggle source

Execute a block within this Transaction.

No begin, commit or rollback is performed in within, but this Transaction will pushed on the per thread stack of transactions for each adapter it is associated with, and it will ensures that it will pop the Transaction away again after the block is finished.

@param block<Block> the block of code to execute.

@api private

# File lib/dm-transactions.rb, line 179
def within
  unless block_given?
    raise 'No block provided'
  end

  unless begin?
    raise "Illegal state for within: #{state}"
  end

  adapters = @adapters

  adapters.each_key do |adapter|
    adapter.push_transaction(self)
  end

  begin
    yield self
  ensure
    adapters.each_key do |adapter|
      adapter.pop_transaction
    end
  end
end

Private Instance Methods

begin_adapter(adapter) click to toggle source

@api private

# File lib/dm-transactions.rb, line 324
def begin_adapter(adapter)
  do_adapter(adapter, :begin, :none)
end
close_adapter(adapter) click to toggle source

@api private

# File lib/dm-transactions.rb, line 314
def close_adapter(adapter)
  unless @transaction_primitives.include?(adapter)
    raise 'No primitive for adapter'
  end

  @transaction_primitives[adapter].close
  @transaction_primitives.delete(adapter)
end
close_adapter_if_open(adapter) click to toggle source

@api private

# File lib/dm-transactions.rb, line 307
def close_adapter_if_open(adapter)
  if @transaction_primitives.include?(adapter)
    close_adapter(adapter)
  end
end
commit_adapter(adapter) click to toggle source

@api private

# File lib/dm-transactions.rb, line 329
def commit_adapter(adapter)
  do_adapter(adapter, :commit, :begin)
end
connect_adapter(adapter) click to toggle source

@api private

# File lib/dm-transactions.rb, line 298
def connect_adapter(adapter)
  if @transaction_primitives.key?(adapter)
    raise "Already a primitive for adapter #{adapter}"
  end

  @transaction_primitives[adapter] = validate_primitive(adapter.transaction_primitive)
end
do_adapter(adapter, what, prerequisite) click to toggle source

@api private

# File lib/dm-transactions.rb, line 276
def do_adapter(adapter, what, prerequisite)
  unless @transaction_primitives.include?(adapter)
    raise "No primitive for #{adapter}"
  end

  state = state_for(adapter)

  unless state == prerequisite
    raise "Illegal state for #{what}: #{state}"
  end

  DataMapper.logger.debug("#{adapter.name}: #{what}")
  @transaction_primitives[adapter].send(what)
  @adapters[adapter] = what
end
each_adapter(method, on_fail) click to toggle source

@api private

# File lib/dm-transactions.rb, line 246
def each_adapter(method, on_fail)
  adapters = @adapters
  begin
    adapters.each_key do |adapter|
      send(method, adapter)
    end
  rescue Exception => exception
    adapters.each_key do |adapter|
      on_fail.each do |fail_handler|
        begin
          send(fail_handler, adapter)
        rescue Exception => inner_exception
          DataMapper.logger.fatal("#{self}#each_adapter(#{method.inspect}, #{on_fail.inspect}) failed with #{exception.inspect}: #{exception.backtrace.join("\n")} - and when sending #{fail_handler} to #{adapter} we failed again with #{inner_exception.inspect}: #{inner_exception.backtrace.join("\n")}")
        end
      end
    end
    raise exception
  end
end
log_fatal_transaction_breakage(adapter) click to toggle source

@api private

# File lib/dm-transactions.rb, line 293
def log_fatal_transaction_breakage(adapter)
  DataMapper.logger.fatal("#{self} experienced a totally broken transaction execution. Presenting member #{adapter.inspect}.")
end
rollback_adapter(adapter) click to toggle source

@api private

# File lib/dm-transactions.rb, line 334
def rollback_adapter(adapter)
  do_adapter(adapter, :rollback, :begin)
end
rollback_and_close_adapter(adapter) click to toggle source

@api private

# File lib/dm-transactions.rb, line 339
def rollback_and_close_adapter(adapter)
  rollback_adapter(adapter)
  close_adapter(adapter)
end
state_for(adapter) click to toggle source

@api private

# File lib/dm-transactions.rb, line 267
def state_for(adapter)
  unless @adapters.include?(adapter)
    raise "Unknown adapter #{adapter}"
  end

  @adapters[adapter]
end
validate_primitive(primitive) click to toggle source

@api private

# File lib/dm-transactions.rb, line 235
def validate_primitive(primitive)
  [:close, :begin, :rollback, :commit].each do |meth|
    unless primitive.respond_to?(meth)
      raise "Invalid primitive #{primitive}: doesnt respond_to?(#{meth.inspect})"
    end
  end

  primitive
end