sequel-activerecord_connection 0.2.0

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: 8c039d5e631eab07727b3ae6f451bc04656a5f5215a1c3389c64dead44a16f2e
4
+ data.tar.gz: 99402da40ca6bbbadc05dd1dfd24757368a6153a85922b705cd491c14ab54096
5
+ SHA512:
6
+ metadata.gz: 6312fad7666b13a9fb6d670ef5b1806edb4cda9843b008090f69a8f5472982673f1180a16b5e903f0e26eab7ef6cdba3d9d6a762024ef28842d17f61f9d5ac68
7
+ data.tar.gz: e80315a652b89d0cb13075dc72ade8d882f3a35038f998293bfbb8f3aeed8483457e80ed085fa850694e5694fbc10a7e6641ad35bd66f3df01bdb9b3e9dc21d8
@@ -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 when the extension(`: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,77 @@
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
+ activerecord_connection.lock.synchronize do
61
+ yield activerecord_raw_connection
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def activerecord_raw_connection
68
+ activerecord_connection.raw_connection
69
+ end
70
+
71
+ def activerecord_connection
72
+ activerecord_model.connection
73
+ end
74
+ end
75
+
76
+ Database.register_extension(:activerecord_connection, ActiveRecordConnection)
77
+ 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,40 @@
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
+ activerecord_raw_connection.extended_result_codes = true
14
+
15
+ activerecord_connection.materialize_transactions
16
+
17
+ activerecord_connection.send(:log, sql) do
18
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
19
+ case type
20
+ when :select
21
+ activerecord_raw_connection.query(sql, &block)
22
+ when :insert
23
+ activerecord_raw_connection.execute(sql)
24
+ activerecord_raw_connection.last_insert_row_id
25
+ when :update
26
+ activerecord_raw_connection.execute_batch(sql)
27
+ activerecord_raw_connection.changes
28
+ end
29
+ end
30
+ end
31
+ rescue ActiveRecord::StatementInvalid => exception
32
+ if exception.cause.is_a?(SQLite3::Exception)
33
+ raise_error(exception.cause)
34
+ else
35
+ raise exception
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "sequel-activerecord_connection"
3
+ spec.version = "0.2.0"
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", "~> 5.0"
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.4"
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,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sequel-activerecord_connection
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Janko Marohnić
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-04-29 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: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '7'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '5.0'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '7'
47
+ - !ruby/object:Gem::Dependency
48
+ name: pg
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: mysql2
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.5'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.5'
75
+ - !ruby/object:Gem::Dependency
76
+ name: sqlite3
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.4'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '1.4'
89
+ - !ruby/object:Gem::Dependency
90
+ name: minitest
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ description: Allows Sequel to use ActiveRecord connection for database interaction.
104
+ email:
105
+ - janko.marohnic@gmail.com
106
+ executables: []
107
+ extensions: []
108
+ extra_rdoc_files: []
109
+ files:
110
+ - LICENSE.txt
111
+ - README.md
112
+ - lib/sequel/extensions/activerecord_connection.rb
113
+ - lib/sequel/extensions/activerecord_connection/mysql2.rb
114
+ - lib/sequel/extensions/activerecord_connection/postgres.rb
115
+ - lib/sequel/extensions/activerecord_connection/sqlite.rb
116
+ - sequel-activerecord_connection.gemspec
117
+ homepage: https://github.com/janko/sequel-activerecord_connection
118
+ licenses:
119
+ - MIT
120
+ metadata: {}
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: 2.2.0
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubygems_version: 3.1.1
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: Allows Sequel to use ActiveRecord connection for database interaction.
140
+ test_files: []