iry 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.envrc.example +6 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE +21 -0
- data/README.md +119 -0
- data/Rakefile +14 -0
- data/Steepfile +28 -0
- data/VERSION +1 -0
- data/db/schema.pgsql +14 -0
- data/lib/iry/callbacks.rb +27 -0
- data/lib/iry/constraint/check.rb +39 -0
- data/lib/iry/constraint/exclusion.rb +39 -0
- data/lib/iry/constraint/foreign_key.rb +44 -0
- data/lib/iry/constraint/unique.rb +44 -0
- data/lib/iry/constraint.rb +26 -0
- data/lib/iry/handlers/null.rb +24 -0
- data/lib/iry/handlers/pg.rb +56 -0
- data/lib/iry/handlers.rb +48 -0
- data/lib/iry/macros.rb +118 -0
- data/lib/iry/version.rb +4 -0
- data/lib/iry.rb +44 -0
- data/rbi/iry.rbi +408 -0
- data/sig/iry.rbs +340 -0
- data/sorbet/config +10 -0
- data/sorbet/tapioca/config.yml +64 -0
- data/sorbet/tapioca/require.rb +5 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 21ffff3b3300f8b100bbe0af995b5d0736260f8c17ca93c9f6e66b44b9fd3088
|
4
|
+
data.tar.gz: 5df2033893b5f5d8e8bdf7c97061f8bacc185620daa9a42b87167e945cf534ae
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dbc9d0e8a465a34834007308bce3955ed5248c8a93699f81a346c6472cf153dc3df982217868a0392b510f32a0d3fc7ead3c6e9474bc55a508fd976a03e64be4
|
7
|
+
data.tar.gz: 3810fa311f0c09b51dd681eeaa31d6e36eb6a9ee3c131042e43cc525a0231a8df8cbe6f4ae2a55588de95ca3c7e950bf4ef36bc56410214c9adc71f1ceea1ab7
|
data/.envrc.example
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--no-private 'lib/**/*.rb' - 'README.md' 'CHANGELOG.md' VERSION LICENSE '.envrc.example' Gemfile 'Gemfile.lock' 'db/schema.pgsql' 'iry.gemspec'
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Francesco Belladonna
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
# Iry
|
2
|
+
|
3
|
+
Convert constraint errors into Rails model validation errors.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
Given the following database schema:
|
8
|
+
|
9
|
+
```sql
|
10
|
+
create extension if not exists "pgcrypto";
|
11
|
+
create extension if not exists "btree_gist";
|
12
|
+
|
13
|
+
create table if not exists users (
|
14
|
+
id uuid primary key default gen_random_uuid(),
|
15
|
+
unique_text text unique not null default gen_random_uuid()::text
|
16
|
+
created_at timestamp(6) not null,
|
17
|
+
updated_at timestamp(6) not null
|
18
|
+
);
|
19
|
+
```
|
20
|
+
|
21
|
+
The following constraint can be used on the `User` class:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
class User < ActiveRecord::Base
|
25
|
+
include Iry
|
26
|
+
|
27
|
+
belongs_to :user, optional: true
|
28
|
+
|
29
|
+
unique_constraint :unique_text
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
When saving a new `User` record or updating it, in case constraint exceptions are raised, these will be rescued and
|
34
|
+
validation errors will be applied to the record, like in the following example:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
user = User.create!(unique_text: "some unique text")
|
38
|
+
|
39
|
+
fail_user = User.create(unique_text: "some unique text")
|
40
|
+
|
41
|
+
fail_user.errors.details.fetch(:unique_text) #=> [{error: :taken}]
|
42
|
+
```
|
43
|
+
|
44
|
+
Multiple constraints of the same or different types can be present on the model, as long as the `:name` is different.
|
45
|
+
|
46
|
+
The following constraint types are available:
|
47
|
+
- [`check_constraint`](#check_constraint)
|
48
|
+
- [`exclusion_constraint`](#exclusion_constraint)
|
49
|
+
- [`foreign_key_constraint`](#foreign_key_constraint)
|
50
|
+
- [`unique_constraint`](#unique_constraint)
|
51
|
+
|
52
|
+
The class method `.constraints` is also available, that returns all the constraints applied to a model.
|
53
|
+
|
54
|
+
## Constraints
|
55
|
+
|
56
|
+
### `check_constraint`
|
57
|
+
|
58
|
+
Catches a specific check constraint violation.
|
59
|
+
|
60
|
+
- **key** (`Symbol`) which key will have validation errors added to
|
61
|
+
- **name** (optional `String`) constraint name in the database, to detect constraint errors. Infferred if omitted
|
62
|
+
- **message** (optional `String` or `Symbol`) error message, defaults to `:invalid`
|
63
|
+
|
64
|
+
### `exclusion_constraint`
|
65
|
+
|
66
|
+
Catches a specific exclusion constraint violation.
|
67
|
+
|
68
|
+
- **key** (`Symbol`) which key will have validation errors added to
|
69
|
+
- **name** (optional `String`) constraint name in the database, to detect constraint errors. Infferred if omitted
|
70
|
+
- **message** (optional `String` or `Symbol`) error message, defaults to `:taken`
|
71
|
+
|
72
|
+
### `foreign_key_constraint`
|
73
|
+
|
74
|
+
Catches a specific foreign key constraint violation.
|
75
|
+
|
76
|
+
- **key_or_keys** (`Symbol` or array of `Symbol`) key or keys used to make the foreign key constraint
|
77
|
+
- **name** (optional `String`) constraint name in the database, to detect constraint errors. Infferred if omitted
|
78
|
+
- **message** (optional `String` or `Symbol`) error message, defaults to `:required`
|
79
|
+
- **error_key** (optional `Symbol`) which key will have validation errors added to
|
80
|
+
|
81
|
+
### `unique_constraint`
|
82
|
+
|
83
|
+
Catches a specific foreign key constraint violation.
|
84
|
+
|
85
|
+
- **key_or_keys** (`Symbol` or array of `Symbol`) key or keys used to make the unique constraint
|
86
|
+
- **name** (optional `String`) constraint name in the database, to detect constraint errors. Infferred if omitted
|
87
|
+
- **message** (optional `String` or `Symbol`) error message, defaults to `:taken`
|
88
|
+
- **error_key** (optional `Symbol`) which key will have validation errors added to
|
89
|
+
|
90
|
+
## Limitations
|
91
|
+
|
92
|
+
- `valid?` will not check for constraints. If calling `valid?` right after a `save` operation, keep in mind `errors`
|
93
|
+
are cleared
|
94
|
+
- `create!` and `update!` will raise `ActiveRecord::RecordNotSaved` for constraints that are caught by `iry`, instead
|
95
|
+
of `ActiveModel::ValidationError`
|
96
|
+
- Currently only PostgreSQL is supported, with the `pg` gem, but it's easy to add support for other databases.
|
97
|
+
|
98
|
+
## Installation
|
99
|
+
|
100
|
+
Install the gem and add to the application's Gemfile by executing:
|
101
|
+
|
102
|
+
$ bundle add iry
|
103
|
+
|
104
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
105
|
+
|
106
|
+
$ gem install iry
|
107
|
+
|
108
|
+
## Development
|
109
|
+
|
110
|
+
**Requirements:**
|
111
|
+
- PostgreSQL with `psql`, `createdb`, `dropdb`
|
112
|
+
|
113
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
114
|
+
|
115
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
116
|
+
|
117
|
+
## Contributing
|
118
|
+
|
119
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Fire-Dragon-DoL/iry.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "minitest/test_task"
|
5
|
+
|
6
|
+
Minitest::TestTask.create(:test) do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.libs << "lib"
|
9
|
+
t.test_prelude = "require \"test/test_helper\""
|
10
|
+
t.warning = false
|
11
|
+
t.test_globs = ["test/**/*_test.rb"]
|
12
|
+
end
|
13
|
+
|
14
|
+
task(default: :test)
|
data/Steepfile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
D = Steep::Diagnostic
|
2
|
+
|
3
|
+
target(:lib) do
|
4
|
+
signature("sig")
|
5
|
+
|
6
|
+
# Directory name
|
7
|
+
check("lib")
|
8
|
+
# check "Gemfile" # File name
|
9
|
+
# check "app/models/**/*.rb" # Glob
|
10
|
+
# ignore "lib/templates/*.rb"
|
11
|
+
|
12
|
+
# library "pathname", "set" # Standard libraries
|
13
|
+
# library "strong_json" # Gems
|
14
|
+
|
15
|
+
# configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting
|
16
|
+
# configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting
|
17
|
+
# configure_code_diagnostics do |hash| # You can setup everything yourself
|
18
|
+
# hash[D::Ruby::NoMethod] = :information
|
19
|
+
# end
|
20
|
+
end
|
21
|
+
|
22
|
+
# target :test do
|
23
|
+
# signature "sig", "sig-private"
|
24
|
+
#
|
25
|
+
# check "test"
|
26
|
+
#
|
27
|
+
# # library "pathname", "set" # Standard libraries
|
28
|
+
# end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/db/schema.pgsql
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
create extension if not exists "pgcrypto";
|
2
|
+
create extension if not exists "btree_gist";
|
3
|
+
|
4
|
+
create table if not exists users (
|
5
|
+
id uuid primary key default gen_random_uuid(),
|
6
|
+
unique_text text unique not null default gen_random_uuid()::text check (unique_text != 'invalid'),
|
7
|
+
exclude_text text not null default gen_random_uuid()::text,
|
8
|
+
user_id uuid references users (id),
|
9
|
+
friend_user_id uuid references users (id),
|
10
|
+
created_at timestamp(6) not null,
|
11
|
+
updated_at timestamp(6) not null,
|
12
|
+
-- acts similar to unique constraint
|
13
|
+
exclude using gist (exclude_text with =)
|
14
|
+
);
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Iry
|
2
|
+
# Main function to kick-off **Iry** constraint-checking mechanism
|
3
|
+
# If interested in adding support for other databases beside Postgres, modify this file.
|
4
|
+
# @private
|
5
|
+
module Callbacks
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# @param model [Handlers::Model]
|
9
|
+
# @yield
|
10
|
+
# @return [void]
|
11
|
+
def around_save(model)
|
12
|
+
yield
|
13
|
+
rescue ActiveRecord::StatementInvalid => err
|
14
|
+
handler = Handlers::Null
|
15
|
+
case
|
16
|
+
when Handlers::PG.handle?(err)
|
17
|
+
handler = Handlers::PG
|
18
|
+
end
|
19
|
+
|
20
|
+
is_handled = handler.handle(err, model)
|
21
|
+
|
22
|
+
if !is_handled
|
23
|
+
raise
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Iry
|
2
|
+
module Constraint
|
3
|
+
class Check
|
4
|
+
# Infers the check constraint name based on key and table name
|
5
|
+
# @param key [Symbol]
|
6
|
+
# @param table_name [String]
|
7
|
+
# @return [String]
|
8
|
+
def self.infer_name(key, table_name)
|
9
|
+
"#{table_name}_#{key}_check"
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Symbol]
|
13
|
+
attr_accessor :key
|
14
|
+
# @return [Symbol, String]
|
15
|
+
attr_accessor :message
|
16
|
+
# @return [String]
|
17
|
+
attr_accessor :name
|
18
|
+
|
19
|
+
# @param key [Symbol] key to apply error message for check constraint to
|
20
|
+
# @param message [Symbol, String] the validation error message
|
21
|
+
# @param name [String] constraint name
|
22
|
+
def initialize(
|
23
|
+
key,
|
24
|
+
name:,
|
25
|
+
message: :invalid
|
26
|
+
)
|
27
|
+
@key = key
|
28
|
+
@message = message
|
29
|
+
@name = name
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param model [Handlers::Model]
|
33
|
+
# @return [void]
|
34
|
+
def apply(model)
|
35
|
+
model.errors.add(key, message)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Iry
|
2
|
+
module Constraint
|
3
|
+
class Exclusion
|
4
|
+
# Infers the exclusion constraint name based on key and table name
|
5
|
+
# @param key [Symbol]
|
6
|
+
# @param table_name [String]
|
7
|
+
# @return [String]
|
8
|
+
def self.infer_name(key, table_name)
|
9
|
+
"#{table_name}_#{key}_excl"
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Symbol]
|
13
|
+
attr_accessor :key
|
14
|
+
# @return [Symbol, String]
|
15
|
+
attr_accessor :message
|
16
|
+
# @return [String]
|
17
|
+
attr_accessor :name
|
18
|
+
|
19
|
+
# @param key [Symbol] key to apply error message for exclusion constraint to
|
20
|
+
# @param message [Symbol, String] the validation error message
|
21
|
+
# @param name [String] constraint name
|
22
|
+
def initialize(
|
23
|
+
key,
|
24
|
+
name:,
|
25
|
+
message: :taken
|
26
|
+
)
|
27
|
+
@key = key
|
28
|
+
@message = message
|
29
|
+
@name = name
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param model [Handlers::Model]
|
33
|
+
# @return [void]
|
34
|
+
def apply(model)
|
35
|
+
model.errors.add(key, message)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Iry
|
2
|
+
module Constraint
|
3
|
+
class ForeignKey
|
4
|
+
# Infers the unique constraint name based on keys and table name
|
5
|
+
# @param keys [<Symbol>]
|
6
|
+
# @param table_name [String]
|
7
|
+
# @return [String]
|
8
|
+
def self.infer_name(keys, table_name)
|
9
|
+
"#{table_name}_#{keys.join("_")}_fkey"
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [<Symbol>]
|
13
|
+
attr_accessor :keys
|
14
|
+
# @return [Symbol, String]
|
15
|
+
attr_accessor :message
|
16
|
+
# @return [String]
|
17
|
+
attr_accessor :name
|
18
|
+
# @return [Symbol]
|
19
|
+
attr_accessor :error_key
|
20
|
+
|
21
|
+
# @param keys [<Symbol>] array of keys to track the uniqueness constraint of
|
22
|
+
# @param message [Symbol, String] the validation error message
|
23
|
+
# @param name [String] constraint name
|
24
|
+
# @param error_key [Symbol] key to which the validation error will be applied to
|
25
|
+
def initialize(
|
26
|
+
keys,
|
27
|
+
name:,
|
28
|
+
error_key:,
|
29
|
+
message: :required
|
30
|
+
)
|
31
|
+
@keys = keys
|
32
|
+
@message = message
|
33
|
+
@name = name
|
34
|
+
@error_key = error_key
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param model [Handlers::Model]
|
38
|
+
# @return [void]
|
39
|
+
def apply(model)
|
40
|
+
model.errors.add(error_key, message)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Iry
|
2
|
+
module Constraint
|
3
|
+
class Unique
|
4
|
+
# Infers the unique constraint name based on keys and table name
|
5
|
+
# @param keys [<Symbol>]
|
6
|
+
# @param table_name [String]
|
7
|
+
# @return [String]
|
8
|
+
def self.infer_name(keys, table_name)
|
9
|
+
"#{table_name}_#{keys.join("_")}_key"
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [<Symbol>]
|
13
|
+
attr_accessor :keys
|
14
|
+
# @return [Symbol, String]
|
15
|
+
attr_accessor :message
|
16
|
+
# @return [String]
|
17
|
+
attr_accessor :name
|
18
|
+
# @return [Symbol]
|
19
|
+
attr_accessor :error_key
|
20
|
+
|
21
|
+
# @param keys [<Symbol>] array of keys to track the uniqueness constraint of
|
22
|
+
# @param message [Symbol, String] the validation error message
|
23
|
+
# @param name [String] constraint name
|
24
|
+
# @param error_key [Symbol] key to which the validation error will be applied to
|
25
|
+
def initialize(
|
26
|
+
keys,
|
27
|
+
name:,
|
28
|
+
error_key:,
|
29
|
+
message: :taken
|
30
|
+
)
|
31
|
+
@keys = keys
|
32
|
+
@message = message
|
33
|
+
@name = name
|
34
|
+
@error_key = error_key
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param model [Handlers::Model]
|
38
|
+
# @return [void]
|
39
|
+
def apply(model)
|
40
|
+
model.errors.add(error_key, message)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Iry
|
2
|
+
# Interface representing a constraint.
|
3
|
+
# A constraint has a name and can apply errors to an object inheriting from {ActiveRecord::Base}
|
4
|
+
# @abstract
|
5
|
+
module Constraint
|
6
|
+
# Sets validation errors on the model
|
7
|
+
# @abstract
|
8
|
+
# @param model [Handlers::Model]
|
9
|
+
# @return [void]
|
10
|
+
def apply(model)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Name of the constraint to be caught from the database
|
14
|
+
# @abstract
|
15
|
+
# @return [String]
|
16
|
+
def name
|
17
|
+
end
|
18
|
+
|
19
|
+
# Message to be attached as validation error to the model
|
20
|
+
# (see Handlers::Model)
|
21
|
+
# @abstract
|
22
|
+
# @return [Symbol, String]
|
23
|
+
def message
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Iry
|
2
|
+
module Handlers
|
3
|
+
# Catch-all handler for unrecognized database adapters
|
4
|
+
# @private
|
5
|
+
module Null
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# Returns always true, catching any unhandled database exception
|
9
|
+
# @param err [StandardError, ActiveRecord::StatementInvalid]
|
10
|
+
# @return [Boolean]
|
11
|
+
def handle?(err)
|
12
|
+
return true
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return always false, failing to handle any constraint
|
16
|
+
# @param err [ActiveRecord::StatementInvalid]
|
17
|
+
# @param model [Model] should inherit {ActiveRecord::Base} and`include Iry` to match the interface
|
18
|
+
# @return [Boolean]
|
19
|
+
def handle(err, model)
|
20
|
+
return false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Iry
|
2
|
+
module Handlers
|
3
|
+
# PostgreSQL handler through `pg` gem
|
4
|
+
# @private
|
5
|
+
module PG
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# @return [Regexp]
|
9
|
+
REGEX = %r{
|
10
|
+
(?:
|
11
|
+
unique\sconstraint|
|
12
|
+
check\sconstraint|
|
13
|
+
exclusion\sconstraint|
|
14
|
+
foreign\skey\sconstraint
|
15
|
+
)
|
16
|
+
\s"(.+)"
|
17
|
+
}x
|
18
|
+
|
19
|
+
# When true, the handler is able to handle this exception, representing a constraint error in PostgreSQL.
|
20
|
+
# This method must ensure not to raise exception in case the postgresql adapter is missing and as such, the
|
21
|
+
# postgres constant is undefined
|
22
|
+
# @param err [ActiveRecord::StatementInvalid]
|
23
|
+
# @return [Boolean]
|
24
|
+
def handle?(err)
|
25
|
+
return false if !Object.const_defined?("::PG::Error")
|
26
|
+
return false if !err.cause.is_a?(::PG::Error)
|
27
|
+
|
28
|
+
return true if err.cause.is_a?(::PG::UniqueViolation)
|
29
|
+
return true if err.cause.is_a?(::PG::CheckViolation)
|
30
|
+
return true if err.cause.is_a?(::PG::ExclusionViolation)
|
31
|
+
return true if err.cause.is_a?(::PG::ForeignKeyViolation)
|
32
|
+
|
33
|
+
return false
|
34
|
+
end
|
35
|
+
|
36
|
+
# Appends constraint errors as model errors
|
37
|
+
# @param err [ActiveRecord::StatementInvalid]
|
38
|
+
# @param model [Model] should inherit {ActiveRecord::Base} and`include Iry` to match the interface
|
39
|
+
# @return [void]
|
40
|
+
def handle(err, model)
|
41
|
+
pgerr = err.cause
|
42
|
+
constraint_name_msg = pgerr.result.error_field(::PG::Constants::PG_DIAG_MESSAGE_PRIMARY)
|
43
|
+
match = REGEX.match(constraint_name_msg)
|
44
|
+
constraint_name = match[1]
|
45
|
+
constraint = model.class.constraints[constraint_name]
|
46
|
+
if constraint.nil?
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
|
50
|
+
constraint.apply(model)
|
51
|
+
|
52
|
+
return true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/iry/handlers.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Iry
|
2
|
+
module Handlers
|
3
|
+
# Interface for handlers of different database types
|
4
|
+
# @abstract
|
5
|
+
module Handler
|
6
|
+
# @abstract
|
7
|
+
# @param err [ActiveRecord::StatementInvalid] possible constraint error to handle
|
8
|
+
# @return [Boolean] true if this database handler is the correct one for this exception
|
9
|
+
def handle?(err)
|
10
|
+
end
|
11
|
+
|
12
|
+
# @abstract
|
13
|
+
# @param err [ActiveRecord::StatementInvalid] possible constraint error to handle
|
14
|
+
# @param model [Model]
|
15
|
+
# @return [Boolean] true if this database handler handled the constraint error
|
16
|
+
def handle(err, model)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Interface of the model class. This class is usually inherits from {ActiveRecord::Base}
|
21
|
+
# @abstract
|
22
|
+
module ModelClass
|
23
|
+
# @abstract
|
24
|
+
# @return [String]
|
25
|
+
def table_name
|
26
|
+
end
|
27
|
+
|
28
|
+
# @abstract
|
29
|
+
# @return [{String => Constraint}]
|
30
|
+
def constraints
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Interface of the model that should be used to handle constraints.
|
35
|
+
# This object is an instance of {ActiveRecord::Base}
|
36
|
+
# @abstract
|
37
|
+
module Model
|
38
|
+
# @abstract
|
39
|
+
# @return [ActiveModel::Errors]
|
40
|
+
def errors
|
41
|
+
end
|
42
|
+
|
43
|
+
# @!method class
|
44
|
+
# @abstract
|
45
|
+
# @return [ModelClass]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|