sequel-activerecord_connection 0.2.6 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95766ef8e2bf54388e4a139f430c42176760f2b77e0322902445414ffe3d7ddb
4
- data.tar.gz: 35a9fe4b35ff4037299799be9d902e93a29f9d81333ddf084374a4525761a2b1
3
+ metadata.gz: d4078a41f54331cbb97fc266abdbe24cf8c297be14bb7f92b3bf605bf9b549bd
4
+ data.tar.gz: af466cd9b062279a7781106136a05151f02339830469136cdf9b58d629397b98
5
5
  SHA512:
6
- metadata.gz: 93a612e4cfc3454783ba936a19518459fee958e53f8193e9f0561d65961008892a7b8c24a3189e5b2cc8620803e0da1375cd36f5c319af4adf7461656e2e462e
7
- data.tar.gz: 526c8b8ce56b481f5fa98ff1dbd242fc9104665dab6166bd4797514ae187bfe0e9460881dc9f6783e1216323a9a0d701949de2d1972865d464a9a13c51dd4c41
6
+ metadata.gz: e28881de46188d6dc143952a473e00254b0742c497e7888e8d334ff33b627d3d90d9174675809cc3248b15cfcc800908bc83dda5a8da3fee93c4048c44282424
7
+ data.tar.gz: 7c0a46d18a45e76ca4a7af11d467b957035dd4b62adbdcaf64d8e9cafe1bfb406fa40c3d4feadff10c823d2535cab978111203ade32ea519f991e828ed1df033
@@ -1,3 +1,7 @@
1
+ ## 0.3.0 (2020-07-24)
2
+
3
+ * Fully support Sequel transaction API (all transaction options, transaction/savepoint hooks etc.) (@janko)
4
+
1
5
  ## 0.2.6 (2020-07-19)
2
6
 
3
7
  * Return block result in `Sequel::Database#transaction` (@zabolotnov87, @janko)
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  This is an extension for [Sequel] that allows it to reuse an existing
4
4
  ActiveRecord connection for database interaction. It works on ActiveRecord 4.2
5
5
  or higher, and supports the built-in `postgresql`, `mysql2` and `sqlite3`
6
- adapters.
6
+ adapters, as well as JDBC adapter for JRuby.
7
7
 
8
8
  This can be useful if you're using a library that uses Sequel for database
9
9
  interaction (e.g. [Rodauth]), but you want to avoid creating a separate
@@ -11,15 +11,15 @@ database connection. Or if you're transitioning from ActiveRecord to Sequel,
11
11
  and want the database connection to be shared.
12
12
 
13
13
  Note that this is a best-effort implementation, so some discrepancies are still
14
- possible. That being said, this implementation passes [Rodauth]'s test suite
15
- (for all adapters), which has fairly advanced Sequel usage.
14
+ possible. That being said, this implementation passes Rodauth's test suite
15
+ (for all adapters), which has some fairly advanced Sequel usage.
16
16
 
17
17
  ## Installation
18
18
 
19
19
  Add this line to your application's Gemfile:
20
20
 
21
21
  ```ruby
22
- gem "sequel-activerecord_connection"
22
+ gem "sequel-activerecord_connection", "~> 0.3"
23
23
  ```
24
24
 
25
25
  And then execute:
@@ -80,65 +80,62 @@ DB = Sequel.postgres(test: false) # for "postgresql" adapter
80
80
  DB = Sequel.mysql2(test: false) # for "mysql2" adapter
81
81
  # or
82
82
  DB = Sequel.sqlite(test: false) # for "sqlite3" adapter
83
+ # or
84
+ DB = Sequel.jdbc(test: false) # for JDBC adapter
83
85
  ```
84
86
 
85
87
  ### Transactions
86
88
 
87
- The database extension overrides Sequel transactions to use ActiveRecord
88
- transcations, which allows using ActiveRecord inside Sequel transactions (and
89
- vice-versa), and have things like ActiveRecord's transactional callbacks still
90
- work correctly.
89
+ This database extension keeps the transaction state of Sequel and ActiveRecord
90
+ in sync, allowing you to use Sequel and ActiveRecord transactions
91
+ interchangeably (including nesting them), and have things like ActiveRecord's
92
+ and Sequel's transactional callbacks still work correctly.
91
93
 
92
94
  ```rb
93
- DB.transaction do
94
- ActiveRecord::Base.transaction do
95
- # this all works
96
- end
95
+ ActiveRecord::Base.transaction do
96
+ DB.in_transaction? #=> true
97
97
  end
98
98
  ```
99
99
 
100
- The following Sequel transaction options are currently supported:
101
-
102
- * `:savepoint`
103
- * `:auto_savepoint`
104
- * `:rollback`
100
+ Sequel's transaction API is fully supported:
105
101
 
106
102
  ```rb
107
- ActiveRecord::Base.transaction do
108
- DB.transaction(savepoint: true) do # will create a savepoint
109
- DB.transaction do # will not create a savepoint
110
- # ...
111
- end
103
+ DB.transaction(isolation: :serializable) do
104
+ DB.after_commit { ... } # call block after transaction commits
105
+ DB.transaction(savepoint: true) do # creates a savepoint
106
+ # ...
112
107
  end
113
108
  end
114
109
  ```
115
110
 
116
- The `#in_transaction?` method is supported as well:
111
+ One caveat to keep in mind is that Sequel's transaction hooks
112
+ (`after_commit`, `after_rollback`) will *not* run if ActiveRecord holds the
113
+ outer transaction:
117
114
 
118
115
  ```rb
119
- ActiveRecord::Base.transaction do
120
- DB.in_transaction? #=> true
116
+ DB.transaction do
117
+ DB.after_commit { ... } # will get executed
121
118
  end
122
- ```
123
119
 
124
- Other transaction-related Sequel methods (`#after_commit`, `#after_rollback`
125
- etc) are not supported, because ActiveRecord currently doesn't provide
126
- transactional callbacks on the connection level (only on the model level).
120
+ ActiveRecord::Base.transaction do
121
+ DB.after_commit { ... } # won't get executed
122
+ end
127
123
 
128
- ### Exceptions
124
+ ActiveRecord::Base.transaction do
125
+ DB.transaction do
126
+ DB.after_commit { ... } # won't get executed
127
+ end
128
+ end
129
+ ```
129
130
 
130
- To ensure Sequel compatibility, any `ActiveRecord::StatementInvalid` exceptions
131
- will be translated into Sequel exceptions:
131
+ Savepoint hooks should still work, though:
132
132
 
133
133
  ```rb
134
- DB[:posts].multi_insert [{ id: 1 }, { id: 1 }]
135
- #~> Sequel::UniqueConstraintViolation
136
-
137
- DB[:posts].insert(title: nil)
138
- #~> Sequel::NotNullConstraintViolation
139
-
140
- DB[:posts].insert(author_id: 123)
141
- #~> Sequel::ForeignKeyConstraintViolation
134
+ ActiveRecord::Base.transaction do
135
+ DB.transaction(savepoint: true) do
136
+ DB.after_commit { ... } # will get executed after savepoint is released
137
+ end
138
+ end
142
139
  ```
143
140
 
144
141
  ### Model
@@ -2,6 +2,13 @@ module Sequel
2
2
  module ActiveRecordConnection
3
3
  Error = Class.new(Sequel::Error)
4
4
 
5
+ TRANSACTION_ISOLATION_MAP = {
6
+ uncommitted: :read_uncommitted,
7
+ committed: :read_committed,
8
+ repeatable: :repeatable_read,
9
+ serializable: :serializable,
10
+ }
11
+
5
12
  def self.extended(db)
6
13
  db.activerecord_model = ActiveRecord::Base
7
14
  db.timezone = ActiveRecord::Base.default_timezone
@@ -21,36 +28,6 @@ module Sequel
21
28
  raise Error, "creating a Sequel connection is not allowed"
22
29
  end
23
30
 
24
- def transaction(options = {})
25
- %i[isolation num_retries before_retry prepare retry_on].each do |key|
26
- fail Error, "#{key.inspect} transaction option is currently not supported" if options.key?(key)
27
- end
28
-
29
- activerecord_model.transaction(requires_new: !in_transaction? || options[:savepoint] || Thread.current[:sequel_activerecord_auto_savepoint]) do
30
- begin
31
- Thread.current[:sequel_activerecord_auto_savepoint] = true if options[:auto_savepoint]
32
- result = yield
33
- raise ActiveRecord::Rollback if options[:rollback] == :always
34
- result
35
- rescue Sequel::Rollback => exception
36
- raise if options[:rollback] == :reraise
37
- raise ActiveRecord::Rollback, exception.message, exception.backtrace
38
- ensure
39
- Thread.current[:sequel_activerecord_auto_savepoint] = nil if options[:auto_savepoint]
40
- end
41
- end
42
- end
43
-
44
- def in_transaction?(*)
45
- activerecord_connection.transaction_open?
46
- end
47
-
48
- %i[after_commit after_rollback rollback_on_exit rollback_checker].each do |meth|
49
- define_method(meth) do |*|
50
- fail Error, "Database##{meth} is currently not supported"
51
- end
52
- end
53
-
54
31
  # Avoid calling Sequel's connection pool, instead use ActiveRecord.
55
32
  def synchronize(*)
56
33
  if ActiveRecord.version >= Gem::Version.new("5.1.0")
@@ -64,6 +41,53 @@ module Sequel
64
41
 
65
42
  private
66
43
 
44
+ # Backfills any ActiveRecord transactions/savepoints that have been opened
45
+ # directly via ActiveRecord::Base.transaction. Sequel uses this information
46
+ # to know whether we're in a transaction, whether to create a savepoint,
47
+ # when to run transaction/savepoint hooks etc.
48
+ def _trans(conn)
49
+ Sequel.synchronize do
50
+ result = @transactions[conn]
51
+
52
+ if activerecord_connection.transaction_open?
53
+ result ||= { savepoints: [] }
54
+ while result[:savepoints].length < activerecord_connection.open_transactions
55
+ result[:savepoints].unshift({ activerecord: true })
56
+ end
57
+ end
58
+
59
+ @transactions[conn] = result if result
60
+ result
61
+ end
62
+ end
63
+
64
+ # First delete any transactions/savepoints opened directly via
65
+ # ActiveRecord::Base.transaction, so that Sequel can detect when the last
66
+ # Sequel transaction has been closed and clear transaction information.
67
+ def transaction_finished?(conn)
68
+ _trans(conn)[:savepoints].shift while _trans(conn)[:savepoints].first[:activerecord]
69
+ super
70
+ end
71
+
72
+ def begin_transaction(conn, opts = {})
73
+ isolation = TRANSACTION_ISOLATION_MAP.fetch(opts[:isolation]) if opts[:isolation]
74
+
75
+ activerecord_connection.begin_transaction(isolation: isolation)
76
+ end
77
+
78
+ def commit_transaction(conn, opts = {})
79
+ activerecord_connection.commit_transaction
80
+ end
81
+
82
+ def rollback_transaction(conn, opts = {})
83
+ activerecord_connection.rollback_transaction
84
+ activerecord_connection.transaction_manager.send(:after_failure_actions, activerecord_connection.current_transaction, $!) if activerecord_connection.transaction_manager.respond_to?(:after_failure_actions)
85
+ end
86
+
87
+ def savepoint_level(conn)
88
+ activerecord_connection.open_transactions
89
+ end
90
+
67
91
  def activerecord_raw_connection
68
92
  activerecord_connection.raw_connection
69
93
  end
@@ -9,6 +9,8 @@ module Sequel
9
9
  else
10
10
  result.cmd_tuples
11
11
  end
12
+ rescue ActiveRecord::PreparedStatementCacheExpired
13
+ raise # ActiveRecord's transaction manager needs to handle this exception
12
14
  rescue ActiveRecord::StatementInvalid => exception
13
15
  raise_error(exception.cause, classes: database_error_classes)
14
16
  ensure
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "sequel-activerecord_connection"
3
- spec.version = "0.2.6"
3
+ spec.version = "0.3.0"
4
4
  spec.authors = ["Janko Marohnić"]
5
5
  spec.email = ["janko.marohnic@gmail.com"]
6
6
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel-activerecord_connection
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-19 00:00:00.000000000 Z
11
+ date: 2020-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel