ar_after_transaction 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'rake'
4
+ gem 'activerecord', :require => 'active_record'
5
+ gem 'activesupport', :require => 'active_support'
6
+ gem 'sqlite3-ruby', :require => 'sqlite3'
@@ -0,0 +1,20 @@
1
+ task :default => :spec
2
+ require 'spec/rake/spectask'
3
+ Spec::Rake::SpecTask.new {|t| t.spec_opts = ['--color --backtrace']}
4
+
5
+ begin
6
+ require 'jeweler'
7
+ project_name = 'ar_after_transaction'
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = project_name
10
+ gem.summary = "Execute irreversible actions only when transactions are not rolled back"
11
+ gem.email = "grosser.michael@gmail.com"
12
+ gem.homepage = "http://github.com/grosser/#{project_name}"
13
+ gem.authors = ["Michael Grosser"]
14
+ gem.add_dependency ['activerecord']
15
+ end
16
+
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: gem install jeweler"
20
+ end
@@ -0,0 +1,58 @@
1
+ Perform stuff (only) after the currently open transactions have finished.
2
+
3
+ Normally everything gets rolled back when a transaction fails, but you cannot roll back sending an email or adding a job to Resque.
4
+
5
+ Its great for just-in-time callbacks:
6
+ class User
7
+ after_create :do_stuff, :oops
8
+
9
+ def do_stuff
10
+ after_transaction do
11
+ send_an_email # cannot be rolled back
12
+ end
13
+ comments.build(...) # can be rolled back
14
+ end
15
+
16
+ def oops
17
+ raise "do the rolback!"
18
+ end
19
+ end
20
+
21
+ Or general 'this should be rolled back when in a transaction code like jobs:
22
+
23
+ class Resque
24
+ def revertable_enqueue(*args)
25
+ ActiveRecord::Base.after_transaction do
26
+ enqueue(*args)
27
+ end
28
+ end
29
+ end
30
+
31
+ There is a after_commit hook in Rails 3, which can replace the first usage:
32
+
33
+ class User
34
+ after_commit :send_an_email :on=>:create
35
+ ater_create :do_stuff, :oops
36
+ ...
37
+ end
38
+
39
+ after_transaction will behave like it would not exist if you are not in an transaction when calling it.
40
+
41
+
42
+ rails plugin install git://github.com/grosser/ar_after_transaction
43
+ gem install ar_after_transaction
44
+
45
+ TODO
46
+ =====
47
+ - find out if we are in test mode with or without transactions (atm assumes depth of 1 for 'test' env)
48
+
49
+
50
+ Author
51
+ ======
52
+ Original idea and code comes from: Jeremy Kemper @ https://rails.lighthouseapp.com/projects/8994/tickets/2991-after-transaction-patch
53
+
54
+
55
+ [Michael Grosser](http://pragmatig.wordpress.com)
56
+ grosser.michael@gmail.com
57
+ Hereby placed under public domain, do what you want, just do not hold me accountable...
58
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,50 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{ar_after_transaction}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Michael Grosser"]
12
+ s.date = %q{2010-08-28}
13
+ s.email = %q{grosser.michael@gmail.com}
14
+ s.files = [
15
+ "Gemfile",
16
+ "Rakefile",
17
+ "Readme.md",
18
+ "VERSION",
19
+ "ar_after_transaction.gemspec",
20
+ "init.rb",
21
+ "lib/ar_after_transaction.rb",
22
+ "spec/ar_after_transaction_spec.rb",
23
+ "spec/setup_database.rb",
24
+ "spec/spec_helper.rb"
25
+ ]
26
+ s.homepage = %q{http://github.com/grosser/ar_after_transaction}
27
+ s.rdoc_options = ["--charset=UTF-8"]
28
+ s.require_paths = ["lib"]
29
+ s.rubygems_version = %q{1.3.6}
30
+ s.summary = %q{Execute irreversible actions only when transactions are not rolled back}
31
+ s.test_files = [
32
+ "spec/setup_database.rb",
33
+ "spec/spec_helper.rb",
34
+ "spec/ar_after_transaction_spec.rb"
35
+ ]
36
+
37
+ if s.respond_to? :specification_version then
38
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
39
+ s.specification_version = 3
40
+
41
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
42
+ s.add_runtime_dependency(%q<activerecord>, [">= 0"])
43
+ else
44
+ s.add_dependency(%q<activerecord>, [">= 0"])
45
+ end
46
+ else
47
+ s.add_dependency(%q<activerecord>, [">= 0"])
48
+ end
49
+ end
50
+
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'ar_after_transaction'
@@ -0,0 +1,62 @@
1
+ require 'active_record'
2
+
3
+ module ARAfterTransaction
4
+ VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+
9
+ class << base
10
+ alias_method_chain :transaction, :callbacks
11
+ end
12
+ end
13
+
14
+ module ClassMethods
15
+ @@after_transaction_hooks = []
16
+
17
+ def transaction_with_callbacks(&block)
18
+ clean = true
19
+ transaction_without_callbacks(&block)
20
+ rescue Exception
21
+ clean = false
22
+ raise
23
+ ensure
24
+ unless transactions_open?
25
+ execute_after_transaction_callbacks if clean
26
+ clear_transaction_callbacks
27
+ end
28
+ end
29
+
30
+ def after_transaction(&block)
31
+ if transactions_open?
32
+ @@after_transaction_hooks << block
33
+ else
34
+ yield
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def transactions_open?
41
+ connection.open_transactions > normally_open_transactions
42
+ end
43
+
44
+ def normally_open_transactions
45
+ Rails.env.test? ? 1 : 0
46
+ end
47
+
48
+ def execute_after_transaction_callbacks
49
+ @@after_transaction_hooks.each { |hook| hook.call }
50
+ end
51
+
52
+ def clear_transaction_callbacks
53
+ @@after_transaction_hooks.clear
54
+ end
55
+ end
56
+
57
+ def after_transaction(&block)
58
+ self.class.after_transaction(&block)
59
+ end
60
+ end
61
+
62
+ ActiveRecord::Base.send(:include, ARAfterTransaction)
@@ -0,0 +1,88 @@
1
+ require "spec/spec_helper"
2
+
3
+ class AnExpectedError < Exception
4
+ end
5
+
6
+ class User
7
+ cattr_accessor :test_callbacks, :test_stack
8
+ self.test_stack = []
9
+ self.test_callbacks = []
10
+
11
+ after_create :do_it
12
+ def do_it
13
+ self.class.test_callbacks.map{|callback| send(callback)}.last
14
+ end
15
+
16
+ def do_after
17
+ after_transaction do
18
+ self.class.test_stack << :after
19
+ end
20
+ end
21
+
22
+ def do_normal
23
+ self.class.test_stack << :normal
24
+ end
25
+
26
+ def oops
27
+ raise AnExpectedError
28
+ end
29
+ end
30
+
31
+ describe ARAfterTransaction do
32
+ before do
33
+ Rails.env = 'development'
34
+ User.send(:transactions_open?).should == false
35
+ User.test_stack.clear
36
+ User.test_callbacks.clear
37
+ end
38
+
39
+ it "has a VERSION" do
40
+ ARAfterTransaction::VERSION.should =~ /^\d+\.\d+\.\d+$/
41
+ end
42
+
43
+ it "executes after a transaction" do
44
+ User.test_callbacks = [:do_after, :do_normal]
45
+ User.create!
46
+ User.test_stack.should == [:normal, :after]
47
+ end
48
+
49
+ it "does not execute when transaction was rolled back" do
50
+ User.test_callbacks = [:do_after, :do_normal, :oops]
51
+ lambda{
52
+ User.create!
53
+ }.should raise_error(AnExpectedError)
54
+ User.test_stack.should == [:normal]
55
+ end
56
+
57
+ it "executes when no transaction is open" do
58
+ user = User.new
59
+ user.do_after
60
+ user.do_normal
61
+ User.test_stack.should == [:after, :normal]
62
+ end
63
+
64
+ it "executes when open transactions are normal" do
65
+ Rails.env = 'test'
66
+ User.test_callbacks = [:do_after, :do_normal]
67
+ User.create!
68
+ User.test_stack.should == [:after, :normal]
69
+ end
70
+
71
+ it "does not execute the same callback twice when successful" do
72
+ User.test_callbacks = [:do_after, :do_normal]
73
+ User.create!
74
+ User.create!
75
+ User.test_stack.should == [:normal, :after, :normal, :after]
76
+ end
77
+
78
+ it "does not execute the same callback twice when failed" do
79
+ User.test_callbacks = [:do_after, :do_normal, :oops]
80
+ lambda{
81
+ User.create!
82
+ }.should raise_error(AnExpectedError)
83
+ lambda{
84
+ User.create!
85
+ }.should raise_error(AnExpectedError)
86
+ User.test_stack.should == [:normal, :normal]
87
+ end
88
+ end
@@ -0,0 +1,18 @@
1
+ ActiveRecord::Base.establish_connection(
2
+ :adapter => "mysql", # need something that has transactions...
3
+ :database => "ar_after_transaction"
4
+ )
5
+
6
+ ActiveRecord::Base.connection.execute('drop table users')
7
+ ActiveRecord::Schema.define(:version => 1) do
8
+ create_table :users do |t|
9
+ t.string :name
10
+ t.timestamps
11
+ end
12
+ end
13
+
14
+ #require 'logger'
15
+ #ActiveRecord::Base.logger = Logger.new(STDOUT)
16
+
17
+ class User < ActiveRecord::Base
18
+ end
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ $LOAD_PATH << "lib"
3
+ require "init"
4
+ require "spec/setup_database"
5
+
6
+ class FakeEnv
7
+ def initialize(env)
8
+ @env = env
9
+ end
10
+
11
+ def test?
12
+ @env == 'test'
13
+ end
14
+ end
15
+
16
+ module Rails
17
+ def self.env
18
+ @@env
19
+ end
20
+
21
+ def self.env=(env)
22
+ @@env = FakeEnv.new(env)
23
+ end
24
+
25
+ self.env = 'development'
26
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ar_after_transaction
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Michael Grosser
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-08-28 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activerecord
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ description:
33
+ email: grosser.michael@gmail.com
34
+ executables: []
35
+
36
+ extensions: []
37
+
38
+ extra_rdoc_files: []
39
+
40
+ files:
41
+ - Gemfile
42
+ - Rakefile
43
+ - Readme.md
44
+ - VERSION
45
+ - ar_after_transaction.gemspec
46
+ - init.rb
47
+ - lib/ar_after_transaction.rb
48
+ - spec/ar_after_transaction_spec.rb
49
+ - spec/setup_database.rb
50
+ - spec/spec_helper.rb
51
+ has_rdoc: true
52
+ homepage: http://github.com/grosser/ar_after_transaction
53
+ licenses: []
54
+
55
+ post_install_message:
56
+ rdoc_options:
57
+ - --charset=UTF-8
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.3.6
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: Execute irreversible actions only when transactions are not rolled back
81
+ test_files:
82
+ - spec/setup_database.rb
83
+ - spec/spec_helper.rb
84
+ - spec/ar_after_transaction_spec.rb