sequel-activerecord_connection 0.2.6 → 0.3.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.
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