• Stars
    star
    121
  • Rank 293,924 (Top 6 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 4 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Allows Sequel to reuse Active Record's database connection

sequel-activerecord_connection

This is a database extension for Sequel that makes it to reuse an existing Active Record connection for database interaction.

This can be useful if you want to use a library that uses Sequel (e.g. Rodauth or rom-sql), or you're transitioning from Active Record to Sequel, or if you just want to use Sequel for more complex queries, and you want to avoid creating new database connections.

It works on ActiveRecord 4.2+ and fully supports PostgresSQL, MySQL and SQLite, both the native adapters and JDBC (JRuby). The SQL Server external adapter is supported as well (tinytds in Sequel), and there is attempted support for Oracle enhanced (oracle and in Sequel). Other adapters might work too, but their integration hasn't been tested.

Why reuse the database connection?

At first it might appear that, as long as you're fine with the performance impact of your database server having to maintain additional open connections, it would be fine if Sequel had its own database connection. However, there are additional caveats when you try to combine it with Active Record.

If Sequel and Active Record each have their own connections, then it's not possible to combine their transactions. If we executed a Sequel query inside of an Active Record transaction, that query won't actually be executed inside a database transaction. This is because transactions are tied to the database connection; if one connection opens a transaction, this doesn't affect queries executed on a different connection, even if both connections are used in the same ruby process. With this library, transactions and queries can be seamlessly combined between Active Record and Sequel.

In Rails context, there are additional considerations for a Sequel connection to play nicely. Connecting and disconnecting would have to go in lockstep with Active Record, to make commands such as rails db:create and rails db:drop work. You'd also need to find a way for system tests and the app running in the background to share the same database connection, which is something Sequel wasn't designed for. Reusing Active Record's connection means (dis)connecting and sharing between threads is all handled automatically.

Framework Agnostic

The only hard dependencies are:

...which means you can use it with any Rack / Ruby based framework: Rails / Roda / Sinatra etc. or even without a framework.

Installation

Add the gem to your project:

$ bundle add sequel-activerecord_connection

Usage

Assuming you've configured your ActiveRecord connection, you can initialize the appropriate Sequel adapter and load the activerecord_connection extension: e.g.

# Place in relevant initializer
# e.g. Rails: config/initializers/sequel.rb

require "sequel"
DB = Sequel.postgres(extensions: :activerecord_connection) # postgres

Now any Sequel operations that you make will internaly be done using the ActiveRecord connection, so you should see the queries in your ActiveRecord logs.

DB.create_table :posts do
  primary_key :id
  String :title, null: false
  Stirng :body, null: false
end

DB[:posts].insert(
  title: "Sequel::ActiveRecordConnection",
  body:  "Allows Sequel to reuse ActiveRecord's connection",
)
#=> 1

DB[:posts].all
#=> [{ title: "Sequel::ActiveRecordConnection", body: "Allows Sequel to reuse ActiveRecord's connection" }]

DB[:posts].update(title: "sequel-activerecord_connection")
#=> 1

The database extension supports postgresql, mysql2 and sqlite3 ActiveRecord adapters, just make sure to initialize the corresponding Sequel adapter before loading the extension.

Sequel.postgres(extensions: :activerecord_connection) # for "postgresql" adapter
Sequel.mysql2(extensions: :activerecord_connection)   # for "mysql2" adapter
Sequel.sqlite(extensions: :activerecord_connection)   # for "sqlite3" adapter

If you're on JRuby, you should be using the JDBC adapters:

Sequel.connect("jdbc:postgresql://", extensions: :activerecord_connection) # for "jdbcpostgresql" adapter
Sequel.connect("jdbc:mysql://", extensions: :activerecord_connection)      # for "jdbcmysql" adapter
Sequel.connect("jdbc:sqlite://", extensions: :activerecord_connection)     # for "jdbcsqlite3" adapter

Transactions

This database extension keeps the transaction state of Sequel and ActiveRecord in sync, allowing you to use Sequel and ActiveRecord transactions interchangeably (including nesting them), and have things like ActiveRecord's and Sequel's transactional callbacks still work correctly.

ActiveRecord::Base.transaction do
  DB.in_transaction? #=> true
end

Sequel's transaction API is fully supported:

DB.transaction(isolation: :serializable) do
  DB.after_commit { ... } # executed after transaction commits
  DB.transaction(savepoint: true) do # creates a savepoint
    DB.after_commit(savepoint: true) { ... } # executed if all enclosing savepoints have been released
  end
end

When registering transaction hooks, they will be registered on Sequel transactions when possible, in which case they will behave as described in the Sequel docs.

# Sequel: An after_commit transaction hook will always get executed if the outer
# transaction commits, even if it's added inside a savepoint that's rolled back.
DB.transaction do
  ActiveRecord::Base.transaction(requires_new: true) do
    DB.after_commit { puts "after commit" }
    raise ActiveRecord::Rollback
  end
end
#>> BEGIN
#>> SAVEPOINT active_record_1
#>> ROLLBACK TO SAVEPOINT active_record_1
#>> COMMIT
#>> after commit

# Sequel: An after_commit savepoint hook will get executed only after the outer
# transaction commits, given that all enclosing savepoints have been released.
DB.transaction(auto_savepoint: true) do
  DB.transaction do
    DB.after_commit(savepoint: true) { puts "after commit" }
    raise Sequel::Rollback
  end
end
#>> BEGIN
#>> SAVEPOINT active_record_1
#>> ROLLBACK TO SAVEPOINT active_record_1
#>> COMMIT

In case of (a) adding a transaction hook while Active Record holds the transaction, or (b) adding a savepoint hook when Active Record holds any enclosing savepoint, Active Record transaction callbacks will be used instead of Sequel hooks, which have slightly different behaviour in some circumstances.

# ActiveRecord: An after_commit transaction callback is not executed if any
# if the enclosing savepoints have been rolled back
ActiveRecord::Base.transaction do
  DB.transaction(savepoint: true) do
    DB.after_commit { puts "after commit" }
    raise Sequel::Rollback
  end
end
#>> BEGIN
#>> SAVEPOINT active_record_1
#>> ROLLBACK TO SAVEPOINT active_record_1
#>> COMMIT

# ActiveRecord: An after_commit transaction callback can be executed already
# after a savepoint is released, if the enclosing transaction is not joinable.
ActiveRecord::Base.transaction(joinable: false) do
  DB.transaction do
    DB.after_commit { puts "after savepoint release" }
  end
end
#>> BEGIN
#>> SAVEPOINT active_record_1
#>> RELEASE SAVEPOINT active_record_1
#>> after savepoint release
#>> COMMIT

Model

By default, the connection configuration will be read from ActiveRecord::Base. If you want to use connection configuration from a different model, you can can assign it to the database object after loading the extension:

class MyModel < ActiveRecord::Base
  connects_to database: { writing: :animals, reading: :animals_replica }
end
DB.activerecord_model = MyModel

Normalizing SQL logs

Active Record injects values into queries using bound variables, and displays them at the end of SQL logs:

SELECT accounts.* FROM accounts WHERE accounts.email = $1 LIMIT $2  [["email", "[email protected]"], ["LIMIT", 1]]

Sequel interpolates values into its queries, so by default its SQL logs include them inline:

SELECT accounts.* FROM accounts WHERE accounts.email = '[email protected]' LIMIT 1

If you want to normalize logs to group similar queries, or you want to protect sensitive data from being stored in the logs, you can use the sql_log_normalizer extension to remove literal strings and numbers from logged SQL queries:

Sequel.postgres(extensions: [:activerecord_connection, :sql_log_normalizer])
SELECT accounts.* FROM accounts WHERE accounts.email = ? LIMIT ?

Note that the sql_log_normalizer extension opens a database connection while it's being loaded. If you're setting up Sequel in a Rails initializer, you'll probably want to handle the database not existing, so that commands such as rails db:create continue to work.

DB = Sequel.postgres(extensions: :activerecord_connection)
begin
  DB.extension :sql_log_normalizer
rescue ActiveRecord::NoDatabaseError
end

Tests

You'll first want to run the rake tasks for setting up databases and users:

$ rake db_setup_postgres
$ rake db_setup_mysql

Then you can run the tests:

$ rake test

When you're done, you can delete the created databases and users:

$ rake db_teardown_postgres
$ rake db_teardown_mysql

Support

Please feel free to raise a new disucssion in Github issues, or search amongst the existing questions there.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in this project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

More Repositories

1

down

Streaming downloads using Net::HTTP, http.rb or HTTPX
Ruby
1,024
star
2

image_processing

High-level image processing wrapper for libvips and ImageMagick/GraphicsMagick
Ruby
858
star
3

rodauth-rails

Rails integration for Rodauth authentication framework
HTML
424
star
4

tus-ruby-server

Ruby server for tus resumable upload protocol
Ruby
215
star
5

paperclip-dropbox

[OBSOLETE] Extends Paperclip with Dropbox storage.
Ruby
148
star
6

as-duration

Extraction of ActiveSupport::Duration from Rails
Ruby
125
star
7

uppy-s3_multipart

Provides Ruby endpoints for aws-s3-multipart Uppy plugin
Ruby
63
star
8

tic-tac-toe

Play tic-tac-toe in your Terminal
Ruby
43
star
9

rodauth-demo-rails

Example Rails app that uses Rodauth for authentication
Ruby
36
star
10

rodauth-omniauth

OmniAuth login and registration for Rodauth authentication framework
Ruby
34
star
11

flickr-objects

An object-oriented wrapper for the Flickr API.
Ruby
28
star
12

shrine-example

[MOVED] Roda & Sequel demo for Shrine
JavaScript
19
star
13

rodauth-i18n

I18n integration and translations for Rodauth authentication framework
Ruby
16
star
14

flickrie

[DEPRECATED] A wrapper gem for the Flickr API
Ruby
14
star
15

rodauth-model

Password attribute and associations for Rodauth account model
Ruby
13
star
16

rodauth-pwned

Rodauth extension that checks user passwords against the Pwned Passwords API
Ruby
12
star
17

goliath-rack_proxy

[DEPRECATED] Goliath as a web server for your Rack app
Ruby
11
star
18

budget

Roda & Sequel app for tracking expenses
Ruby
8
star
19

janko.io

My blog
HTML
7
star
20

dotfiles

My dotfiles
Vim Script
6
star
21

hanami-rodauth-example

Example Hanami 2 application using Rodauth authentication framework
Ruby
6
star
22

musique

A Ruby gem for manipulating musical constructs.
Ruby
6
star
23

capybara-vs-webdriverio

Battle between Capybara and Webdriver.io for browser testing
JavaScript
5
star
24

flickr-login

A gem that provides Flickr authentication.
Ruby
2
star
25

roda-symbolized_params

A Roda plugin which symbolizes request params
Ruby
2
star
26

shrine-example-melbourne

Shrine tutorial application for Coder Factory Academy
Ruby
1
star
27

refactoring_practice

Ruby
1
star
28

sequel-jsonapi_eager

DEPRECATED in favor of `tactical_eager_loading` Sequel plugin
Ruby
1
star
29

rodauth-guest

Guest accounts for Rodauth authentication framework
Ruby
1
star