Skip to content

Commit

Permalink
Cleanup README
Browse files Browse the repository at this point in the history
  • Loading branch information
agios committed Oct 21, 2023
1 parent 0aff142 commit a9b958b
Showing 1 changed file with 26 additions and 56 deletions.
82 changes: 26 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,31 @@
# Unconstrained

Unconstrained for Active Record converts exceptions raised because of
underlying database constraints to appropriate ActiveModel::Errors
underlying foreign key constraints to appropriate ActiveModel::Errors

For example, if trying to insert a record in a model `Book`, which
`belongs_to :author` using an invalid `author_id`, instead of getting
an exception such as:

```
PG::ForeignKeyViolation: ERROR: insert or update on table "books"
violates foreign key constraint "fk_rails_xxxxxxxxxx"
DETAIL: Key (parent_id)=(xxxxxxxxxx) is not present in table "authors".
```

you will now get a validation error on the field `author_id` with the
message `"author is invalid"`

Conversely, when if trying to delete an author but books exist, instead of:

```
PG::ForeignKeyViolation: ERROR: update or delete on table "authors"
violates foreign key constraint "fk_rails_xxxxxxxxxx" on table "books"
DETAIL: Key (id)=(xxxxxxxxxx) is still referenced from table "books".
```

you will now get a `base` validation error on the record with the
message `"Cannot delete record because dependent books exist"`

## Usage

Expand All @@ -18,58 +42,4 @@ This is intentional, as these require a roundrip to the database anyway.
Constraints that can be enforced using validations in the application
should be checked that way.

Currently only handles PostgreSQL.

## Reasoning

Rails supports the `dependent` option on `has_one` / `has_many`
associations, which can be used to a similar effect, ie:

```ruby
has_many :children, dependent: :restrict_with_error
```

However, this has drawbacks. In the simple case, it offers no particular
advantage, since it still needs a roundrip to the database, and since no
locking takes place, there exists the possibility of an error in a
system under heavy use.

In more complex scenarios, errors are bound to occur. For example,
consider the following scenario:

```
parents
|
| <- dependent: :destroy
|
---children
|
| <- dependent: :restrict_with_error
|
---grandchildren
```

Here ActiveRecord will never have an efficient way to proceed. It would
either have to delete the children en masse, leading to database errors
such as:

```
ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR:
update or delete on table "children" violates foreign key constraint
"fk_rails_xxxxxxxxxx" on table "grandchildren"
DETAIL: Key (id)=(xxxxxxxxxx) is still referenced from table "grandchildren".
```

Otherwise ActiveRecord would need to load and check every child record,
which would be extremely inefficient. On the other hand, a relational
database, with the appropriate constraints defined, would handle the
operation swiftly and efficiently.

Now that Rails 4.2 supports foreign key constraints, one would simply
define in the migration:

```ruby
add_foreign_key :children, :parents, on_delete: :cascade
add_foreign_key :grandchildren, :children
```
and then the database exceptions would be handled by Unconstrained.
Currently only handles PostgreSQL.

0 comments on commit a9b958b

Please sign in to comment.