activerecord-transactionable 0.1.0 → 0.1.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 134ddc5a2d594c0ddf785ff45ff5ec2661f0660e
4
- data.tar.gz: 3fb2afbaa552fbec81c70bb7824874646efacfde
3
+ metadata.gz: 303aa3641ecea594ad05589a09a3e9d6a12c31ba
4
+ data.tar.gz: af04e048d3dbd066d5bd5fe917f49a6a421eba53
5
5
  SHA512:
6
- metadata.gz: 78d23d187b037feb977342e6120d001c92699b7db37dfd4cb953bd4add4dcc91762f822c359ce841dd3725d6c6232fcfe375d4b2f99f61415a9fc9aabff9e5a7
7
- data.tar.gz: 71d8ae5296a058dca0ff90c746b15320824556c6fde806abcdf3e01e3556e7b8a4b1e09e438bec82473222c3b60631525e9b67632f275317122055b872b49590
6
+ metadata.gz: d6314257b6007bd563dd9dbb682cd1ffa415fe42e7edb337303aa5ae04156fc4977e4f3bb368151fd29675e2a9af7364cf2ebff2d21542b80a9a89ab9795801a
7
+ data.tar.gz: fbb91d3c932c6739a0847018ce6a227beb61845109c8133542de71b3fe59e1dc4cae55cf0825632163a8d623e1150f8d3ea788ece283e8ba8fa15de460107c72
data/README.md CHANGED
@@ -25,17 +25,64 @@ Or install it yourself as:
25
25
 
26
26
  ## Usage
27
27
 
28
+ ```
29
+ class Car < ActiveRecord::Base
30
+ include Activerecord::Transactionable # Note lowercase "r" in Activerecord (different namespace than rails' module)
31
+
32
+ validates_presence_of :name
33
+ end
34
+ ```
35
+
28
36
  When creating, saving, deleting within the transaction make sure to use the bang methods (`!`) in order to ensure a rollback on failure.
29
37
 
38
+ When everything works:
30
39
  ```
31
40
  car = Car.new(name: "Fiesta")
32
41
  car.transaction_wrapper do
33
42
  car.save!
34
43
  end
44
+ car.persisted? # => true
45
+ ```
46
+
47
+ When something goes wrong:
48
+ ```
49
+ car = Car.new(name: nil)
50
+ car.transaction_wrapper do
51
+ car.save!
52
+ end
53
+ car.persisted? # => false
54
+ car.errors.full_messages # => ["Name can't be blank"]
35
55
  ```
36
56
 
57
+ These examples are too simple to be useful with transactions, but if you are working with multiple records then it will make sense.
58
+
37
59
  Also see the specs.
38
60
 
61
+ If you need to lock the car as well as have a transaction (note: will reload the `car`):
62
+ ```
63
+ car = Car.new(name: nil)
64
+ car.transaction_wrapper(lock: true) do # uses ActiveRecord's with_lock
65
+ car.save!
66
+ end
67
+ car.persisted? # => false
68
+ car.errors.full_messages # => ["Name can't be blank"]
69
+ ```
70
+
71
+ If you need to know if the transaction succeeded:
72
+ ```
73
+ car = Car.new(name: nil)
74
+ result = car.transaction_wrapper(lock: true) do # uses ActiveRecord's with_lock
75
+ car.save!
76
+ end
77
+ result # => true, false or nil
78
+ ```
79
+
80
+ Meanings of `transaction_wrapper` return values:
81
+
82
+ **nil** - ActiveRecord::Rollback was raised, and then caught by the transaction, and not re-raised; the transaction failed.
83
+ **false** - An error was raised which was handled by the transaction_wrapper; the transaction failed.
84
+ **true** - The transaction was a success.
85
+
39
86
  ## Development
40
87
 
41
88
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -2,7 +2,7 @@ require "activerecord/transactionable/version"
2
2
  require "active_model"
3
3
  require "active_record"
4
4
 
5
- module Activerecord
5
+ module Activerecord # Note lowercase "r" in Activerecord (different namespace than rails' module)
6
6
  # SRP: Provides an example of correct behavior for wrapping transactions.
7
7
  # NOTE: Rails' transactions are per-database connection, not per-model, nor per-instance,
8
8
  # see: http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html
@@ -20,6 +20,34 @@ module Activerecord
20
20
 
21
21
  module ClassMethods
22
22
  def transaction_wrapper(object: nil, **args)
23
+ if object
24
+ if args[:lock]
25
+ # Note with_lock will reload object!
26
+ object.with_lock do
27
+ error_handler(object: object, **args) do
28
+ yield
29
+ end
30
+ end
31
+ else
32
+ object.transaction do
33
+ error_handler(object: object, **args) do
34
+ yield
35
+ end
36
+ end
37
+ end
38
+ else
39
+ raise ArgumentError, "No object to lock!" if args[:lock]
40
+ ActiveRecord::Base.transaction do
41
+ error_handler(object: object, **args) do
42
+ yield
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def error_handler(object: nil, **args)
23
51
  rescued_errors = Array(args[:rescued_errors])
24
52
  prepared_errors = Array(args[:prepared_errors])
25
53
  retriable_errors = Array(args[:retriable_errors])
@@ -29,10 +57,12 @@ module Activerecord
29
57
  already_been_added_to_self, needing_added_to_self = rescued_errors.partition {|error_class| prepared_errors.include?(error_class)}
30
58
  re_try = false
31
59
  begin
32
- ActiveRecord::Base.transaction do
33
- yield
34
- end
35
- true # <= make the return value meaningful. Meaning is either: transaction succeeded, OR raised ActiveRecord::Rollback
60
+ # If the block we yield to here raises an error that is not caught below the `true` will not get hit.
61
+ # If the error is rescued higher up, like where the transaction in active record
62
+ # rescues ActiveRecord::Rollback without re-raising, then transaction_wrapper will return nil
63
+ # If the error is not rescued higher up the error will continue to bubble
64
+ yield
65
+ true # <= make the return value meaningful. Meaning: transaction succeeded, no errors raised
36
66
  rescue *reraisable_errors => error
37
67
  # This has highest precedence because raising is the most critical functionality of a raised error to keep
38
68
  # if that is in the intended behavior, and this way a specific child of StandardError can be reraised while
@@ -1,5 +1,5 @@
1
1
  module Activerecord
2
2
  module Transactionable
3
- VERSION = "0.1.0"
3
+ VERSION = "0.1.1"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-transactionable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Boling
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-03-28 00:00:00.000000000 Z
11
+ date: 2016-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -118,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
118
118
  version: '0'
119
119
  requirements: []
120
120
  rubyforge_project:
121
- rubygems_version: 2.4.8
121
+ rubygems_version: 2.4.6
122
122
  signing_key:
123
123
  specification_version: 4
124
124
  summary: Do ActiveRecord transactions the right way.