0.01
No commit activity in last 3 years
No release in over 3 years
Annotate a Sequel Model with its expected schema and immediately identify inconsistencies.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.10
>= 0
>= 0
>= 0
~> 10.0

Runtime

>= 0
 Project Readme

ModelSchema Build Status

ModelSchema lets you annotate a Sequel Model with its expected schema and immediately identify inconsistencies. Instead of seeing a Sequel Model file that looks like this:

class User < Sequel::Model(:users)
end

You'll see one that looks like this:

class User < Sequel::Model(:users)
  model_schema do
    primary_key :id

    String :email, :null => false
    String :password, :null => false

    TrueClass :is_admin, :default => false

    DateTime :created_at, :null => false
    DateTime :updated_at

    index :email
  end
end

Unlike other similar gems, ModelSchema provides enforcement; if the schema you specify doesn't match the table schema, ModelSchema will raise an error and tell you exactly what's wrong, like so:

ModelSchema::SchemaError: Table users does not match the expected schema.

Table users has extra columns:

  Integer :age

Table users is missing columns:

  TrueClass :is_admin, :default => false

Table users has mismatched indexes:

  actual:    index [:email], :unique => true
  expected:  index [:email]

You may disable schema checks by passing :disable => true to model_schema or by
setting the ENV variable DISABLE_MODEL_SCHEMA=1.

When developing on a team, local databases of team members can easily get out of sync due to differing migrations. ModelSchema immediately lets you know if the schema you expect differs from the actual schema. This ensures you identify database inconsistencies before they cause problems. As a nice added benefit, ModelSchema lets you see a list of columns for a model directly within the class itself.

Installation

Add model_schema to your Gemfile:

gem 'model_schema'

And then execute bundle in your terminal. You can also install model_schema with gem directly by running gem install model_schema.

Usage

Require model_schema and register the plugin with Sequel:

require 'model_schema'
Sequel::Model.plugin(ModelSchema::Plugin)

Then, in each model where you'd like to use ModelSchema, introduce a call to model_schema, passing in a block that defines the schema. The block operates exactly as a Sequel create_table block. See the documentation on that page for further details.

class Post < Sequel::Model(:posts)
  model_schema do
    primary_key :id

    String :title, :null => false
    String :description, :text => true, :null => false
    DateTime :date_posted, :null => false
  end
end

When the class is loaded, ModelSchema will ensure the table schema matches the given schema. If there are any errors, it will raise a ModelSchema::SchemaError, notifying you of any inconsistencies.

You may pass an optional hash to model_schema with the following options:

disable: true to disable all schema checks, false otherwise
no_indexes: true to disable schema checks for indexes (columns will still be checked), false otherwise

For instance, to disable index checking:

class Item < Sequel::Model(:items)
  model_schema(:no_indexes => true) do
    ...
  end
end

Note that you can disable ModelSchema in two ways: either pass :disable => true to the model_schema method, or set the environment variable DISABLE_MODEL_SCHEMA=1 .

Bootstrap Existing Project

To help bootstrap existing projects that don't yet use ModelSchema, you can use the dump_model_schema executable. It will automatically dump an up-to-date model_schema block in each Sequel Model class. Use it like so:

$ dump_model_schema -c [connection_string] model_file [model_file ...]

where model_file is a path to a Ruby file that contains a single Sequel Model, and connection_string is the database connection string to pass to Sequel.connect().

dump_model_schema will insert a model_schema block right after the definition of the Sequel Model class in model_file. Specifically, it looks for a line of the form class SomeClassName < Sequel::Model(:table_name), and inserts a valid schema for table table_name directly after that line. Note that dump_model_schema overwrites model_file.

For instance, say you had a file items.rb that looks like this:

module SomeModule
  class Item < Sequel::Model(:items)
  end
end

If you run:

$ dump_model_schema -c [connection_string] items.rb

items.rb might now look like:

module SomeModule
  class Item < Sequel::Model(:items)
    model_schema do
      primary_key :id,

      String :name, :null => false
      Integer :quantity

      index [:name], :name => :items_name_key
    end
  end
end

By default, dump_model_schema assumes a tab size of 2 spaces, but you can change this with the -t option. Pass an integer representing the number of spaces, or 0 if you want to use hard tabs.

You may specify multiple model_files as distinct arguments, and each will have its model_schema dumped. This can be done easily with shell expansion:

$ dump_model_schema -c [connection_string] models/*.rb

You may see help text with dump_model_schema -h and view the version of ModelSchema with dump_model_schema -v.

Limitations

ModelSchema has a few notable limitations:

  • It checks columns independently from indexes. Say you create a table like so:

    DB.create_table(:items) do
      String :name, :unique => true
      Integer :value, :index => true
    end

    The corresponding model_schema block would be:

    class Item < Sequel::Model(:items)
      model_schema do
        String :name
        Integer :value
    
        index :name, :unique => true
        index :value
      end
    end

    You have to separate the columns from the indexes, since the schema dumper reads them independently of one another.

  • It relies on Sequel's schema dumper extension to read your table's schema. The schema dumper doesn't read constraints, triggers, special index types (e.g. gin, gist) or partial indexes; you'll have to omit these from your model_schema block.

  • It doesn't handle all type aliasing. For instance, the Postgres types character varying(255)[] and varchar(255)[] are equivalent, but ModelSchema is unaware of this. In turn, you might see this error message:

    Table complex has mismatched columns:
    
      actual:    column :advisors, "character varying(255)[]", :null=>false
      expected:  column :advisors, "varchar(255)[]", :null=>false
    

    In the above case, you'll need to change varchar(255)[] to character varying(255)[] in your model_schema block to fix the issue.

    A similar problem occurs with numeric(x, 0) and numeric(x), where x is an integer; they are equivalent in Postgres, but ModelSchema doesn't know this.

Support for ActiveRecord

Currently, ModelSchema only works with Sequel. If you'd like something similar for other Ruby ORMs, like ActiveRecord, please express your interest in this issue.

Development and Contributing

After cloning this repository, execute bundle to install dependencies. You may run tests with rake test, and open up a REPL using bin/repl.

Note that tests require access to a Postgres database. Set the environment variable DB_URL to a Postgres connection string (e.g. postgres://localhost:5432/model_schema) prior to running tests. See connecting to a database for information about connection strings.

To install this gem onto your local machine, run bundle exec rake install.

Any bug reports and pull requests are welcome.

License

See the LICENSE.txt file.