sequel-activerecord_connection 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c11ff9b3633981f462112bbbb844ec6033522314b6849d66b1f1868976521549
4
+ data.tar.gz: 6ef2ebc50251b4e0f894d95e4953afbabd9b1a955a226809053a664788c94bc9
5
+ SHA512:
6
+ metadata.gz: 867693e62bde77afd9ff107f73670b488dbdd3f53ab1665da27a59d560d2737c045446e1d1995c49f9f700cc215303167404349c4c275597b030c5b65637c7e8
7
+ data.tar.gz: 0ceb8dc99f066df61850fdd596d3dbb5543fa437ffe428a3722d2d77a89e9227edccb57023d0d83834c1b9d846f2d06ccbac20c9904d19ba79c9db8470a5cbb8
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Janko Marohnić
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,202 @@
1
+ # Sequel::ActiveRecordConnection
2
+
3
+ This is an extension for [Sequel] that allows it to reuse an existing
4
+ ActiveRecord connection for database interaction. It supports `postgresql`,
5
+ `mysql2` and `sqlite3` adapters.
6
+
7
+ This can be useful if you're using a library that uses Sequel for database
8
+ interaction (e.g. [Rodauth]), but you want to avoid creating a separate
9
+ database connection. Or if you're transitioning from ActiveRecord to Sequel,
10
+ and want the database connection to be shared.
11
+
12
+ Note that this is a best-effort implementation, so some discrepancies are still
13
+ possible. That being said, this implementation passes [Rodauth]'s test suite
14
+ (for all adapters), which has fairly advanced Sequel usage.
15
+
16
+ ## Installation
17
+
18
+ Add this line to your application's Gemfile:
19
+
20
+ ```ruby
21
+ gem "sequel-activerecord_connection"
22
+ ```
23
+
24
+ And then execute:
25
+
26
+ ```sh
27
+ $ bundle install
28
+ ```
29
+
30
+ Or install it yourself as:
31
+
32
+ ```sh
33
+ $ gem install sequel-activerecord_connection
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ Assuming you've configured your ActiveRecord connection, you can initialize the
39
+ appropriate Sequel adapter and load the `activerecord_connection` extension:
40
+
41
+ ```rb
42
+ require "sequel"
43
+
44
+ DB = Sequel.postgres(test: false) # avoid creating a connection
45
+ DB.extension :activerecord_connection
46
+ ```
47
+
48
+ Now any Sequel operations that you make will internaly be done using the
49
+ ActiveRecord connection, so you should see the queries in your ActiveRecord
50
+ logs.
51
+
52
+ ```rb
53
+ DB.create_table :posts do
54
+ primary_key :id
55
+ String :title, null: false
56
+ Stirng :body, null: false
57
+ end
58
+
59
+ DB[:posts].insert(
60
+ title: "Sequel::ActiveRecordConnection",
61
+ body: "Allows Sequel to reuse ActiveRecord's connection",
62
+ )
63
+ #=> 1
64
+
65
+ DB[:posts].all
66
+ #=> [{ title: "Sequel::ActiveRecordConnection", body: "Allows Sequel to reuse ActiveRecord's connection" }]
67
+
68
+ DB[:posts].update(title: "sequel-activerecord_connection")
69
+ #=> 1
70
+ ```
71
+
72
+ The database extension supports `postgresql`, `mysql2` and `sqlite3`
73
+ ActiveRecord adapters, just make sure to initialize the corresponding Sequel
74
+ adapter before loading the extension.
75
+
76
+ ```rb
77
+ DB = Sequel.postgres(test: false) # for "postgresql" adapter
78
+ # or
79
+ DB = Sequel.mysql2(test: false) # for "mysql2" adapter
80
+ # or
81
+ DB = Sequel.sqlite(test: false) # for "sqlite3" adapter
82
+ ```
83
+
84
+ ### Transactions
85
+
86
+ The database extension overrides Sequel transactions to use ActiveRecord
87
+ transcations, which allows using ActiveRecord inside Sequel transactions (and
88
+ vice-versa), and have things like ActiveRecord's transactional callbacks still
89
+ work correctly.
90
+
91
+ ```rb
92
+ DB.transaction do
93
+ ActiveRecord::Base.transaction do
94
+ # this all works
95
+ end
96
+ end
97
+ ```
98
+
99
+ The following Sequel transaction options are currently supported:
100
+
101
+ * `:savepoint`
102
+ * `:auto_savepoint`
103
+ * `:rollback`
104
+
105
+ ```rb
106
+ ActiveRecord::Base.transaction do
107
+ DB.transaction(savepoint: true) do # will create a savepoint
108
+ DB.transaction do # will not create a savepoint
109
+ # ...
110
+ end
111
+ end
112
+ end
113
+ ```
114
+
115
+ The `#in_transaction?` method is supported as well:
116
+
117
+ ```rb
118
+ ActiveRecord::Base.transaction do
119
+ DB.in_transaction? #=> true
120
+ end
121
+ ```
122
+
123
+ Other transaction-related Sequel methods (`#after_commit`, `#after_rollback`
124
+ etc) are not supported, because ActiveRecord currently doesn't provide
125
+ transactional callbacks on the connection level (only on the model level).
126
+
127
+ ### Exceptions
128
+
129
+ To ensure Sequel compatibility, any `ActiveRecord::StatementInvalid` exceptions
130
+ will be translated into Sequel exceptions:
131
+
132
+ ```rb
133
+ DB[:posts].multi_insert [{ id: 1 }, { id: 1 }]
134
+ #~> Sequel::UniqueConstraintViolation
135
+
136
+ DB[:posts].insert(title: nil)
137
+ #~> Sequel::NotNullConstraintViolation
138
+
139
+ DB[:posts].insert(author_id: 123)
140
+ #~> Sequel::ForeignKeyConstraintViolation
141
+ ```
142
+
143
+ ### Model
144
+
145
+ By default, the connection configuration will be read from `ActiveRecord::Base`.
146
+ If you want to use connection configuration from a different model, you can
147
+ can assign it to the database object after loading the extension:
148
+
149
+ ```rb
150
+ class MyModel < ActiveRecord::Base
151
+ connects_to database: { writing: :animals, reading: :animals_replica }
152
+ end
153
+ ```
154
+ ```rb
155
+ DB.activerecord_model = MyModel
156
+ ```
157
+
158
+ ### Timezone
159
+
160
+ Sequel's database timezone will be automatically set to ActiveRecord's default
161
+ timezone (`:utc` by default) when the extension is loaded.
162
+
163
+ If you happen to be changing ActiveRecord's default timezone after you've
164
+ loaded the extension, make sure to reflect that in your Sequel database object,
165
+ for example:
166
+
167
+ ```rb
168
+ DB.timezone = :local
169
+ ```
170
+
171
+ ## Tests
172
+
173
+ You'll first want to run the rake tasks for setting up databases and users:
174
+
175
+ ```sh
176
+ $ rake db_setup_postgres
177
+ $ rake db_setup_mysql
178
+ ```
179
+
180
+ Then you can run the tests:
181
+
182
+ ```sh
183
+ $ rake test
184
+ ```
185
+
186
+ When you're done, you can delete the created databases and users:
187
+
188
+ ```sh
189
+ $ rake db_teardown_postgres
190
+ $ rake db_teardown_mysql
191
+ ```
192
+
193
+ ## License
194
+
195
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
196
+
197
+ ## Code of Conduct
198
+
199
+ Everyone interacting in this project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/janko/sequel-activerecord-adapter/blob/master/CODE_OF_CONDUCT.md).
200
+
201
+ [Sequel]: https://github.com/jeremyevans/sequel
202
+ [Rodauth]: https://github.com/jeremyevans/rodauth
@@ -0,0 +1,81 @@
1
+ module Sequel
2
+ module ActiveRecordConnection
3
+ Error = Class.new(Sequel::Error)
4
+
5
+ def self.extended(db)
6
+ db.activerecord_model = ActiveRecord::Base
7
+ db.timezone = ActiveRecord::Base.default_timezone
8
+
9
+ begin
10
+ require "sequel/extensions/activerecord_connection/#{db.adapter_scheme}"
11
+ db.extend Sequel::ActiveRecordConnection.const_get(db.adapter_scheme.capitalize)
12
+ rescue LoadError
13
+ fail Error, "unsupported adapter: #{db.adapter_scheme}"
14
+ end
15
+ end
16
+
17
+ attr_accessor :activerecord_model
18
+
19
+ # Ensure Sequel is not creating its own connection anywhere.
20
+ def connect(*)
21
+ raise Error, "creating a Sequel connection is not allowed"
22
+ end
23
+
24
+ def transaction(savepoint: nil, rollback: nil, auto_savepoint: nil, server: nil, **options)
25
+ fail Error, "#{options} transaction options are currently not supported" unless options.empty?
26
+
27
+ if in_transaction?
28
+ requires_new = savepoint || Thread.current[:sequel_activerecord_auto_savepoint]
29
+ else
30
+ requires_new = true
31
+ end
32
+
33
+ activerecord_model.transaction(requires_new: requires_new) do
34
+ begin
35
+ Thread.current[:sequel_activerecord_auto_savepoint] = true if auto_savepoint
36
+ yield
37
+ rescue Sequel::Rollback => exception
38
+ raise if rollback == :reraise
39
+ raise ActiveRecord::Rollback, exception.message, exception.backtrace
40
+ ensure
41
+ Thread.current[:sequel_activerecord_auto_savepoint] = nil if auto_savepoint
42
+ end
43
+
44
+ raise ActiveRecord::Rollback if rollback == :always
45
+ end
46
+ end
47
+
48
+ def in_transaction?(*)
49
+ activerecord_connection.transaction_open?
50
+ end
51
+
52
+ %i[after_commit after_rollback rollback_on_exit rollback_checker].each do |meth|
53
+ define_method(meth) do |*|
54
+ fail Error, "Database##{meth} is currently not supported"
55
+ end
56
+ end
57
+
58
+ # Avoid calling Sequel's connection pool, instead use ActiveRecord.
59
+ def synchronize(*)
60
+ if ActiveRecord.version >= Gem::Version.new("5.1.0")
61
+ activerecord_connection.lock.synchronize do
62
+ yield activerecord_raw_connection
63
+ end
64
+ else
65
+ yield activerecord_raw_connection
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def activerecord_raw_connection
72
+ activerecord_connection.raw_connection
73
+ end
74
+
75
+ def activerecord_connection
76
+ activerecord_model.connection
77
+ end
78
+ end
79
+
80
+ Database.register_extension(:activerecord_connection, ActiveRecordConnection)
81
+ end
@@ -0,0 +1,35 @@
1
+ module Sequel
2
+ module ActiveRecordConnection
3
+ module Mysql2
4
+ def execute(sql, opts=OPTS)
5
+ original_query_options = activerecord_raw_connection.query_options.dup
6
+
7
+ activerecord_raw_connection.query_options.merge!(
8
+ as: :hash,
9
+ symbolize_keys: true,
10
+ cache_rows: false,
11
+ )
12
+
13
+ result = activerecord_connection.execute(sql)
14
+
15
+ if opts[:type] == :select
16
+ if block_given?
17
+ yield result
18
+ else
19
+ result
20
+ end
21
+ elsif block_given?
22
+ yield activerecord_raw_connection
23
+ end
24
+ rescue ActiveRecord::StatementInvalid => exception
25
+ if exception.cause.is_a?(::Mysql2::Error)
26
+ raise_error(exception.cause)
27
+ else
28
+ raise
29
+ end
30
+ ensure
31
+ activerecord_raw_connection.query_options.replace(original_query_options)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,19 @@
1
+ module Sequel
2
+ module ActiveRecordConnection
3
+ module Postgres
4
+ def execute(sql, opts=OPTS)
5
+ result = activerecord_connection.execute(sql)
6
+
7
+ if block_given?
8
+ yield result
9
+ else
10
+ result.cmd_tuples
11
+ end
12
+ rescue ActiveRecord::StatementInvalid => exception
13
+ raise_error(exception.cause, classes: database_error_classes)
14
+ ensure
15
+ result.clear if result
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,44 @@
1
+ module Sequel
2
+ module ActiveRecordConnection
3
+ module Sqlite
4
+ def execute_ddl(sql, opts=OPTS)
5
+ execute(sql, opts)
6
+ end
7
+
8
+ private
9
+
10
+ # ActiveRecord doesn't send SQLite methods Sequel expects, so we need to
11
+ # try to replicate what ActiveRecord does around connection excecution.
12
+ def _execute(type, sql, opts, &block)
13
+ if activerecord_raw_connection.respond_to?(:extended_result_codes=)
14
+ activerecord_raw_connection.extended_result_codes = true
15
+ end
16
+
17
+ if ActiveRecord::VERSION::MAJOR >= 6
18
+ activerecord_connection.materialize_transactions
19
+ end
20
+
21
+ activerecord_connection.send(:log, sql) do
22
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
23
+ case type
24
+ when :select
25
+ activerecord_raw_connection.query(sql, &block)
26
+ when :insert
27
+ activerecord_raw_connection.execute(sql)
28
+ activerecord_raw_connection.last_insert_row_id
29
+ when :update
30
+ activerecord_raw_connection.execute_batch(sql)
31
+ activerecord_raw_connection.changes
32
+ end
33
+ end
34
+ end
35
+ rescue ActiveRecord::StatementInvalid => exception
36
+ if exception.cause.is_a?(SQLite3::Exception)
37
+ raise_error(exception.cause)
38
+ else
39
+ raise exception
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "sequel-activerecord_connection"
3
+ spec.version = "0.2.1"
4
+ spec.authors = ["Janko Marohnić"]
5
+ spec.email = ["janko.marohnic@gmail.com"]
6
+
7
+ spec.summary = %q{Allows Sequel to use ActiveRecord connection for database interaction.}
8
+ spec.description = %q{Allows Sequel to use ActiveRecord connection for database interaction.}
9
+ spec.homepage = "https://github.com/janko/sequel-activerecord_connection"
10
+ spec.license = "MIT"
11
+
12
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.2.0")
13
+
14
+ spec.add_dependency "sequel", ">= 4.0", "< 6"
15
+ spec.add_dependency "activerecord", ">= 5.0", "< 7"
16
+
17
+ spec.add_development_dependency "pg", "~> 1.0"
18
+ spec.add_development_dependency "mysql2", "~> 0.5"
19
+ spec.add_development_dependency "sqlite3", "~> 1.3"
20
+ spec.add_development_dependency "minitest"
21
+
22
+ spec.files = Dir["README.md", "LICENSE.txt", "CHANGELOG.md", "lib/**/*.rb", "*.gemspec"]
23
+ spec.require_paths = ["lib"]
24
+ end
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sequel-activerecord_connection
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Janko Marohnić
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-05-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sequel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '6'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '4.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '6'
33
+ - !ruby/object:Gem::Dependency
34
+ name: activerecord
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '5.0'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '7'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '5.0'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '7'
53
+ - !ruby/object:Gem::Dependency
54
+ name: pg
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '1.0'
60
+ type: :development
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '1.0'
67
+ - !ruby/object:Gem::Dependency
68
+ name: mysql2
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '0.5'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '0.5'
81
+ - !ruby/object:Gem::Dependency
82
+ name: sqlite3
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '1.3'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '1.3'
95
+ - !ruby/object:Gem::Dependency
96
+ name: minitest
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ description: Allows Sequel to use ActiveRecord connection for database interaction.
110
+ email:
111
+ - janko.marohnic@gmail.com
112
+ executables: []
113
+ extensions: []
114
+ extra_rdoc_files: []
115
+ files:
116
+ - LICENSE.txt
117
+ - README.md
118
+ - lib/sequel/extensions/activerecord_connection.rb
119
+ - lib/sequel/extensions/activerecord_connection/mysql2.rb
120
+ - lib/sequel/extensions/activerecord_connection/postgres.rb
121
+ - lib/sequel/extensions/activerecord_connection/sqlite.rb
122
+ - sequel-activerecord_connection.gemspec
123
+ homepage: https://github.com/janko/sequel-activerecord_connection
124
+ licenses:
125
+ - MIT
126
+ metadata: {}
127
+ post_install_message:
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: 2.2.0
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubygems_version: 3.1.1
143
+ signing_key:
144
+ specification_version: 4
145
+ summary: Allows Sequel to use ActiveRecord connection for database interaction.
146
+ test_files: []