Gem Version Build Status Coverage Status

SchemaPlus::ForeignKeys

SchemaPlus::ForeignKeys provides extended support in ActiveRecord. This includes extended support for declaring foreign key constraints in migrations; support for deferrable constraints; support for SQLite3; cleaner schema dumps.

SchemaPlus::ForeignKeys is part of the SchemaPlus family of Ruby on Rails ActiveRecord extension gems.

For extra convenience, see also schema_auto_foreign_keys, which creates foriegn key constraints automatically.

Installation

As usual:

gem "schema_plus_foreign_keys"                # in a Gemfile
gem.add_dependency "schema_plus_foreign_keys" # in a .gemspec

Usage

Migrations

To declare a foreign key constraint for a column, use the :foreign_key option. The same options can be used with t.integer, t.references, t.belongs_to, t.foreign_key, change_column, and add_foreign_key:

t.references :author, foreign_key: true           # create a foreign_key to table "authors"
t.references :author, foreign_key: {}             # create a foreign_key to table "authors"
t.references :author, foreign_key: false          # don't create a constraint (this is the default)
t.integer :author,    foreign_key: true           # create a foreign_key to table "authors"

t.references :parent, foreign_key: true           # special column parent_id defaults to referencing its own table

Specify the target table and its primary key using the :references and :primary_key:

t.references :author, foreign_key: { references: :authors }        # the default
t.integer :author,    foreign_key: { references: :authors }        # the default
t.references :author, foreign_key: { references: :people }         # choose table name
t.references :author, foreign_key: { primary_key: :ssn] }          # choose primary key
t.references :author, foreign_key: { references: :people, primary_key: :ssn] } # choose both
t.references :author, foreign_key: { references: [:people, :ssn] } # shortcut for both
t.references :author, foreign_key: { references: nil }             # same as foreign_key: false

You can also specify other attributes that are supported by Rails:

t.references :author, foreign_key: { name: "my_fk" }               # override default auto-generated constraint name
t.references :author, foreign_key: { on_delete: :cascade }
t.references :author, foreign_key: { on_update: :set_null }

In addition, support for deferrable constraints is implemented:

t.references :author, foreign_key: { deferrable: true }
t.references :author, foreign_key: { deferrable: :initially_deferred }

Of course the options can be combined:

t.references :author, foreign_key: { references: :people, primary_key: :ssn, name: "my_fk", on_delet: :no_action }

As a shorthand, all options except :name can be specified without placing them in a foreign_key hash, e.g.

t.references :author, on_delete: :cascade  # shorthand for foreign_key: { on_delete: :cascade }
t.references :author, references: :people  # shorthand for foreign_key: { references: :people }

To remove a foreign key constraint, you can either change the column, specifying foreign_key: false, or use migration.remove_foreign_key(table, column)

Introspection

To examine the foreign keys on a model, you can use:

Model.foreign_keys            # foreign key constraints from this model to another
Model.reverse_foreign_keys    # foreign key constraints from other models to this

(These results are cached along with other column-specific information; if you change the table definition, call Model.reset_column_information to clear the cache)

You can also query at the connection level (uncached):

connection.foreign_keys(table_name)
connection.reverse_foreign_keys(table_name)

These calls all return an array of ForeignKeyDefinition objects, which support these methods:

fk.from_table
fk.column
fk.to_table
fk.primary_key
fk.on_update
fk.on_delete
fk.deferrable

Configuring Defaults

If you don't specify on_update and on_delete when creating a foreign key constraint, they normally default to whatever the DBMS's default behavior is.

But you can also configure a global default (e.g. in a Rails initializer):

SchemaPlus::ForeignKeys.setup do |config|
    config.on_update = :cascade   # default is nil, meaning use default dbms behavior
    config.on_delete = :nullify   # default is nil, meaning use default dbms behavior
end

Or you can configure a per-table default in a migration:

create_table :things, foreign_key: { on_update: :set_null } do |t|
    ...
end

SQLite 3 Notes

SchemaPlus::ForeignKeys supports foreign key constraints in SQLite3.

However note that SQLite3 requires you to declare the constraints as part of the table definition, and does not allow you to add, remove, or change constraints after the fact. Thus you'll get an exception if you try add_foreign_key, remove_foreign_key, or change_column changing the foreign key options.

Schema Dump

For clarity (and because it's required for SQLite3), in the generated schema_dump.rb file, the foreign key definitions are included within the table definitions.

This means that the tables are output sorted that a table is defined before others that depend on it. If, however, there are circular dependencies in the foreign key relations, this won't be possible; In that case some table definitions will include comments indicating a "forward reference" to a table that's farther down in the file, and the constraint will be defined once that table is defined (this can never happen with SQLite3).

Compatibility

SchemaPlus::ForeignKeys is tested on:

  • ruby 2.5 with activerecord 5.2, using mysql2, sqlite3 or postgresql:9.6
  • ruby 2.5 with activerecord 6.0, using mysql2, sqlite3 or postgresql:9.6
  • ruby 2.5 with activerecord 6.1, using mysql2, sqlite3 or postgresql:9.6
  • ruby 2.7 with activerecord 5.2, using mysql2, sqlite3 or postgresql:9.6
  • ruby 2.7 with activerecord 6.0, using mysql2, sqlite3 or postgresql:9.6
  • ruby 2.7 with activerecord 6.1, using mysql2, sqlite3 or postgresql:9.6
  • ruby 2.7 with activerecord 7.0, using mysql2, sqlite3 or postgresql:9.6
  • ruby 3.0 with activerecord 6.0, using mysql2, sqlite3 or postgresql:9.6
  • ruby 3.0 with activerecord 6.1, using mysql2, sqlite3 or postgresql:9.6
  • ruby 3.0 with activerecord 7.0, using mysql2, sqlite3 or postgresql:9.6
  • ruby 3.1 with activerecord 6.0, using mysql2, sqlite3 or postgresql:9.6
  • ruby 3.1 with activerecord 6.1, using mysql2, sqlite3 or postgresql:9.6
  • ruby 3.1 with activerecord 7.0, using mysql2, sqlite3 or postgresql:9.6

History

  • 1.1.0 - Add AR 6.1 and 7.0. Add Ruby 3.1, and drop schema_plus_compatibility dependency
  • 1.0.0 - Drop Ruby < 2.5 and Rails < 5.2, adding Rails 6.0, and remove many deprecations
  • 0.1.8 - Compatibility with ActiveRecord 5.1 and ActiveRecord 5.2.
  • 0.1.7 - Compatibility with ActiveRecord 5.0.
  • 0.1.6 - Missing require
  • 0.1.5 - Explicit gem dependencies
  • 0.1.4 - Upgrade schema_plus_core dependency
  • 0.1.3 - Support aciverecord 4.2.6. Thanks to @btsuhako and @dholdren
  • 0.1.2 - Handle very long names
  • 0.1.1 - Cleanup; use (new) core Migration::RenameTable stack rather than monkey patching.
  • 0.1.0 - Initial release, brought over from schema_plus 1.x via 2.0.0.pre*

Development & Testing

Are you interested in contributing to SchemaPlus::ForeignKeys? Thanks! Please follow the standard protocol: fork, feature branch, develop, push, and issue pull request.

Some things to know about to help you develop and test:

  • schema_dev: SchemaPlus::ForeignKeys uses schema_dev to facilitate running rspec tests on the matrix of ruby, activerecord, and database versions that the gem supports, both locally and on github actions

To to run rspec locally on the full matrix, do:

    $ schema_dev bundle install
    $ schema_dev rspec

You can also run on just one configuration at a time; For info, see schema_dev --help or the schema_dev README.

The matrix of configurations is specified in schema_dev.yml in the project root.

  • schema_plus_core: SchemaPlus::ForeignKeys uses the SchemaPlus::Core API that provides middleware callback stacks to make it easy to extend ActiveRecord's behavior. If that API is missing something you need for your contribution, please head over to schema_plus_core and open an issue or pull request.
  • schema_monkey: SchemaPlus::ForeignKeys is implemented as a schema_monkey client, using schema_monkey's convention-based protocols for extending ActiveRecord and using middleware stacks.