db_schema 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Guardfile +17 -0
- data/LICENSE.txt +21 -0
- data/README.md +522 -0
- data/Rakefile +6 -0
- data/bin/console +10 -0
- data/bin/setup +8 -0
- data/db_schema.gemspec +35 -0
- data/lib/db_schema.rb +125 -0
- data/lib/db_schema/awesome_print.rb +246 -0
- data/lib/db_schema/changes.rb +396 -0
- data/lib/db_schema/configuration.rb +29 -0
- data/lib/db_schema/definitions.rb +122 -0
- data/lib/db_schema/definitions/field.rb +38 -0
- data/lib/db_schema/definitions/field/array.rb +19 -0
- data/lib/db_schema/definitions/field/base.rb +90 -0
- data/lib/db_schema/definitions/field/binary.rb +9 -0
- data/lib/db_schema/definitions/field/bit_string.rb +15 -0
- data/lib/db_schema/definitions/field/boolean.rb +9 -0
- data/lib/db_schema/definitions/field/character.rb +19 -0
- data/lib/db_schema/definitions/field/custom.rb +22 -0
- data/lib/db_schema/definitions/field/datetime.rb +30 -0
- data/lib/db_schema/definitions/field/geometric.rb +33 -0
- data/lib/db_schema/definitions/field/json.rb +13 -0
- data/lib/db_schema/definitions/field/monetary.rb +9 -0
- data/lib/db_schema/definitions/field/network.rb +17 -0
- data/lib/db_schema/definitions/field/numeric.rb +30 -0
- data/lib/db_schema/definitions/field/range.rb +29 -0
- data/lib/db_schema/definitions/field/text_search.rb +13 -0
- data/lib/db_schema/definitions/field/uuid.rb +9 -0
- data/lib/db_schema/dsl.rb +145 -0
- data/lib/db_schema/reader.rb +270 -0
- data/lib/db_schema/runner.rb +220 -0
- data/lib/db_schema/utils.rb +50 -0
- data/lib/db_schema/validator.rb +89 -0
- data/lib/db_schema/version.rb +3 -0
- metadata +239 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8dbf606aae084d2032ffbfb12940cfee0d0b9ad3
|
4
|
+
data.tar.gz: e354a7f339af08dadfba3b2a9bd505be2603778f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6777fcffd1c201b885ae98ff4b3c44bd64b0d3fcc7585a06ae5a9a966b874a7a96070c4fb428f3762ebf5ea68e4d8ecd688a496d5076328a539e4c348ded62b8
|
7
|
+
data.tar.gz: 54a567aeb035b438447b7a70aa0837a6d84974f400f606820b53b4046e192189ab54999c61a439e8a4e7739114936f6f26af4d2ea26ed00002c2290ddcb58e1f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
guard :rspec, cmd: 'bundle exec rspec', all_on_start: true do
|
2
|
+
require 'guard/rspec/dsl'
|
3
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
4
|
+
|
5
|
+
# RSpec files
|
6
|
+
rspec = dsl.rspec
|
7
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
8
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
9
|
+
watch(rspec.spec_files)
|
10
|
+
|
11
|
+
# Ruby files
|
12
|
+
ruby = dsl.ruby
|
13
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
14
|
+
watch(%r{lib/db_schema/definitions\.rb}) { rspec.spec_dir }
|
15
|
+
watch(%r{lib/db_schema/definitions/.*\.rb}) { rspec.spec_dir }
|
16
|
+
watch('lib/db_schema/utils.rb') { rspec.spec_dir }
|
17
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Vsevolod Romashov
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,522 @@
|
|
1
|
+
# DbSchema
|
2
|
+
|
3
|
+
DbSchema is an opinionated database schema management tool that lets you maintain your DB schema with a single ruby file.
|
4
|
+
|
5
|
+
It works like this:
|
6
|
+
|
7
|
+
* you create a `schema.rb` file where you describe the schema you want in a special DSL
|
8
|
+
* you make your application load this file as early as possible during the application bootup in development and test environments
|
9
|
+
* you create a rake task that loads your `schema.rb` and tell your favorite deployment tool to run it on each deploy
|
10
|
+
* each time you need to change the schema you just change the `schema.rb` file and commit it to your VCS
|
11
|
+
|
12
|
+
As a result you always have an up-to-date database schema. No need to run and rollback migrations, no need to even think about the extra step - DbSchema compares the schema you want with the schema your database has and applies all necessary changes to the latter. This operation is [idempotent](https://en.wikipedia.org/wiki/Idempotence) - if DbSchema sees that the database already has the requested schema it does nothing.
|
13
|
+
|
14
|
+
*Currently DbSchema only supports PostgreSQL.*
|
15
|
+
|
16
|
+
## Reasons to use
|
17
|
+
|
18
|
+
With DbSchema you almost never need to write migrations by hand and manage a collection of migration files.
|
19
|
+
This gives you a list of important benefits:
|
20
|
+
|
21
|
+
* no more `YouHaveABunchOfPendingMigrations` errors - all needed operations are computed from the differences between the schema definition and the actual database schema
|
22
|
+
* no need to write separate :up and :down migrations - this is all handled automatically
|
23
|
+
* there is no `structure.sql` with a database dump that constantly changes without reason
|
24
|
+
|
25
|
+
But the main reason of DbSchema existence is the pain of switching
|
26
|
+
between long-running VCS branches with different migrations
|
27
|
+
without resetting the database. Have you ever switched
|
28
|
+
to a different branch only to see something like this?
|
29
|
+
|
30
|
+
![](https://cloud.githubusercontent.com/assets/351591/17085038/7da81118-51d6-11e6-91d9-99885235d037.png)
|
31
|
+
|
32
|
+
Yeah, you must remember the oldest `NO FILE` migration,
|
33
|
+
switch back to the previous branch,
|
34
|
+
roll back every migration up to that `NO FILE`,
|
35
|
+
then switch the branch again and migrate these `down` migrations.
|
36
|
+
Every migration or rollback loads the whole app, resulting in 10+ seconds wasted.
|
37
|
+
And at the end of it all you are trying to recall why did you ever
|
38
|
+
want to switch to that branch.
|
39
|
+
|
40
|
+
DbSchema does not rely on migration files and/or `schema_migrations` table in the database
|
41
|
+
so it seamlessly changes the schema to the one defined in the branch you switched to.
|
42
|
+
There is no step 2.
|
43
|
+
|
44
|
+
Of course if you are switching from a branch that defines table A to a branch
|
45
|
+
that doesn't define table A then you lose that table with all the data in it.
|
46
|
+
But you would lose it even with manual migrations.
|
47
|
+
|
48
|
+
## Installation
|
49
|
+
|
50
|
+
Add this line to your application's Gemfile:
|
51
|
+
|
52
|
+
``` ruby
|
53
|
+
gem 'db_schema'
|
54
|
+
```
|
55
|
+
|
56
|
+
And then execute:
|
57
|
+
|
58
|
+
``` sh
|
59
|
+
$ bundle
|
60
|
+
```
|
61
|
+
|
62
|
+
Or install it yourself as:
|
63
|
+
|
64
|
+
``` sh
|
65
|
+
$ gem install db_schema
|
66
|
+
```
|
67
|
+
|
68
|
+
## Usage
|
69
|
+
|
70
|
+
You define your schema with a special DSL; you can put it in `db/schema.rb` file or anywhere you want. Be sure to keep this file under version control.
|
71
|
+
|
72
|
+
DbSchema DSL looks like this:
|
73
|
+
|
74
|
+
``` ruby
|
75
|
+
DbSchema.describe do |db|
|
76
|
+
db.table :users do |t|
|
77
|
+
t.primary_key :id
|
78
|
+
t.varchar :email, null: false
|
79
|
+
t.varchar :password_digest, length: 40
|
80
|
+
t.timestamptz :created_at
|
81
|
+
t.timestamptz :updated_at
|
82
|
+
|
83
|
+
t.index :email, unique: true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
Before DbSchema connects to the database you need to configure it:
|
89
|
+
|
90
|
+
``` ruby
|
91
|
+
DbSchema.configure(adapter: 'postgresql', database: 'my_database', user: 'bob', password: 'secret')
|
92
|
+
# or in Rails
|
93
|
+
DbSchema.configure_from_yaml(Rails.root.join('config', 'database.yml'), Rails.env)
|
94
|
+
```
|
95
|
+
|
96
|
+
Then you can load your schema definition (it is executable - it instantly applies itself to your database):
|
97
|
+
|
98
|
+
``` ruby
|
99
|
+
load 'path/to/schema.rb'
|
100
|
+
```
|
101
|
+
|
102
|
+
In order to get an always-up-to-date database schema in development and test environments you need to load the schema definition when your application is starting up. For instance, in Rails an initializer would be a good place to do that.
|
103
|
+
|
104
|
+
On the other hand, in production environment this can cause race condition problems as your schema can be applied concurrently by different worker processes (this also applies to staging and any other environments where the application is being run by multi-worker servers); therefore it is wiser to disable schema auto loading in such environments and run it from a rake task on each deploy.
|
105
|
+
|
106
|
+
Here's an initializer example for a Rails app:
|
107
|
+
|
108
|
+
``` ruby
|
109
|
+
# config/initializers/db_schema.rb
|
110
|
+
DbSchema.configure_from_yaml(Rails.root.join('config', 'database.yml'), Rails.env)
|
111
|
+
|
112
|
+
if Rails.env.development? || Rails.env.test?
|
113
|
+
load Rails.root.join('db', 'schema.rb')
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
And the rake task:
|
118
|
+
|
119
|
+
``` ruby
|
120
|
+
# lib/tasks/db_schema.rake
|
121
|
+
namespace :db do
|
122
|
+
namespace :schema do
|
123
|
+
desc 'Apply database schema'
|
124
|
+
task apply: :environment do
|
125
|
+
load Rails.root.join('db', 'schema.rb')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
Then you just call `rake db:schema:apply` from your deploy script before restarting the app.
|
132
|
+
|
133
|
+
### DSL
|
134
|
+
|
135
|
+
Database schema is defined with a block passed to `DbSchema.describe` method.
|
136
|
+
This block receives a `db` object on which you can call `#table` to define a table
|
137
|
+
and `#enum` to define a custom enum type. Everything that belongs to a specific table
|
138
|
+
is described in a block passed to `#table`.
|
139
|
+
|
140
|
+
``` ruby
|
141
|
+
DbSchema.describe do |db|
|
142
|
+
db.table :users do |t|
|
143
|
+
t.primary_key :id
|
144
|
+
t.varchar :email, null: false
|
145
|
+
t.varchar :password, null: false
|
146
|
+
t.varchar :name, null: false
|
147
|
+
t.integer :age
|
148
|
+
t.user_status :status, null: false, default: 'registered'
|
149
|
+
|
150
|
+
t.index :email, unique: true
|
151
|
+
end
|
152
|
+
|
153
|
+
db.enum :user_status, [:registered, :confirmed_email, :subscriber]
|
154
|
+
|
155
|
+
db.table :posts do |t|
|
156
|
+
t.primary_key :id
|
157
|
+
t.integer :user_id, null: false
|
158
|
+
t.varchar :title, null: false, length: 50
|
159
|
+
t.text :content
|
160
|
+
t.array :tags, of: :varchar
|
161
|
+
|
162
|
+
t.index :user_id
|
163
|
+
t.foreign_key :user_id, references: :users
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
#### Tables
|
169
|
+
|
170
|
+
Tables are described with the `#table` method; you pass it the name of the table and describe the table structure in the block:
|
171
|
+
|
172
|
+
``` ruby
|
173
|
+
db.table :users do |t|
|
174
|
+
t.varchar :email
|
175
|
+
t.varchar :password
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
##### Fields
|
180
|
+
|
181
|
+
You can define a field of any type by calling the corresponding method inside the table block passing it the field name and it's attributes. Most of the attributes are optional.
|
182
|
+
|
183
|
+
Here's an example table with various kinds of data:
|
184
|
+
|
185
|
+
``` ruby
|
186
|
+
db.table :people do |t|
|
187
|
+
t.varchar :first_name, length: 50, null: false
|
188
|
+
t.varchar :last_name, length: 60, null: false
|
189
|
+
t.integer :age
|
190
|
+
t.numeric :salary, precision: 10, scale: 2
|
191
|
+
t.text :about
|
192
|
+
t.date :birthday
|
193
|
+
t.boolean :developer
|
194
|
+
t.inet :ip_address
|
195
|
+
t.jsonb :preferences, default: '{}'
|
196
|
+
t.array :interests, of: :varchar
|
197
|
+
t.numrange :salary_expectations
|
198
|
+
|
199
|
+
t.timestamptz :created_at
|
200
|
+
t.timestamptz :updated_at
|
201
|
+
end
|
202
|
+
```
|
203
|
+
|
204
|
+
Passing `null: false` to the field definition makes it `NOT NULL`; passing some value under the `:default` key makes it the default value. A symbol passed as a default is interpreted as a function call so `t.timestamp :created_at, default: :now` defines a field with a default value of `NOW()`; strings, numbers, timestamps etc are evaluated "as is".
|
205
|
+
|
206
|
+
Other attributes are type specific, like `:length` for varchars; the following table lists them all (values in parentheses are default attribute values).
|
207
|
+
|
208
|
+
| Type | Attributes |
|
209
|
+
| ------------- | ---------------- |
|
210
|
+
| `smallint` | |
|
211
|
+
| `integer` | |
|
212
|
+
| `bigint` | |
|
213
|
+
| `numeric` | precision, scale |
|
214
|
+
| `real` | |
|
215
|
+
| `float` | |
|
216
|
+
| `money` | |
|
217
|
+
| `char` | length(1) |
|
218
|
+
| `varchar` | length |
|
219
|
+
| `text` | |
|
220
|
+
| `bytea` | |
|
221
|
+
| `timestamp` | |
|
222
|
+
| `timestamptz` | |
|
223
|
+
| `date` | |
|
224
|
+
| `time` | |
|
225
|
+
| `timetz` | |
|
226
|
+
| `interval` | fields |
|
227
|
+
| `boolean` | |
|
228
|
+
| `point` | |
|
229
|
+
| `line` | |
|
230
|
+
| `lseg` | |
|
231
|
+
| `box` | |
|
232
|
+
| `path` | |
|
233
|
+
| `polygon` | |
|
234
|
+
| `circle` | |
|
235
|
+
| `cidr` | |
|
236
|
+
| `inet` | |
|
237
|
+
| `macaddr` | |
|
238
|
+
| `bit` | length(1) |
|
239
|
+
| `varbit` | length |
|
240
|
+
| `tsvector` | |
|
241
|
+
| `tsquery` | |
|
242
|
+
| `uuid` | |
|
243
|
+
| `json` | |
|
244
|
+
| `jsonb` | |
|
245
|
+
| `array` | of |
|
246
|
+
| `int4range` | |
|
247
|
+
| `int8range` | |
|
248
|
+
| `numrange` | |
|
249
|
+
| `tsrange` | |
|
250
|
+
| `tstzrange` | |
|
251
|
+
| `daterange` | |
|
252
|
+
|
253
|
+
The `of` attribute of the array type is the only required attribute (you need to specify the array element type here); other attributes either have default values or can be omitted at all.
|
254
|
+
|
255
|
+
You can also use your custom types in the same way: `t.user_status :status` creates a field called `status` with `user_status` type. Custom types are explained in a later section of this document.
|
256
|
+
|
257
|
+
Primary key is a special case; currently when you create a primary key with DbSchema you get a NOT NULL autoincrementing (by a sequence) integer field with a primary key constraint. There is no way to change the primary key field type or make a complex primary key at the moment; this is planned for future versions of DbSchema.
|
258
|
+
|
259
|
+
Primary keys are created with the `#primary_key` method:
|
260
|
+
|
261
|
+
``` ruby
|
262
|
+
db.table :posts do |t|
|
263
|
+
t.primary_key :id
|
264
|
+
t.varchar :title
|
265
|
+
end
|
266
|
+
```
|
267
|
+
|
268
|
+
**Important: you can't rename a table or a column just by changing it's name in the schema definition - this will result in a column with the old name being deleted and a column with the new name being added; all data in that table or column will be lost.**
|
269
|
+
|
270
|
+
##### Indexes
|
271
|
+
|
272
|
+
Indexes are created using the `#index` method: you pass it the field name you want to index:
|
273
|
+
|
274
|
+
``` ruby
|
275
|
+
db.table :users do |t|
|
276
|
+
t.varchar :email
|
277
|
+
t.index :email
|
278
|
+
end
|
279
|
+
```
|
280
|
+
|
281
|
+
Unique indexes are created with `unique: true`:
|
282
|
+
|
283
|
+
``` ruby
|
284
|
+
t.index :email, unique: true
|
285
|
+
```
|
286
|
+
|
287
|
+
Passing several field names makes a multiple index:
|
288
|
+
|
289
|
+
``` ruby
|
290
|
+
db.table :users do |t|
|
291
|
+
t.varchar :first_name
|
292
|
+
t.varchar :last_name
|
293
|
+
|
294
|
+
t.index :first_name, :last_name
|
295
|
+
end
|
296
|
+
```
|
297
|
+
|
298
|
+
If you want to specify a custom name for your index, you can pass it in the `:name` option:
|
299
|
+
|
300
|
+
``` ruby
|
301
|
+
t.index :first_name, :last_name, name: :username_index
|
302
|
+
```
|
303
|
+
|
304
|
+
Otherwise the index name will be generated as `"#{table_name}_#{field_names.join('_')}_index"` so the index above will be called `users_first_name_last_name_index`.
|
305
|
+
|
306
|
+
You can specify the order of each field in your index - it's either `ASC` (`:asc`, the default), `DESC` (`:desc`), `ASC NULLS FIRST` (`:asc_nulls_first`), or `DESC NULLS LAST` (`:desc_nulls_last`). It looks like this:
|
307
|
+
|
308
|
+
``` ruby
|
309
|
+
db.table :some_table do |t|
|
310
|
+
t.integer :col1
|
311
|
+
t.integer :col2
|
312
|
+
t.integer :col3
|
313
|
+
t.integer :col4
|
314
|
+
|
315
|
+
t.index col1: :asc, col2: :desc, col3: :asc_nulls_first, col4: :desc_nulls_last
|
316
|
+
end
|
317
|
+
```
|
318
|
+
|
319
|
+
By default B-tree indexes are created; if you need an index of a different type you can pass it in the `:using` option:
|
320
|
+
|
321
|
+
``` ruby
|
322
|
+
db.table :users do |t|
|
323
|
+
t.array :interests, of: :varchar
|
324
|
+
t.index :interests, using: :gin
|
325
|
+
end
|
326
|
+
```
|
327
|
+
|
328
|
+
You can also create a partial index if you pass some condition as SQL string in the `:where` option:
|
329
|
+
|
330
|
+
``` ruby
|
331
|
+
db.table :users do |t|
|
332
|
+
t.varchar :email
|
333
|
+
t.index :email, unique: true, where: 'email IS NOT NULL'
|
334
|
+
end
|
335
|
+
```
|
336
|
+
|
337
|
+
Be warned though that you have to specify the condition exactly as PostgreSQL outputs it in `psql` with `\d table_name` command; otherwise your index will be recreated on each DbSchema run. This will be fixed in a later DbSchema version.
|
338
|
+
|
339
|
+
##### Foreign keys
|
340
|
+
|
341
|
+
The `#foreign_key` method defines a foreign key. In it's minimal form it takes a referencing field name and referenced table name:
|
342
|
+
|
343
|
+
``` ruby
|
344
|
+
db.table :users do |t|
|
345
|
+
t.primary_key :id
|
346
|
+
t.varchar :name
|
347
|
+
end
|
348
|
+
|
349
|
+
db.table :posts do |t|
|
350
|
+
t.integer :user_id
|
351
|
+
t.varchar :title
|
352
|
+
|
353
|
+
t.foreign_key :user_id, references: :users
|
354
|
+
end
|
355
|
+
```
|
356
|
+
|
357
|
+
The syntax above assumes that this foreign key references the primary key. If you need to reference another field you can pass a 2-element array in `:references` option, the first element being table name and the second being field name:
|
358
|
+
|
359
|
+
``` ruby
|
360
|
+
db.table :users do |t|
|
361
|
+
t.varchar :name
|
362
|
+
t.index :name, unique: true # you can only reference either primary keys or unique columns
|
363
|
+
end
|
364
|
+
|
365
|
+
db.table :posts do |t|
|
366
|
+
t.varchar :username
|
367
|
+
t.foreign_key :username, references: [:users, :name]
|
368
|
+
end
|
369
|
+
```
|
370
|
+
|
371
|
+
As with indexes, you can pass your custom name in the `:name` option; default foreign key name looks like `"#{table_name}_#{fkey_fields.first}_fkey"`.
|
372
|
+
|
373
|
+
You can also define a composite foreign key consisting of (and referencing) multiple columns; just list them all:
|
374
|
+
|
375
|
+
``` ruby
|
376
|
+
db.table :table_a do |t|
|
377
|
+
t.integer :col1
|
378
|
+
t.integer :col2
|
379
|
+
t.index :col1, :col2, unique: true
|
380
|
+
end
|
381
|
+
|
382
|
+
db.table :table_b do |t|
|
383
|
+
t.integer :a_col1
|
384
|
+
t.integer :a_col2
|
385
|
+
t.foreign_key :a_col1, :a_col2, references: [:table_a, :col1, :col2]
|
386
|
+
end
|
387
|
+
```
|
388
|
+
|
389
|
+
There are 3 more options to the `#foreign_key` method: `:on_update`, `:on_delete` and `:deferrable`. First two define an action that will be taken when a referenced column is changed or the whole referenced row is deleted, respectively; you can set these to one of `:no_action` (the default), `:restrict`, `:cascade`, `:set_null` or `:set_default`. See [PostgreSQL documentation](https://www.postgresql.org/docs/current/static/ddl-constraints.html#DDL-CONSTRAINTS-FK) for more information.
|
390
|
+
|
391
|
+
Passing `deferrable: true` defines a foreign key that is checked at the end of transaction.
|
392
|
+
|
393
|
+
##### Check constraints
|
394
|
+
|
395
|
+
A check constraint is like a validation on the database side: it checks if the inserted/updated row has valid values.
|
396
|
+
|
397
|
+
To define a check constraint you can use the `#check` method passing it the constraint name (no auto-generated names here, sorry) and the condition that must be satisfied, in a form of SQL string.
|
398
|
+
|
399
|
+
``` ruby
|
400
|
+
db.table :users do |t|
|
401
|
+
t.primary_key :id
|
402
|
+
t.varchar :name
|
403
|
+
t.integer :age, null: false
|
404
|
+
|
405
|
+
t.check :valid_age, 'age >= 18'
|
406
|
+
end
|
407
|
+
```
|
408
|
+
|
409
|
+
As with partial index conditions, for now you have to specify the SQL exactly as `psql` outputs it (otherwise the constraint will be recreated on each run).
|
410
|
+
|
411
|
+
#### Enum types
|
412
|
+
|
413
|
+
PostgreSQL allows developers to create custom enum types; value of enum type is one of a fixed set of values stored in the type definition.
|
414
|
+
|
415
|
+
Enum types are declared with the `#enum` method (note that you must call it from the top level of your schema and not from within some table definition):
|
416
|
+
|
417
|
+
``` ruby
|
418
|
+
db.enum :user_status, [:registered, :confirmed_email]
|
419
|
+
```
|
420
|
+
|
421
|
+
Then you can create fields of that type exactly as you would create a field of any built-in type - just call the method with the same name as the type you defined:
|
422
|
+
|
423
|
+
``` ruby
|
424
|
+
db.table :users do |t|
|
425
|
+
t.user_status :status, default: 'registered'
|
426
|
+
end
|
427
|
+
```
|
428
|
+
|
429
|
+
After the enum type is created, you can add more values to it. They don't have to appear in the end of the values list - you can add new values to the middle or even to the beginning of the list:
|
430
|
+
|
431
|
+
``` ruby
|
432
|
+
db.enum :user_status, [:guest, :registered, :sent_confirmation_email, :confirmed_email, :subscriber]
|
433
|
+
```
|
434
|
+
|
435
|
+
Reordering and deleting values from enum types is not supported.
|
436
|
+
|
437
|
+
### Configuration
|
438
|
+
|
439
|
+
DbSchema must be configured prior to applying the schema. There are 2 methods you can use for that: `configure` and `configure_from_yaml`.
|
440
|
+
|
441
|
+
#### DbSchema.configure
|
442
|
+
|
443
|
+
`configure` is a generic method that receives a hash with all configuration options:
|
444
|
+
|
445
|
+
``` ruby
|
446
|
+
DbSchema.configure(
|
447
|
+
adapter: 'postgresql',
|
448
|
+
host: ENV['db_host'],
|
449
|
+
port: ENV['db_port'],
|
450
|
+
database: ENV['db_name'],
|
451
|
+
user: ENV['db_user'],
|
452
|
+
password: ENV['db_password']
|
453
|
+
)
|
454
|
+
```
|
455
|
+
|
456
|
+
#### DbSchema.configure_from_yaml
|
457
|
+
|
458
|
+
`configure_from_yaml` is designed to use with Rails so you don't have to duplicate database connection settings from your `database.yml` in DbSchema configuration. Pass it the full path to your `database.yml` file and your current application environment (`development`, `production` etc), and it will read the db connection settings from that file.
|
459
|
+
|
460
|
+
``` ruby
|
461
|
+
DbSchema.configure_from_yaml(Rails.root.join('config', 'database.yml'), Rails.env)
|
462
|
+
```
|
463
|
+
|
464
|
+
If you need to specify other options you can simply pass them as keyword arguments after the environment:
|
465
|
+
|
466
|
+
``` ruby
|
467
|
+
DbSchema.configure_from_yaml(
|
468
|
+
Rails.root.join('config', 'database.yml'),
|
469
|
+
Rails.env,
|
470
|
+
dry_run: true
|
471
|
+
)
|
472
|
+
```
|
473
|
+
|
474
|
+
#### Configuration options
|
475
|
+
|
476
|
+
All configuration options are described in the following table:
|
477
|
+
|
478
|
+
| Option | Default value | Description |
|
479
|
+
| ----------- | ------------- | ------------------------------------------------ |
|
480
|
+
| adapter | `'postgres'` | Database adapter |
|
481
|
+
| host | `'localhost'` | Database host |
|
482
|
+
| port | `5432` | Database port |
|
483
|
+
| database | (no default) | Database name |
|
484
|
+
| user | `nil` | Database user |
|
485
|
+
| password | `''` | Database password |
|
486
|
+
| log_changes | `true` | When true, schema changes are logged |
|
487
|
+
| post_check | `true` | When true, database schema is checked afterwards |
|
488
|
+
| dry_run | `false` | When true, no operations are actually made |
|
489
|
+
|
490
|
+
By default DbSchema logs the changes it applies to your database; you can disable that by setting `log_changes` to false.
|
491
|
+
|
492
|
+
DbSchema provides an opt-out post-run schema check; it ensures that there are no remaining differences between your `schema.rb` and the actual database schema. If DbSchema still sees any differences it will keep applying them on each run - usually this is harmless (because it does not really change your schema) but in the case of a partial index with a complex condition it may rebuild the index which is an expensive operation on a large table. You can set `post_check` to false if you are 100% sure that your persistent changes are not a problem for you but I strongly recommend that you turn it on from time to time just to make sure nothing dangerous appears in these persistent changes.
|
493
|
+
|
494
|
+
The `post_check` option is likely to become off by default when DbSchema becomes more stable and battle-tested, and when the partial index problem will be solved.
|
495
|
+
|
496
|
+
There is also a dry run mode which does not apply the changes to your database - it just logs the necessary changes (if you leave `log_changes` set to `true`). Post check is also skipped in that case.
|
497
|
+
|
498
|
+
Dry run may be useful while you are building your schema definition for an existing app; adjust your `schema.rb` and apply it in dry run mode until it fits your database and next dry run doesn't report any changes. Don't forget to turn `dry_run` off afterwards!
|
499
|
+
|
500
|
+
## Known problems and limitations
|
501
|
+
|
502
|
+
* primary keys are hardcoded to a single NOT NULL integer field with a postgres sequence attached
|
503
|
+
* "partial index problem": some conditions of partial indexes and check constraints can cause
|
504
|
+
a false positive result of checking for differences between `schema.rb` and actual database schema,
|
505
|
+
resulting in unwanted operations on each run (the worst of them being the recreation of an index
|
506
|
+
on a large table)
|
507
|
+
* array element type attributes are not supported
|
508
|
+
* precision in time & datetime types isn't supported
|
509
|
+
* no support for databases other than PostgreSQL
|
510
|
+
* no support for renaming tables & columns
|
511
|
+
|
512
|
+
## Development
|
513
|
+
|
514
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`.
|
515
|
+
|
516
|
+
## Contributing
|
517
|
+
|
518
|
+
Bug reports and pull requests are welcome on GitHub at [7even/db_schema](https://github.com/7even/db_schema).
|
519
|
+
|
520
|
+
## License
|
521
|
+
|
522
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|