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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f0f9f174fd7d9539076fe6c50be05824c8055cd3a37cdfbfd8444be73227742
4
- data.tar.gz: 7be7e62f6e803ba1179e0b074dbdf9f4b9f69fcfde189b291d2aaf9a94dc5a12
3
+ metadata.gz: 884f668a94a7ace3a25a9c91b5ca889f0f213a44f266b32c9685b663b0b719d1
4
+ data.tar.gz: 24e9eb3649972ec8d92c9682b54d985c9e1b372442c0a3f8ca9989cfbb0f675c
5
5
  SHA512:
6
- metadata.gz: 9f5790093d7a9ff0d5268ed03921817c45927d5e4741c3ccd741e4fcae94f5173deff1454967627e34a61b177420a3989568776bfe962c47654e97a663054590
7
- data.tar.gz: 44a571a3c63cd105bf3c0f3d8866247c91c064c1b1558a1028dbfca5617e0b210a64d621574c290f98213e7c2ba011cdfc500d2e78e389c9cd43b757a4b419d7
6
+ metadata.gz: c5466d6384d910a03972f81166b444fb26608e11cddb8c6be99701960d9582ca11a077409653788c676bb176ce22df0b11a2a7eaa1e83ff033011ae9615939a8
7
+ data.tar.gz: cbb0602dd3d6904b7067a7e709c961919e5451111cc85f2cd4578a01011a135ae7d54c0e3ea562dfd7421833452f7dc3d0b895888b757014f3a869e19d9d3e0d
data/.envrc.example CHANGED
@@ -4,3 +4,4 @@ export PGDATABASE=iry_development
4
4
  export PGHOST=localhost
5
5
  export PGPORT=5432
6
6
  export PGUSER=postgres
7
+ export PATH="$PWD/exe:$PATH"
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
- - [`check_constraint`](#check_constraint)
62
- - [`exclusion_constraint`](#exclusion_constraint)
63
- - [`foreign_key_constraint`](#foreign_key_constraint)
64
- - [`unique_constraint`](#unique_constraint)
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 only PostgreSQL is supported with the `pg` gem, but it should be simple to add support for other databases.
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
- - NodeJS 18 with `npm`
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
- t.libs << "test"
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.8.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 = max_index_name_size - hashed_identifier.bytesize
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}#{hashed_identifier}"
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
@@ -77,6 +77,8 @@ module Iry
77
77
  case
78
78
  when Handlers::PG.handle?(err)
79
79
  handler = Handlers::PG
80
+ when Handlers::Sqlite.handle?(err)
81
+ handler = Handlers::Sqlite
80
82
  end
81
83
 
82
84
  model_error = handler.handle(err, model)
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/callbacks"
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.8.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.8.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iry",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "Transform database constraint errors into activerecord validation errors",
5
5
  "private": true,
6
6
  "main": "index.js",
@@ -2,4 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "active_record"
5
+ require "openssl"
5
6
  require "securerandom"
7
+ require "set"
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.8.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: 2023-07-14 00:00:00.000000000 Z
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.4.15
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