Skip to content
Browse files

Initial

  • Loading branch information...
0 parents commit f008898f75917187105c201ba7117ee66fc418c9 @xaviershay committed Mar 9, 2010
Showing with 117 additions and 0 deletions.
  1. +12 −0 README
  2. +13 −0 Rakefile
  3. +85 −0 lib/dm-nested-transactions.rb
  4. +7 −0 rails/init.rb
12 README
@@ -0,0 +1,12 @@
+dm-nested-transactions
+----------------------
+
+Rails plugin to add nested transaction support to DataMapper. Only tested with Postgres, and it's a bit of a hack, so use at your own risk!
+
+Install:
+ gem install dm-nested-transactions
+
+Usage:
+ repository.adapter.extend(DataMapper::NestedTransactinos)
+
+There's a rails/init.rb file that automatically does the above for you.
13 Rakefile
@@ -0,0 +1,13 @@
+begin
+ require 'jeweler'
+ Jeweler::Tasks.new do |gemspec|
+ gemspec.name = "dm-nested-transactions"
+ gemspec.summary = "Adds nested transaction support to DM"
+ gemspec.description = "Only tested with Postgres, and it's a bit of a hack, so use at your own risk!"
+ gemspec.email = "contact@rhnh.net"
+ gemspec.homepage = "http://github.com/xaviershay/dm-nested-transactions"
+ gemspec.authors = ["Xavier Shay"]
+ end
+rescue LoadError
+ puts "Jeweler not available. Install it with: gem install jeweler"
+end
85 lib/dm-nested-transactions.rb
@@ -0,0 +1,85 @@
+# Hacks to get nested transactions in Postgres
+# Not extensively tested, more a proof of concept
+#
+# It re-opens the existing Transaction class to add a check for whether
+# we need a nested transaction or not, and adds a new NestedTransaction
+# transaction primitive that issues savepoint commands rather than begin/commit.
+
+module DataMapper
+ module Resource
+ def transaction(&block)
+ self.class.transaction(&block)
+ end
+ end
+
+ class Transaction
+ # Overridden to allow nested transactions
+ def connect_adapter(adapter)
+ if @transaction_primitives.key?(adapter)
+ raise "Already a primitive for adapter #{adapter}"
+ end
+
+ primitive = if adapter.current_transaction
+ adapter.nested_transaction_primitive
+ else
+ adapter.transaction_primitive
+ end
+
+ @transaction_primitives[adapter] = validate_primitive(primitive)
+ end
+ end
+
+ module NestedTransactions
+ def nested_transaction_primitive
+ DataObjects::NestedTransaction.create_for_uri(normalized_uri, current_connection)
+ end
+ end
+end
+
+module DataObjects
+ class NestedTransaction < Transaction
+
+ # The host name. Note, this relies on the host name being configured
+ # and resolvable using DNS
+ HOST = "#{Socket::gethostbyname(Socket::gethostname)[0]}" rescue "localhost"
+ @@counter = 0
+
+ # The connection object for this transaction - must have already had
+ # a transaction begun on it
+ attr_reader :connection
+ # A unique ID for this transaction
+ attr_reader :id
+
+ def self.create_for_uri(uri, connection)
+ uri = uri.is_a?(String) ? URI::parse(uri) : uri
+ DataObjects::NestedTransaction.new(uri, connection)
+ end
+
+ #
+ # Creates a NestedTransaction bound to an existing connection
+ #
+ def initialize(uri, connection)
+ @connection = connection
+ @id = Digest::SHA256.hexdigest("#{HOST}:#{$$}:#{Time.now.to_f}:nested:#{@@counter += 1}")
+ end
+
+ def close
+ end
+
+ def begin
+ cmd = "SAVEPOINT \"#{@id}\""
+ connection.create_command(cmd).execute_non_query
+ end
+
+ def commit
+ cmd = "RELEASE SAVEPOINT \"#{@id}\""
+ connection.create_command(cmd).execute_non_query
+ end
+
+ def rollback
+ cmd = "ROLLBACK TO SAVEPOINT \"#{@id}\""
+ connection.create_command(cmd).execute_non_query
+ end
+ end
+end
+
7 rails/init.rb
@@ -0,0 +1,7 @@
+class NestedTransactionConfig < Rails::Railtie
+ railtie_name :dm_nested_transactions
+
+ config.after_initialize do
+ repository.adapter.extend(DataMapper::NestedTransactions)
+ end
+end

0 comments on commit f008898

Please sign in to comment.
Something went wrong with that request. Please try again.