iry 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f0f9f174fd7d9539076fe6c50be05824c8055cd3a37cdfbfd8444be73227742
4
- data.tar.gz: 7be7e62f6e803ba1179e0b074dbdf9f4b9f69fcfde189b291d2aaf9a94dc5a12
3
+ metadata.gz: 9042962f657c4d8500afe884c991d739d3242260886af48f2b44cd2a5404465c
4
+ data.tar.gz: 991b8d4fe5745a532f9dd8dcd84a3474b731b17f933c2eceda81c3cdad9f1d17
5
5
  SHA512:
6
- metadata.gz: 9f5790093d7a9ff0d5268ed03921817c45927d5e4741c3ccd741e4fcae94f5173deff1454967627e34a61b177420a3989568776bfe962c47654e97a663054590
7
- data.tar.gz: 44a571a3c63cd105bf3c0f3d8866247c91c064c1b1558a1028dbfca5617e0b210a64d621574c290f98213e7c2ba011cdfc500d2e78e389c9cd43b757a4b419d7
6
+ metadata.gz: 921c64359cfb033b4b218c0e686286e1dc7af58381915a6eba6921ca2d724d49ae3ec6e1942100137e3cc8e677f06a7d8fd000a1c9e998457a68a319e9a98e63
7
+ data.tar.gz: 344f1ad7142f2e53c5b331fdfb027f27e2507c51ea15567ae38bd1bd359988990f318cca95f919c7b5459985c0b4b01c196b2affaee3094796aa98e9f0fddb2b
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.0
@@ -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_index_name_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.0",
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.0",
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.0",
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.0
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-02-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