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 +6 -0
- data/Rakefile +20 -0
- data/Readme.md +58 -0
- data/VERSION +1 -0
- data/ar_after_transaction.gemspec +50 -0
- data/init.rb +1 -0
- data/lib/ar_after_transaction.rb +62 -0
- data/spec/ar_after_transaction_spec.rb +88 -0
- data/spec/setup_database.rb +18 -0
- data/spec/spec_helper.rb +26 -0
- metadata +84 -0
data/Gemfile
ADDED
data/Rakefile
ADDED
|
@@ -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
|
data/Readme.md
ADDED
|
@@ -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
|
data/spec/spec_helper.rb
ADDED
|
@@ -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
|