sequel-connection_guard 0.1.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 +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +24 -0
- data/.travis.yml +20 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.md +162 -0
- data/Rakefile +8 -0
- data/bin/console +10 -0
- data/bin/setup +7 -0
- data/lib/sequel/extensions/connection_guard.rb +80 -0
- data/lib/sequel/extensions/connection_guard/configuration_error.rb +9 -0
- data/lib/sequel/extensions/connection_guard/connection_guard.rb +67 -0
- data/lib/sequel/extensions/connection_guard/database_guard.rb +98 -0
- data/lib/sequel/extensions/connection_guard/dataset.rb +25 -0
- data/lib/sequel/extensions/connection_guard/executor.rb +60 -0
- data/lib/sequel/extensions/connection_guard/model_guard.rb +105 -0
- data/sequel-connection_guard.gemspec +36 -0
- metadata +202 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9deb4fcabe84e3c610ee8041ab0eaa63633f2d7525f47324e9bb2e99bc6de84e
|
4
|
+
data.tar.gz: d30029a4edbd19ee0427f09018fa3791e55fa4f6e9d6d38d14f3e6cee52ab4cf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3f8fa9c222c2169cef6308dca198b6a405133990576c273ff1a4a38be299aea4a035f1fb56820a202cf0a5aed08ffaddab484ceb7f2520affc0ee07a68fcb8d8
|
7
|
+
data.tar.gz: 4469478b2db4f9cce22f5cb30a7db36d5edaff74871f103da1a6ec0d8bd4142afb7c6992a5afba68f27fcc6f17d9a8d131f8d9bfa46000e17a79e8ae02483669
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
inherit_gem:
|
2
|
+
rubocop-config-umbrellio: lib/rubocop.yml
|
3
|
+
armitage-rubocop:
|
4
|
+
- lib/rubocop.general.yml
|
5
|
+
- lib/rubocop.rspec.yml
|
6
|
+
|
7
|
+
AllCops:
|
8
|
+
TargetRubyVersion: 2.6.3
|
9
|
+
Include:
|
10
|
+
- sequel-connection_guard.gemspec
|
11
|
+
- lib/**/*.rb
|
12
|
+
- spec/**/*.rb
|
13
|
+
- Gemfile
|
14
|
+
- Rakefile
|
15
|
+
- bin/console
|
16
|
+
|
17
|
+
Style/TrailingCommaInArguments:
|
18
|
+
EnforcedStyleForMultiline: comma
|
19
|
+
|
20
|
+
Style/TrailingCommaInArrayLiteral:
|
21
|
+
EnforcedStyleForMultiline: comma
|
22
|
+
|
23
|
+
Style/TrailingCommaInHashLiteral:
|
24
|
+
EnforcedStyleForMultiline: comma
|
data/.travis.yml
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
sudo: false
|
2
|
+
language: ruby
|
3
|
+
rvm:
|
4
|
+
- 2.5.1
|
5
|
+
- 2.6
|
6
|
+
- ruby-head
|
7
|
+
|
8
|
+
services:
|
9
|
+
- postgresql
|
10
|
+
addons:
|
11
|
+
postgresql: 9.6
|
12
|
+
|
13
|
+
before_install: gem install bundler
|
14
|
+
before_script:
|
15
|
+
- psql -c 'create database sequel_connection_guard;' -U postgres
|
16
|
+
script:
|
17
|
+
- bundle exec rspec
|
18
|
+
- bundle exec rubocop
|
19
|
+
|
20
|
+
cache: bundler
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 Alexander Komarov
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
# sequel-connection_guard
|
2
|
+
[](https://travis-ci.org/umbrellio/sequel-connection_guard)
|
3
|
+
[](https://coveralls.io/github/umbrellio/sequel-connection_guard?branch=master)
|
4
|
+
[](https://badge.fury.io/rb/sequel-connection_guard)
|
5
|
+
|
6
|
+
This Sequel extension provides a set of abstractions for working with databases that might not be
|
7
|
+
reachable at any given moment in time.
|
8
|
+
|
9
|
+
**This gem was only tested against PostgreSQL databases.**
|
10
|
+
|
11
|
+
Goals:
|
12
|
+
- Allow to bootstrap an application when a database server is down
|
13
|
+
- Allow to safely and explicitly access a database
|
14
|
+
- In case connection fails, retry on next attempt
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem 'sequel-connection_guard'
|
22
|
+
```
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
```sh
|
26
|
+
$ bundle
|
27
|
+
```
|
28
|
+
|
29
|
+
Enable the extension:
|
30
|
+
```ruby
|
31
|
+
Sequel.extension :connection_guard
|
32
|
+
```
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
36
|
+
This extension provides two main abstractions for accessing unreliable databases. These are almost
|
37
|
+
identical, but one allows you to reach a database handle (instance of `Sequel::Database`) and
|
38
|
+
another allows you to reach a Sequel model (instance of `Sequel::Model`).
|
39
|
+
|
40
|
+
### Database guard
|
41
|
+
|
42
|
+
A database guard is what you use to access a database handle. First, you need to instantiate one:
|
43
|
+
```ruby
|
44
|
+
::DB = Sequel::DatabaseGuard.new('postgres://localhost/mydb')
|
45
|
+
```
|
46
|
+
|
47
|
+
You can perform additional actions upon DB initialization, such as enabling Sequel plugins:
|
48
|
+
```ruby
|
49
|
+
::DB = Sequel::DatabaseGuard.new('postgres://localhost/mydb') do |db|
|
50
|
+
db.extension :advisory_locking
|
51
|
+
db.extension :pg_json
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
There are two ways of using the guard.
|
56
|
+
|
57
|
+
#### Safe access
|
58
|
+
|
59
|
+
You can safely access the database handle by using `#safe_execute`:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
users = DB.safe_execute do
|
63
|
+
# if the database is reachable
|
64
|
+
alive do |db|
|
65
|
+
db[:users].all
|
66
|
+
end
|
67
|
+
|
68
|
+
# if the database could not be reached. NOTE: this is optional
|
69
|
+
dead do
|
70
|
+
[]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
#### Unsafe access
|
76
|
+
|
77
|
+
When you don't care about safety (or you're already inside a `safe_execute` context), use
|
78
|
+
`#force_execute`:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
users = DB.force_execute { |db| db[:users].all }
|
82
|
+
```
|
83
|
+
|
84
|
+
#### Accessing a raw database handle
|
85
|
+
|
86
|
+
Sometimes it's necessary to get access to a raw instance of `Sequel::Database` (for example, when
|
87
|
+
using the `database_cleaner` gem). You can get a raw handle like this:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
DB.raw_handle
|
91
|
+
```
|
92
|
+
|
93
|
+
Beware that this will raise `Sequel::DatabaseConnectionError` if the database is currently
|
94
|
+
unreachable.
|
95
|
+
|
96
|
+
### Model guard
|
97
|
+
|
98
|
+
A model guard is what you use to access a model handle. To create a model guard:
|
99
|
+
```ruby
|
100
|
+
# NOTE: `DB` must be an instance of Sequel::DatabaseGuard
|
101
|
+
UserGuard = Sequel::ModelGuard(DB[:users]) do
|
102
|
+
one_to_many :cookies, class: 'Cookie::RawModel'
|
103
|
+
|
104
|
+
def admin?
|
105
|
+
role == 'admin'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
There are, again, two ways of using the guard.
|
111
|
+
|
112
|
+
#### Safe access
|
113
|
+
|
114
|
+
You can safely access the model by using `#safe_execute`:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
users = UserGuard.safe_execute do
|
118
|
+
# if the database is reachable
|
119
|
+
alive do |model|
|
120
|
+
model.all
|
121
|
+
end
|
122
|
+
|
123
|
+
# if the database could not be reached. NOTE: this is optional
|
124
|
+
dead do
|
125
|
+
[]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
#### Unsafe access
|
131
|
+
|
132
|
+
When you don't care about safety (or you're already inside a `safe_execute` context), use
|
133
|
+
`#force_execute`:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
users = UserGuard.force_execute { |model| model.all }
|
137
|
+
```
|
138
|
+
|
139
|
+
#### Accessing a raw model
|
140
|
+
|
141
|
+
Sometimes it's necessary to get access to a raw instance of `Sequel::Model` (good examples are
|
142
|
+
using this extension with `factory_bot` and describing associations like shown above).
|
143
|
+
To get the raw model:
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
User = UserGuard::RawModel
|
147
|
+
```
|
148
|
+
|
149
|
+
## Contributing
|
150
|
+
|
151
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/umbrellio/sequel-connection_guard.
|
152
|
+
|
153
|
+
## License
|
154
|
+
|
155
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
156
|
+
|
157
|
+
## Authors
|
158
|
+
Created by [Alexander Komarov](https://github.com/akxcv).
|
159
|
+
|
160
|
+
<a href="https://github.com/umbrellio/">
|
161
|
+
<img style="float: left;" src="https://umbrellio.github.io/Umbrellio/supported_by_umbrellio.svg" alt="Supported by Umbrellio" width="439" height="72">
|
162
|
+
</a>
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sequel/extensions/connection_guard/configuration_error"
|
4
|
+
require "sequel/extensions/connection_guard/connection_guard"
|
5
|
+
require "sequel/extensions/connection_guard/database_guard"
|
6
|
+
require "sequel/extensions/connection_guard/dataset"
|
7
|
+
require "sequel/extensions/connection_guard/executor"
|
8
|
+
require "sequel/extensions/connection_guard/model_guard"
|
9
|
+
|
10
|
+
# @api public
|
11
|
+
# @since 0.1.0
|
12
|
+
module Sequel
|
13
|
+
# A constructor for model guards.
|
14
|
+
#
|
15
|
+
# @param ds [Sequel::ConnectionGuard::Dataset]
|
16
|
+
# @param class_body [Proc] Mimics Sequel::Model class body.
|
17
|
+
#
|
18
|
+
# @example Creating model guards
|
19
|
+
# DB = Sequel::DatabaseGuard.new('postgres://localhost/mydb')
|
20
|
+
#
|
21
|
+
# UserGuard = Sequel::ModelGuard(DB[:users]) do
|
22
|
+
# many_to_one :cookies, class: 'CookieGuard::RawModel', key: :user_id
|
23
|
+
#
|
24
|
+
# def admin?
|
25
|
+
# role == 'admin'
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# CookieGuard = Sequel::ModelGuard(DB[:cookies])
|
30
|
+
#
|
31
|
+
# @example Safely accessing a model
|
32
|
+
# users = UserGuard.safe_execute do
|
33
|
+
# alive do |model|
|
34
|
+
# model.all
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# dead do
|
38
|
+
# []
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# @example Unsafely accessing a model (raises an exception if connection fails)
|
43
|
+
# cookies = UserGuard.force_execute { |model| model.first!(id: id).cookies }
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
# @since 0.1.0
|
47
|
+
# rubocop:disable Naming/MethodName
|
48
|
+
def self.ModelGuard(ds, &class_body)
|
49
|
+
model = ConnectionGuard::ModelGuard.new(ds, &class_body)
|
50
|
+
|
51
|
+
Class.new.tap do |klass|
|
52
|
+
klass.define_singleton_method(:safe_execute) do |&block|
|
53
|
+
model.safe_execute(&block)
|
54
|
+
end
|
55
|
+
|
56
|
+
klass.define_singleton_method(:force_execute) do |&block|
|
57
|
+
model.force_execute(&block)
|
58
|
+
end
|
59
|
+
|
60
|
+
klass.define_singleton_method(:const_missing) do |const_name|
|
61
|
+
if const_name == :RawModel
|
62
|
+
model.raw_model
|
63
|
+
else
|
64
|
+
super(const_name)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
# rubocop:enable Naming/MethodName
|
70
|
+
|
71
|
+
# @see Sequel::ConnectionGuard::DatabaseGuard
|
72
|
+
#
|
73
|
+
# @api public
|
74
|
+
# @since 0.1.0
|
75
|
+
DatabaseGuard = ConnectionGuard::DatabaseGuard
|
76
|
+
|
77
|
+
# @api public
|
78
|
+
# @since 0.1.0
|
79
|
+
module ConnectionGuard; end
|
80
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module ConnectionGuard
|
5
|
+
# @api private
|
6
|
+
# @since 0.1.0
|
7
|
+
class ConnectionGuard
|
8
|
+
# @param config [String, Hash] database configuration
|
9
|
+
# @param initializer [Proc] code to run upon successful connection
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
# @since 0.1.0
|
13
|
+
def initialize(config, &initializer)
|
14
|
+
@config = config
|
15
|
+
@initializer = initializer
|
16
|
+
@connection = nil
|
17
|
+
|
18
|
+
try_establish_connection
|
19
|
+
end
|
20
|
+
|
21
|
+
# @raise [Sequel::DatabaseConnectionError] connection failure
|
22
|
+
#
|
23
|
+
# @api private
|
24
|
+
# @since 0.1.0
|
25
|
+
def force_execute(&_block)
|
26
|
+
try_establish_connection if @connection.nil?
|
27
|
+
raise Sequel::DatabaseConnectionError unless connection_established?
|
28
|
+
|
29
|
+
yield @connection
|
30
|
+
end
|
31
|
+
|
32
|
+
# @raise [Sequel::DatabaseConnectionError] if connection is not established
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
# @since 0.1.0
|
36
|
+
def raw_handle
|
37
|
+
try_establish_connection if @connection.nil?
|
38
|
+
|
39
|
+
return @connection if connection_established?
|
40
|
+
raise Sequel::DatabaseConnectionError
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# @return [void]
|
46
|
+
#
|
47
|
+
# @api private
|
48
|
+
# @since 0.1.0
|
49
|
+
def try_establish_connection
|
50
|
+
@connection = Sequel.connect(@config)
|
51
|
+
@initializer&.call(@connection)
|
52
|
+
rescue Sequel::DatabaseConnectionError
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [bool]
|
56
|
+
#
|
57
|
+
# @api private
|
58
|
+
# @since 0.1.0
|
59
|
+
def connection_established?
|
60
|
+
return false if @connection.nil?
|
61
|
+
@connection.test_connection
|
62
|
+
rescue Sequel::Error
|
63
|
+
false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module ConnectionGuard
|
5
|
+
# An abstraction for safely accessing Sequel models.
|
6
|
+
#
|
7
|
+
# @example Creating a database guard
|
8
|
+
# DB = Sequel::DatabaseGuard.new('postgres://localhost/mydb')
|
9
|
+
#
|
10
|
+
# @example Safely accessing the database
|
11
|
+
# users = DB.safe_execute do
|
12
|
+
# alive do |db|
|
13
|
+
# db[:users].all
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# dead do
|
17
|
+
# []
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# @example Unsafely accessing the database (raises an exception if connection fails)
|
22
|
+
# DB.force_execute { |db| db[:users].insert(email: 'billikota@example.com', role: 'admin') }
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
# @since 0.1.0
|
26
|
+
class DatabaseGuard
|
27
|
+
# @param config [String, Hash] database configuration
|
28
|
+
# @param initializer [Proc] code to run upon successful connection
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
# @since 0.1.0
|
32
|
+
def initialize(config, &initializer)
|
33
|
+
@connection_guard = ConnectionGuard.new(config, &initializer)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Safely access the database.
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
# users = DB.safe_execute do
|
40
|
+
# alive { |db| db[:users].all }
|
41
|
+
# dead { [] }
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# @param block [Proc]
|
45
|
+
#
|
46
|
+
# @api public
|
47
|
+
# @since 0.1.0
|
48
|
+
def safe_execute(&block)
|
49
|
+
executor = Executor.new
|
50
|
+
executor.instance_eval(&block)
|
51
|
+
@connection_guard.force_execute(&executor.on_alive)
|
52
|
+
rescue Sequel::DatabaseConnectionError
|
53
|
+
executor.on_dead&.call
|
54
|
+
end
|
55
|
+
|
56
|
+
# Unsafely access the database. Will fail if connection fails.
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# DB.force_execute { |db| db[:users].insert(email: 'rustam@example.com') }
|
60
|
+
#
|
61
|
+
# @param block [Proc]
|
62
|
+
# @raise [Sequel::DatabaseConnectionError] connection failure
|
63
|
+
#
|
64
|
+
# @api public
|
65
|
+
# @since 0.1.0
|
66
|
+
def force_execute(&block)
|
67
|
+
@connection_guard.force_execute(&block)
|
68
|
+
end
|
69
|
+
|
70
|
+
# A raw connection handle. Intended for use in test environments (e.x. with DatabaseCleaner)
|
71
|
+
#
|
72
|
+
# @raise [Sequel::DatabaseConnectionError] if connection is not established
|
73
|
+
#
|
74
|
+
# @api public
|
75
|
+
# @since 0.1.0
|
76
|
+
def raw_handle
|
77
|
+
@connection_guard.raw_handle
|
78
|
+
end
|
79
|
+
|
80
|
+
# @param table_name [Symbol]
|
81
|
+
# @return [Sequel::ConnectionGuard::Dataset]
|
82
|
+
#
|
83
|
+
# @api private
|
84
|
+
# @since 0.1.0
|
85
|
+
def [](table_name)
|
86
|
+
Dataset.new(@connection_guard, table_name)
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [void]
|
90
|
+
#
|
91
|
+
# @api private
|
92
|
+
# @since 0.1.0
|
93
|
+
def disconnect
|
94
|
+
@connection_guard.force_execute(&:disconnect)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module ConnectionGuard
|
5
|
+
# A value object that stores all the information required to construct a Sequel dataset.
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
# @since 0.1.0
|
9
|
+
class Dataset
|
10
|
+
# @api private
|
11
|
+
# @since 0.1.0
|
12
|
+
attr_reader :connection_guard, :table_name
|
13
|
+
|
14
|
+
# @param connection_guard [Sequel::ConnectionGuard::ConnectionGuard]
|
15
|
+
# @param table_name [Symbol]
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
# @since 0.1.0
|
19
|
+
def initialize(connection_guard, table_name)
|
20
|
+
@connection_guard = connection_guard
|
21
|
+
@table_name = table_name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module ConnectionGuard
|
5
|
+
# Provides a DSL for accessing the database safely.
|
6
|
+
#
|
7
|
+
# @example Safely accessing a database
|
8
|
+
# DB.safe_execute do
|
9
|
+
# alive do |db|
|
10
|
+
# db[:users].all
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# dead do
|
14
|
+
# []
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# @example Safely accessing a model
|
19
|
+
# UserGuard.safe_execute do
|
20
|
+
# alive do |model|
|
21
|
+
# model.first!
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# # `dead` handler is optional
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# @api private
|
28
|
+
# @since 0.1.0
|
29
|
+
class Executor
|
30
|
+
# @api private
|
31
|
+
# @since 0.1.0
|
32
|
+
attr_reader :on_dead
|
33
|
+
|
34
|
+
# @param block [Proc]
|
35
|
+
#
|
36
|
+
# @api private
|
37
|
+
# @since 0.1.0
|
38
|
+
def alive(&block)
|
39
|
+
@on_alive = block
|
40
|
+
end
|
41
|
+
|
42
|
+
# @param block [Proc]
|
43
|
+
#
|
44
|
+
# @api private
|
45
|
+
# @since 0.1.0
|
46
|
+
def dead(&block)
|
47
|
+
@on_dead = block
|
48
|
+
end
|
49
|
+
|
50
|
+
# @raise [Sequel::ConnectionGuard::ConfigurationError] if an `alive` handler is missing
|
51
|
+
#
|
52
|
+
# @api private
|
53
|
+
# @since 0.1.0
|
54
|
+
def on_alive
|
55
|
+
raise ConfigurationError, "`alive` handler is required for .safe_execute" if @on_alive.nil?
|
56
|
+
@on_alive
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module ConnectionGuard
|
5
|
+
# An abstraction for safely accessing Sequel models.
|
6
|
+
#
|
7
|
+
# @see Sequel.ModelGuard
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
# @since 0.1.0
|
11
|
+
class ModelGuard
|
12
|
+
# @param ds [Sequel::ConnectionGuard::Dataset]
|
13
|
+
# @option class_body [Proc]
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
# @since 0.1.0
|
17
|
+
def initialize(ds, &class_body)
|
18
|
+
@connection_guard = ds.connection_guard
|
19
|
+
@table_name = ds.table_name
|
20
|
+
@class_body = class_body
|
21
|
+
@connected = false
|
22
|
+
@model = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# Safely access the model.
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# users = UserGuard.safe_execute do
|
29
|
+
# alive { |model| model.all }
|
30
|
+
# dead { [] }
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# @param block [Proc]
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
# @since 0.1.0
|
37
|
+
def safe_execute(&block)
|
38
|
+
executor = Executor.new
|
39
|
+
executor.instance_eval(&block)
|
40
|
+
|
41
|
+
@connection_guard.force_execute do |connection|
|
42
|
+
instantiate_model(connection) unless @connected
|
43
|
+
|
44
|
+
executor.on_alive.call(@model)
|
45
|
+
end
|
46
|
+
rescue Sequel::DatabaseConnectionError
|
47
|
+
@connected = false
|
48
|
+
|
49
|
+
executor.on_dead&.call
|
50
|
+
end
|
51
|
+
|
52
|
+
# Unsafely access the model. Will fail if connection fails.
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
# UserGuard.force_execute { |model| model.create(email: 'vova@example.com') }
|
56
|
+
#
|
57
|
+
# @param block [Proc]
|
58
|
+
# @raise [Sequel::DatabaseConnectionError] connection failure
|
59
|
+
#
|
60
|
+
# @api public
|
61
|
+
# @since 0.1.0
|
62
|
+
def force_execute(&block)
|
63
|
+
@connection_guard.force_execute do |connection|
|
64
|
+
instantiate_model(connection) unless @connected
|
65
|
+
|
66
|
+
yield @model
|
67
|
+
end
|
68
|
+
rescue Sequel::DatabaseConnectionError => error
|
69
|
+
@connected = false
|
70
|
+
raise error
|
71
|
+
end
|
72
|
+
|
73
|
+
# @api private
|
74
|
+
# @since 0.1.0
|
75
|
+
def raw_model
|
76
|
+
try_instantiate_model if @model.nil?
|
77
|
+
@model
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# @raise [Sequel::DatabaseConnectionError] connection failure
|
83
|
+
#
|
84
|
+
# @api private
|
85
|
+
# @since 0.1.0
|
86
|
+
def instantiate_model(connection)
|
87
|
+
if @model.nil?
|
88
|
+
@model = Class.new(Sequel::Model(connection[@table_name]))
|
89
|
+
@model.class_eval(&@class_body) unless @class_body.nil?
|
90
|
+
else
|
91
|
+
@model.dataset = connection[@table_name]
|
92
|
+
end
|
93
|
+
|
94
|
+
@connected = true
|
95
|
+
end
|
96
|
+
|
97
|
+
# @api private
|
98
|
+
# @since 0.1.0
|
99
|
+
def try_instantiate_model
|
100
|
+
@connection_guard.force_execute { |connection| instantiate_model(connection) }
|
101
|
+
rescue Sequel::DatabaseConnectionError
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.required_ruby_version = ">= 2.5.1"
|
8
|
+
|
9
|
+
spec.name = "sequel-connection_guard"
|
10
|
+
spec.version = "0.1.0"
|
11
|
+
spec.authors = ["Alexander Komarov"]
|
12
|
+
spec.email = %w[ak@akxcv.com oss@umbrellio.biz]
|
13
|
+
|
14
|
+
spec.summary = "A set of tools for working with unreliable databases."
|
15
|
+
spec.description = "Provides an abstraction for working with unreliable databases safely."
|
16
|
+
spec.homepage = "https://github.com/umbrellio/sequel-connection_guard"
|
17
|
+
spec.license = "MIT"
|
18
|
+
|
19
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
20
|
+
f.match(%r{^(test|spec|features)/})
|
21
|
+
end
|
22
|
+
|
23
|
+
spec.require_paths = %w[lib]
|
24
|
+
|
25
|
+
spec.add_dependency "sequel", "> 5.5"
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler"
|
28
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
29
|
+
spec.add_development_dependency "rspec", "~> 3.8"
|
30
|
+
spec.add_development_dependency "armitage-rubocop", "~> 0.33"
|
31
|
+
spec.add_development_dependency "rubocop-config-umbrellio", "~> 0.70.0"
|
32
|
+
spec.add_development_dependency "pg", "~> 1.0"
|
33
|
+
spec.add_development_dependency "pry"
|
34
|
+
spec.add_development_dependency "coveralls", "~> 0.8"
|
35
|
+
spec.add_development_dependency "simplecov", "~> 0.16"
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sequel-connection_guard
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alexander Komarov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-05-23 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.5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.8'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.8'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: armitage-rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.33'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.33'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop-config-umbrellio
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.70.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.70.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pg
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: pry
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: coveralls
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.8'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.8'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: simplecov
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0.16'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0.16'
|
153
|
+
description: Provides an abstraction for working with unreliable databases safely.
|
154
|
+
email:
|
155
|
+
- ak@akxcv.com
|
156
|
+
- oss@umbrellio.biz
|
157
|
+
executables: []
|
158
|
+
extensions: []
|
159
|
+
extra_rdoc_files: []
|
160
|
+
files:
|
161
|
+
- ".gitignore"
|
162
|
+
- ".rspec"
|
163
|
+
- ".rubocop.yml"
|
164
|
+
- ".travis.yml"
|
165
|
+
- Gemfile
|
166
|
+
- LICENSE
|
167
|
+
- README.md
|
168
|
+
- Rakefile
|
169
|
+
- bin/console
|
170
|
+
- bin/setup
|
171
|
+
- lib/sequel/extensions/connection_guard.rb
|
172
|
+
- lib/sequel/extensions/connection_guard/configuration_error.rb
|
173
|
+
- lib/sequel/extensions/connection_guard/connection_guard.rb
|
174
|
+
- lib/sequel/extensions/connection_guard/database_guard.rb
|
175
|
+
- lib/sequel/extensions/connection_guard/dataset.rb
|
176
|
+
- lib/sequel/extensions/connection_guard/executor.rb
|
177
|
+
- lib/sequel/extensions/connection_guard/model_guard.rb
|
178
|
+
- sequel-connection_guard.gemspec
|
179
|
+
homepage: https://github.com/umbrellio/sequel-connection_guard
|
180
|
+
licenses:
|
181
|
+
- MIT
|
182
|
+
metadata: {}
|
183
|
+
post_install_message:
|
184
|
+
rdoc_options: []
|
185
|
+
require_paths:
|
186
|
+
- lib
|
187
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
188
|
+
requirements:
|
189
|
+
- - ">="
|
190
|
+
- !ruby/object:Gem::Version
|
191
|
+
version: 2.5.1
|
192
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
193
|
+
requirements:
|
194
|
+
- - ">="
|
195
|
+
- !ruby/object:Gem::Version
|
196
|
+
version: '0'
|
197
|
+
requirements: []
|
198
|
+
rubygems_version: 3.0.3
|
199
|
+
signing_key:
|
200
|
+
specification_version: 4
|
201
|
+
summary: A set of tools for working with unreliable databases.
|
202
|
+
test_files: []
|