iry 0.8.0 → 0.9.1
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 +4 -4
- data/.envrc.example +1 -0
- data/README.md +21 -6
- data/Rakefile +19 -5
- data/VERSION +1 -1
- data/db/schema-sqlite.sql +25 -0
- data/lib/iry/constraint/unique.rb +2 -2
- data/lib/iry/handlers/sqlite.rb +85 -0
- data/lib/iry/transform_constraints.rb +2 -0
- data/lib/iry.rb +1 -1
- data/package-lock.json +2 -2
- data/package.json +1 -1
- data/sorbet/tapioca/require.rb +2 -0
- metadata +19 -9
- data/lib/iry/callbacks.rb +0 -31
- data/rbi/iry.rbi +0 -612
- data/sig/iry.rbs +0 -520
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 884f668a94a7ace3a25a9c91b5ca889f0f213a44f266b32c9685b663b0b719d1
|
4
|
+
data.tar.gz: 24e9eb3649972ec8d92c9682b54d985c9e1b372442c0a3f8ca9989cfbb0f675c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c5466d6384d910a03972f81166b444fb26608e11cddb8c6be99701960d9582ca11a077409653788c676bb176ce22df0b11a2a7eaa1e83ff033011ae9615939a8
|
7
|
+
data.tar.gz: cbb0602dd3d6904b7067a7e709c961919e5451111cc85f2cd4578a01011a135ae7d54c0e3ea562dfd7421833452f7dc3d0b895888b757014f3a869e19d9d3e0d
|
data/.envrc.example
CHANGED
data/README.md
CHANGED
@@ -58,10 +58,23 @@ Multiple constraints of the same or different types can be present on the model,
|
|
58
58
|
|
59
59
|
The following constraint types are available:
|
60
60
|
|
61
|
-
- [
|
62
|
-
- [
|
63
|
-
- [
|
64
|
-
- [
|
61
|
+
- [Iry](#iry)
|
62
|
+
- [Documentation](#documentation)
|
63
|
+
- [Usage](#usage)
|
64
|
+
- [Constraints](#constraints)
|
65
|
+
- [`check_constraint`](#check_constraint)
|
66
|
+
- [`exclusion_constraint`](#exclusion_constraint)
|
67
|
+
- [`foreign_key_constraint`](#foreign_key_constraint)
|
68
|
+
- [`unique_constraint`](#unique_constraint)
|
69
|
+
- [Advanced Usage](#advanced-usage)
|
70
|
+
- [`handle_constraints!`](#handle_constraints)
|
71
|
+
- [`save`](#save)
|
72
|
+
- [`save!`](#save-1)
|
73
|
+
- [`destroy`](#destroy)
|
74
|
+
- [Limitations](#limitations)
|
75
|
+
- [Installation](#installation)
|
76
|
+
- [Development](#development)
|
77
|
+
- [Contributing](#contributing)
|
65
78
|
|
66
79
|
The class method `.constraints` is also available, that returns all the constraints applied to a model.
|
67
80
|
|
@@ -153,7 +166,8 @@ errors will be added to `errors`.
|
|
153
166
|
- It is recommended to avoid transactions when using `Iry`, because if a violation is detected, anything after
|
154
167
|
`Iry.save/save!/handle_constraints` will result in `ActiveRecord::StatementInvalid`, since the transaction is
|
155
168
|
aborted
|
156
|
-
- Currently
|
169
|
+
- Currently PostgreSQL and SQLite are supported with the `pg` and `sqlite3` gem
|
170
|
+
- SQLite support is limited to unique constraints and check constraints, with no support for the `name` field on the unique constraints
|
157
171
|
|
158
172
|
## Installation
|
159
173
|
|
@@ -169,7 +183,8 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
169
183
|
|
170
184
|
**Requirements:**
|
171
185
|
- PostgreSQL with `psql`, `createdb`, `dropdb`
|
172
|
-
-
|
186
|
+
- Sqlite3
|
187
|
+
- NodeJS 22 with `npm`
|
173
188
|
|
174
189
|
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.
|
175
190
|
|
data/Rakefile
CHANGED
@@ -3,12 +3,26 @@ require "bundler/setup"
|
|
3
3
|
require "bundler/gem_tasks"
|
4
4
|
require "minitest/test_task"
|
5
5
|
|
6
|
-
Minitest::TestTask.create(:test) do |t|
|
7
|
-
|
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
|
+
Minitest::TestTask.create(:"test:pg") do |t|
|
14
|
+
t.libs << "test/pg"
|
8
15
|
t.libs << "lib"
|
9
|
-
t.test_prelude = "require \"test/test_helper\""
|
16
|
+
t.test_prelude = "require \"test/pg/test_helper\""
|
10
17
|
t.warning = false
|
11
|
-
t.test_globs = ["test/**/*_test.rb"]
|
18
|
+
t.test_globs = ["test/pg/**/*_test.rb"]
|
19
|
+
end
|
20
|
+
Minitest::TestTask.create(:"test:sqlite") do |t|
|
21
|
+
t.libs << "test/sqlite"
|
22
|
+
t.libs << "lib"
|
23
|
+
t.test_prelude = "require \"test/sqlite/test_helper\""
|
24
|
+
t.warning = false
|
25
|
+
t.test_globs = ["test/sqlite/**/*_test.rb"]
|
12
26
|
end
|
13
27
|
|
14
|
-
task(default: :test)
|
28
|
+
task(default: [:"test:pg", :"test:sqlite"])
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.9.1
|
@@ -0,0 +1,25 @@
|
|
1
|
+
create table users (
|
2
|
+
id integer primary key,
|
3
|
+
unique_text text not null default CURRENT_TIMESTAMP,
|
4
|
+
exclude_text text not null default CURRENT_TIMESTAMP,
|
5
|
+
user_id integer,
|
6
|
+
untracked_text text not null default CURRENT_TIMESTAMP,
|
7
|
+
free_text text not null default '',
|
8
|
+
friend_user_id integer,
|
9
|
+
created_at datetime(6) not null,
|
10
|
+
updated_at datetime(6) not null,
|
11
|
+
|
12
|
+
constraint chk_rails_15df0d7772
|
13
|
+
check (unique_text != 'invalid'),
|
14
|
+
|
15
|
+
foreign key(user_id) references users(id),
|
16
|
+
foreign key(friend_user_id) references users(id)
|
17
|
+
);
|
18
|
+
|
19
|
+
create unique index
|
20
|
+
index_users_on_unique_text on users(unique_text);
|
21
|
+
create unique index
|
22
|
+
index_users_on_untracked_text on users(untracked_text);
|
23
|
+
|
24
|
+
create unique index
|
25
|
+
index_users_on_unique_text_and_untracked_text on users(unique_text, untracked_text);
|
@@ -23,10 +23,10 @@ module Iry
|
|
23
23
|
hashed_id = "_#{digest}"
|
24
24
|
name = "idx_on_#{keys.join("_")}"
|
25
25
|
|
26
|
-
short_limit =
|
26
|
+
short_limit = MAX_INFER_NAME_BYTE_SIZE - hashed_id.bytesize
|
27
27
|
short_name = name.mb_chars.limit(short_limit).to_s
|
28
28
|
|
29
|
-
"#{short_name}#{
|
29
|
+
"#{short_name}#{hashed_id}"
|
30
30
|
end
|
31
31
|
|
32
32
|
# @return [<Symbol>]
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Iry
|
2
|
+
module Handlers
|
3
|
+
# Sqlite handler through `sqlite3` gem
|
4
|
+
# @private
|
5
|
+
module Sqlite
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# @return [Regexp]
|
9
|
+
REGEX = %r{
|
10
|
+
(?:
|
11
|
+
UNIQUE\sconstraint|
|
12
|
+
CHECK\sconstraint
|
13
|
+
)
|
14
|
+
\sfailed(.+)?
|
15
|
+
}x
|
16
|
+
|
17
|
+
REGEX_CONSTRAINT_TYPE = /(UNIQUE|CHECK)\sconstraint/x
|
18
|
+
|
19
|
+
# @return [Regexp]
|
20
|
+
REGEX_CONSTRAINT_NAME = /(?:\:\sindex\s\'|\:\s)([^']+)\'?(?:\s\(\d+\))?$/x
|
21
|
+
|
22
|
+
# When true, the handler is able to handle this exception, representing a constraint error in Sqlite.
|
23
|
+
# This method must ensure not to raise exception in case the sqlite adapter is missing and as such, the
|
24
|
+
# sqlite constant is undefined
|
25
|
+
# @param err [ActiveRecord::StatementInvalid]
|
26
|
+
# @return [Boolean]
|
27
|
+
def handle?(err)
|
28
|
+
return false if !Object.const_defined?("::SQLite3::ConstraintException")
|
29
|
+
|
30
|
+
return true if err.cause.is_a?(::SQLite3::ConstraintException)
|
31
|
+
|
32
|
+
return false
|
33
|
+
end
|
34
|
+
|
35
|
+
# Appends constraint errors as model errors
|
36
|
+
# @param err [ActiveRecord::StatementInvalid]
|
37
|
+
# @param model [Model] should inherit {ActiveRecord::Base} and`include Iry` to match the interface
|
38
|
+
# @return [nil, ActiveModel::Error] if handled constraint, returns the
|
39
|
+
# error attached to the model. If constraint wasn't handled or handling
|
40
|
+
# failed, `nil` is returned
|
41
|
+
def handle(err, model)
|
42
|
+
sqliterr = err.cause
|
43
|
+
constraint_name_msg = sqliterr.message
|
44
|
+
constraint_type_match = REGEX_CONSTRAINT_TYPE.match(constraint_name_msg)
|
45
|
+
if constraint_type_match.nil?
|
46
|
+
return nil
|
47
|
+
end
|
48
|
+
|
49
|
+
constraint_type = constraint_type_match[1]
|
50
|
+
match_constraint = REGEX.match(constraint_name_msg)
|
51
|
+
if match_constraint.nil?
|
52
|
+
return nil
|
53
|
+
end
|
54
|
+
|
55
|
+
match = REGEX_CONSTRAINT_NAME.match(match_constraint[1])
|
56
|
+
if match.nil?
|
57
|
+
return nil
|
58
|
+
end
|
59
|
+
|
60
|
+
constraint_name = nil
|
61
|
+
case constraint_type
|
62
|
+
in "UNIQUE"
|
63
|
+
columns = match[1].
|
64
|
+
split(", ").
|
65
|
+
map { |full_col| full_col.delete_prefix("#{model.class.table_name}.") }
|
66
|
+
constraint_name = Constraint::Unique.infer_name(
|
67
|
+
columns,
|
68
|
+
model.class.table_name
|
69
|
+
)
|
70
|
+
in "CHECK"
|
71
|
+
constraint_name = match[1]
|
72
|
+
else
|
73
|
+
return nil
|
74
|
+
end
|
75
|
+
|
76
|
+
constraint = model.class.constraints[constraint_name]
|
77
|
+
if constraint.nil?
|
78
|
+
return nil
|
79
|
+
end
|
80
|
+
|
81
|
+
return constraint.apply(model)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/iry.rb
CHANGED
@@ -5,7 +5,7 @@ require_relative "iry/version"
|
|
5
5
|
require_relative "iry/handlers"
|
6
6
|
require_relative "iry/handlers/null"
|
7
7
|
require_relative "iry/handlers/pg"
|
8
|
-
require_relative "iry/
|
8
|
+
require_relative "iry/handlers/sqlite"
|
9
9
|
require_relative "iry/macros"
|
10
10
|
require_relative "iry/constraint"
|
11
11
|
require_relative "iry/constraint/check"
|
data/package-lock.json
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
{
|
2
2
|
"name": "iry",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.9.1",
|
4
4
|
"lockfileVersion": 3,
|
5
5
|
"requires": true,
|
6
6
|
"packages": {
|
7
7
|
"": {
|
8
8
|
"name": "iry",
|
9
|
-
"version": "0.
|
9
|
+
"version": "0.9.1",
|
10
10
|
"license": "MIT",
|
11
11
|
"devDependencies": {
|
12
12
|
"conventional-changelog-cli": ">= 3.0.0",
|
data/package.json
CHANGED
data/sorbet/tapioca/require.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Francesco Belladonna
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-06-06 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: activerecord
|
@@ -80,6 +79,20 @@ dependencies:
|
|
80
79
|
- - ">="
|
81
80
|
- !ruby/object:Gem::Version
|
82
81
|
version: '13'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: sqlite3
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '2.1'
|
89
|
+
type: :development
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '2.1'
|
83
96
|
description: Transform database constraint errors into activerecord validation errors
|
84
97
|
email:
|
85
98
|
- francesco@fc5.me
|
@@ -94,9 +107,9 @@ files:
|
|
94
107
|
- Rakefile
|
95
108
|
- Steepfile
|
96
109
|
- VERSION
|
110
|
+
- db/schema-sqlite.sql
|
97
111
|
- db/schema.pgsql
|
98
112
|
- lib/iry.rb
|
99
|
-
- lib/iry/callbacks.rb
|
100
113
|
- lib/iry/constraint.rb
|
101
114
|
- lib/iry/constraint/check.rb
|
102
115
|
- lib/iry/constraint/exclusion.rb
|
@@ -105,14 +118,13 @@ files:
|
|
105
118
|
- lib/iry/handlers.rb
|
106
119
|
- lib/iry/handlers/null.rb
|
107
120
|
- lib/iry/handlers/pg.rb
|
121
|
+
- lib/iry/handlers/sqlite.rb
|
108
122
|
- lib/iry/macros.rb
|
109
123
|
- lib/iry/patch.rb
|
110
124
|
- lib/iry/transform_constraints.rb
|
111
125
|
- lib/iry/version.rb
|
112
126
|
- package-lock.json
|
113
127
|
- package.json
|
114
|
-
- rbi/iry.rbi
|
115
|
-
- sig/iry.rbs
|
116
128
|
- sorbet/config
|
117
129
|
- sorbet/tapioca/config.yml
|
118
130
|
- sorbet/tapioca/require.rb
|
@@ -123,7 +135,6 @@ metadata:
|
|
123
135
|
allowed_push_host: https://rubygems.org/
|
124
136
|
homepage_uri: https://github.com/Fire-Dragon-DoL/iry
|
125
137
|
source_code_uri: https://github.com/Fire-Dragon-DoL/iry
|
126
|
-
post_install_message:
|
127
138
|
rdoc_options: []
|
128
139
|
require_paths:
|
129
140
|
- lib
|
@@ -138,8 +149,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
149
|
- !ruby/object:Gem::Version
|
139
150
|
version: '0'
|
140
151
|
requirements: []
|
141
|
-
rubygems_version: 3.
|
142
|
-
signing_key:
|
152
|
+
rubygems_version: 3.6.3
|
143
153
|
specification_version: 4
|
144
154
|
summary: Transform database constraint errors into activerecord validation errors
|
145
155
|
test_files: []
|
data/lib/iry/callbacks.rb
DELETED
@@ -1,31 +0,0 @@
|
|
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
|
-
# Rolling back allows to keep using SQL in other callbacks, which
|
24
|
-
# otherwise would raise PG::InFailedSqlTransaction
|
25
|
-
raise ActiveRecord::Rollback
|
26
|
-
else
|
27
|
-
raise
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|