Skip to content

Schema analysis DSL

Vsevolod Romashov edited this page Mar 13, 2019 · 9 revisions

After applying the schema to your database DbSchema exposes it's current structure as a DbSchema::Definitions::Schema object at DbSchema.current_schema.

schema = DbSchema.current_schema

This method does not query the database in any way, it just returns a pre-cached object.

DbSchema::Definitions::Schema instance is a nested structure describing all tables, fields, indexes, enum types etc; you can inspect them in various ways.

Tables

You can get a table definition with #table:

schema.table(:users)
# or just
schema[:users]

If you just need to check if a given table is present you can use #has_table?:

schema.has_table?(:users)

Fields

Table's #field method returns a field definition:

schema.table(:users).field(:email)
# or
schema[:users][:email]

Fields have a set of attributes you can query:

field = schema.table(:users).field(:email)

field.name         # this field's name
field.null?        # is the field nullable (NOT NULL fields return false)
field.type         # the field's type as a symbol
field.default      # the field's default value
field.attributes   # the field's type-specific attributes

#has_field? can be used to check if a table has a given field:

schema.table(:users).has_field?(:email)

Indexes

Index definition may be obtained with a Table#index method and queried for their attributes:

index = schema.table(:users).index(:users_email_index)

index.name      # name of the index (in case you wonder)
index.primary?  # is the index a primary key
index.unique?   # is the index unique
index.type      # the type of this index as a symbol (:btree, :gist etc)
index.condition # condition of the index (if it is a partial index)

A special case is the primary key index; you can get it with Table#primary_key:

pkey = schema.table(:users).primary_key
pkey.primary? # => true

There are several methods that check an index existance:

# check index by it's name
schema.table(:users).has_index?(:users_email_index)

# check if table has any index on given columns (exactly in that order)
schema.table(:users).has_index_on?(:email)
schema.table(:users).has_index_on?(:first_name, :last_name)

# check if table has a unique index on given columns
schema.table(:users).has_unique_index_on?(:first_name, :last_name)

# check if table has a primary key
schema.table(:users).has_primary_key?

#has_index_on? and #has_unique_index_on? only search for indexes on table columns; they ignore expression indexes like lower(email).

Index columns

Indexed columns and expressions can also be queried for their attributes:

index = schema.table(:users).index(:users_email_index)

index.columns.first.name        # name of the field (e.g. :name) or full expression string (like "lower(email)")
index.columns.first.expression? # is it an expression
index.columns.first.asc?        # is the column order ascending in the index
index.columns.first.desc?       # is the column order descending in the index

Check constraints

Check constraints can be searched for with #check:

check = schema.table(:users).check(:valid_age)

check.name      # the name of the constraint
check.condition # the SQL condition string

As before, #has_check? can be used to check if a check constraint with the given name exists in that table.

Foreign keys

Foreign key can be picked by it's name with #foreign_key:

fkey = schema.table(:posts).foreign_key(:posts_user_id_fkey)

fkey.name                    # key's name
fkey.fields                  # array of fields in the key
fkey.table                   # name of the table referenced by the key
fkey.references_primary_key? # is the foreign key referencing a primary key
fkey.keys                    # array of the referenced columns (unless a primary key is referenced, in which case an empty array is returned)
fkey.on_update               # action to make when referenced columns are updated
fkey.on_delete               # action to make when referenced records are deleted
fkey.deferrable?             # is the foreign key constraint deferrable

If you need to check if a table has a specific foreign key you have 2 options:

# check foreign key by name
schema.table(:posts).has_foreign_key?(:posts_user_id_fkey)

# check if a table is referencing another table with a foreign key
schema.table(:posts).has_foreign_key_to?(:users)

Enum types

You can get the enum type information with Schema#enum:

user_role = schema.enum(:user_role)

user_role.name   # type's name
user_role.values # type's values (as an array of symbols)

This can be handy if you want to define model methods dynamically:

class User < SomeORM::BaseClass
  class << self
    def roles
      DbSchema.current_schema.enum(:user_role).values
    end
  end

  roles.each do |role|
    define_method("#{role}?") do # def admin?
      self.role == role          #   self.role == :admin
    end                          # end
  end
end

You can check if an enum type exists with #has_enum?:

schema.has_enum?(:user_role)

Extensions

Extensions don't have any attributes so you can only check if an extension is enabled:

schema.has_extension?(:hstore)

Null objects

If methods like #table or #field can't find an object with a given name in your schema they return a null object that has a fully compatible interface with a usual object (e.g. NullTable has the same methods as a usual Table) but it's methods return nils, empty structures and other null objects so you can safely chain your method calls without continuous nil-checking. This concerns #table, #field, #index, #check, #foreign_key, and #enum methods.

schema.table(:users).field(:bar).name            # => nil
schema.table(:foo).field(:bar).name              # => nil
schema.table(:foo).index(:foo_bar_index).columns # => []
schema.enum(:baz).values                         # => []