Empact-sexy_pg_constraints 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +12 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +37 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +123 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/init.rb +1 -0
- data/lib/sexy_pg_constraints.rb +23 -0
- data/lib/sexy_pg_constraints/constrainer.rb +34 -0
- data/lib/sexy_pg_constraints/constraints.rb +179 -0
- data/lib/sexy_pg_constraints/deconstrainer.rb +31 -0
- data/lib/sexy_pg_constraints/helpers.rb +19 -0
- data/lib/sexy_pg_constraints/initializer.rb +7 -0
- data/sexy_pg_constraints.gemspec +32 -0
- data/test/sexy_pg_constraints_test.rb +899 -0
- data/test/support/assert_prohibits_allows.rb +28 -0
- data/test/support/database.yml.example +6 -0
- data/test/support/models.rb +36 -0
- data/test/test_helper.rb +27 -0
- metadata +146 -0
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
=== 0.1.3 - 01.19.2009
|
2
|
+
|
3
|
+
* Bugfix: positive constraint fixed from >0 to >=0. Deconstrain and constrain to reapply.
|
4
|
+
|
5
|
+
=== 0.1.2 - 11.30.2008
|
6
|
+
|
7
|
+
* New constraints: even, odd, format
|
8
|
+
|
9
|
+
=== 0.1.1 - 11.30.2008
|
10
|
+
|
11
|
+
* Added foreign key constraints support.
|
12
|
+
* Added multi-column constraints support.
|
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
gem "activerecord", ">= 3.0.0"
|
5
|
+
gem "pg"
|
6
|
+
|
7
|
+
# Add dependencies to develop your gem here.
|
8
|
+
# Include everything needed to run rake, tests, features, etc.
|
9
|
+
group :development do
|
10
|
+
gem "shoulda", ">= 0"
|
11
|
+
gem "bundler", "~> 1.0.0"
|
12
|
+
gem "jeweler", "~> 1.5.2"
|
13
|
+
gem "rcov", ">= 0"
|
14
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (3.0.7)
|
5
|
+
activesupport (= 3.0.7)
|
6
|
+
builder (~> 2.1.2)
|
7
|
+
i18n (~> 0.5.0)
|
8
|
+
activerecord (3.0.7)
|
9
|
+
activemodel (= 3.0.7)
|
10
|
+
activesupport (= 3.0.7)
|
11
|
+
arel (~> 2.0.2)
|
12
|
+
tzinfo (~> 0.3.23)
|
13
|
+
activesupport (3.0.7)
|
14
|
+
arel (2.0.9)
|
15
|
+
builder (2.1.2)
|
16
|
+
git (1.2.5)
|
17
|
+
i18n (0.5.0)
|
18
|
+
jeweler (1.5.2)
|
19
|
+
bundler (~> 1.0.0)
|
20
|
+
git (>= 1.2.5)
|
21
|
+
rake
|
22
|
+
pg (0.11.0)
|
23
|
+
rake (0.8.7)
|
24
|
+
rcov (0.9.9)
|
25
|
+
shoulda (2.11.3)
|
26
|
+
tzinfo (0.3.26)
|
27
|
+
|
28
|
+
PLATFORMS
|
29
|
+
ruby
|
30
|
+
|
31
|
+
DEPENDENCIES
|
32
|
+
activerecord (>= 3.0.0)
|
33
|
+
bundler (~> 1.0.0)
|
34
|
+
jeweler (~> 1.5.2)
|
35
|
+
pg
|
36
|
+
rcov
|
37
|
+
shoulda
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Maxim Chernyak
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
= Sexy PG Constraints
|
2
|
+
|
3
|
+
If you're on PostgreSQL and see the importance of data-layer constraints - this gem/plugin is for you. It integrates constraints into PostgreSQL adapter so you can add/remove them in your migrations. You get two simple methods for adding/removing constraints, as well as a pack of pre-made constraints.
|
4
|
+
|
5
|
+
== Install
|
6
|
+
As a gem
|
7
|
+
gem install maxim-sexy_pg_constraints --source http://gems.github.com
|
8
|
+
or as a plugin
|
9
|
+
script/plugin install git://github.com/maxim/sexy_pg_constraints.git
|
10
|
+
|
11
|
+
|
12
|
+
One more thing. Make sure that in your environment.rb file you have the following line uncommented.
|
13
|
+
|
14
|
+
config.active_record.schema_format = :sql
|
15
|
+
|
16
|
+
Otherwise your test database will not have these constraints replicated.
|
17
|
+
|
18
|
+
== Usage
|
19
|
+
|
20
|
+
=== Single-column constraints
|
21
|
+
Say you have a table "books" and you want your Postgres DB to ensure that their title is not-blank, alphanumeric, and its length is between 3 and 50 chars. You also want to make sure that their isbn is unique. In addition you want to blacklist a few isbn numbers from ever being in your database. You can tell all that to your Postgres in no time. Generate a migration and write the following.
|
22
|
+
|
23
|
+
class AddConstraintsToBooks < ActiveRecord::Migration
|
24
|
+
def self.up
|
25
|
+
constrain :books do |t|
|
26
|
+
t.title :not_blank => true, :alphanumeric => true, :length_within => 3..50
|
27
|
+
t.isbn :unique => true, :blacklist => %w(badbook1 badbook2)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.down
|
32
|
+
deconstrain :books do |t|
|
33
|
+
t.title :not_blank, :alphanumeric, :length_within
|
34
|
+
t.isbn :unique, :blacklist
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
This will add all the necessary constraints to the database on the next migration, and remove them on rollback.
|
40
|
+
|
41
|
+
There's also a syntax for when you don't need to work with multiple columns at once.
|
42
|
+
|
43
|
+
constrain :books, :title, :not_blank => true, :length_within => 3..50
|
44
|
+
|
45
|
+
The above line works exactly the same as this block
|
46
|
+
|
47
|
+
constrain :books do |t|
|
48
|
+
t.title :not_blank => true, :length_within => 3..50
|
49
|
+
end
|
50
|
+
|
51
|
+
Same applies to deconstrain.
|
52
|
+
|
53
|
+
=== Multi-column constraints
|
54
|
+
Say you have the same table "books" only now you want to tell your Postgres to make sure that you should never have the same title + author_id combination. It means that you want to apply uniqueness to two columns, not just one. There is a special syntax for working with multicolumn constraints.
|
55
|
+
|
56
|
+
class AddConstraintsToBooks < ActiveRecord::Migration
|
57
|
+
def self.up
|
58
|
+
constrain :books do |t|
|
59
|
+
t[:title, :author_id].all :unique => true # Notice how multiple columns are listed in brackets.
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.down
|
64
|
+
deconstrain :books do |t|
|
65
|
+
t[:title, :author_id].all :unique
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
It's important to note that you shouldn't mix multicolumn constraints with regular ones in one line. This may cause unexpected behavior.
|
71
|
+
|
72
|
+
=== Foreign key constrants
|
73
|
+
In our table "books" we have column "author_id" which should reference the "id" column in the "authors" table. Here's the very simple syntax for setting up foreign key constraint that will tell Postgres to enforce this relationship.
|
74
|
+
|
75
|
+
class AddConstraintsToBooks < ActiveRecord::Migration
|
76
|
+
def self.up
|
77
|
+
constrain :books do |t|
|
78
|
+
t.author_id :reference => {:authors => :id, :on_delete => :cascade} # :on_delete is optional
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.down
|
83
|
+
deconstrain :books do |t|
|
84
|
+
t.author_id :reference
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
In this example we're telling Postgres to enforce the connection of author_id to the column "id" in table "authors". However, we're also telling it to cascade on delete. This means that when an author is deleted - every book that referred to that author will be deleted as well.
|
90
|
+
|
91
|
+
== Available constraints
|
92
|
+
|
93
|
+
Below is the list of constraints available and tested so far.
|
94
|
+
|
95
|
+
* whitelist
|
96
|
+
* blacklist
|
97
|
+
* not_blank
|
98
|
+
* within
|
99
|
+
* length_within
|
100
|
+
* email
|
101
|
+
* alphanumeric
|
102
|
+
* positive
|
103
|
+
* unique
|
104
|
+
* exact_length
|
105
|
+
* reference
|
106
|
+
* even
|
107
|
+
* odd
|
108
|
+
* format
|
109
|
+
* lowercase
|
110
|
+
* xor
|
111
|
+
|
112
|
+
== Extensibility
|
113
|
+
|
114
|
+
All constraints are located in the lib/constraints.rb. Extending this module with more methods will automatically make constraints available in migrations. All methods in the Constraints module are under module_function directive. Each method is supposed to return a piece of SQL that is inserted "alter table foo add constraint bar #{RIGHT HERE};."
|
115
|
+
|
116
|
+
== TODO
|
117
|
+
|
118
|
+
* Add support for Rails schema.rb
|
119
|
+
* Create better API for adding constraints
|
120
|
+
|
121
|
+
== Contributors
|
122
|
+
* Empact[http://github.com/Empact] (Big thanks for lots of work. Better flexibility, more tests, organizing code, bug fixes.)
|
123
|
+
* look[http://github.com/look] (Extra constraints: lowercase and xor.)
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = 'Empact-sexy_pg_constraints'
|
16
|
+
gem.homepage = "http://github.com/maxim/sexy_pg_constraints"
|
17
|
+
gem.description = "Use migrations and simple syntax to manage constraints in PostgreSQL DB."
|
18
|
+
gem.email = "ben.woosley@gmail.com"
|
19
|
+
gem.authors = ["Maxim Chernyak", "Ben Woosley"]
|
20
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
21
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
22
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
23
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
24
|
+
end
|
25
|
+
Jeweler::RubygemsDotOrgTasks.new
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
Rake::TestTask.new(:test) do |test|
|
29
|
+
test.libs << 'lib' << 'test'
|
30
|
+
test.pattern = 'test/*_test.rb'
|
31
|
+
test.verbose = true
|
32
|
+
end
|
33
|
+
|
34
|
+
require 'rcov/rcovtask'
|
35
|
+
Rcov::RcovTask.new do |test|
|
36
|
+
test.libs << 'test'
|
37
|
+
test.pattern = 'test/**/test_*.rb'
|
38
|
+
test.verbose = true
|
39
|
+
end
|
40
|
+
|
41
|
+
task :default => :test
|
42
|
+
|
43
|
+
require 'rake/rdoctask'
|
44
|
+
Rake::RDocTask.new do |rdoc|
|
45
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
46
|
+
|
47
|
+
rdoc.rdoc_dir = 'rdoc'
|
48
|
+
rdoc.title = "test #{version}"
|
49
|
+
rdoc.rdoc_files.include('README*')
|
50
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
51
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'sexy_pg_constraints'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'sexy_pg_constraints/initializer'
|
2
|
+
require "sexy_pg_constraints/helpers"
|
3
|
+
require "sexy_pg_constraints/constrainer"
|
4
|
+
require "sexy_pg_constraints/deconstrainer"
|
5
|
+
require "sexy_pg_constraints/constraints"
|
6
|
+
|
7
|
+
module SexyPgConstraints
|
8
|
+
def constrain(*args)
|
9
|
+
if block_given?
|
10
|
+
yield SexyPgConstraints::Constrainer.new(args[0].to_s)
|
11
|
+
else
|
12
|
+
SexyPgConstraints::Constrainer::add_constraints(*args)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def deconstrain(*args)
|
17
|
+
if block_given?
|
18
|
+
yield SexyPgConstraints::DeConstrainer.new(args[0])
|
19
|
+
else
|
20
|
+
SexyPgConstraints::DeConstrainer::drop_constraints(*args)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module SexyPgConstraints
|
2
|
+
class Constrainer
|
3
|
+
include SexyPgConstraints::Helpers
|
4
|
+
|
5
|
+
def initialize(table, columns = [])
|
6
|
+
@table = table.to_s
|
7
|
+
@columns = columns
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(column, constraints)
|
11
|
+
self.class.add_constraints(@table, column.to_s, constraints)
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](*columns)
|
15
|
+
@columns = columns.map{|c| c.to_s}
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def all(constraints)
|
20
|
+
self.class.add_constraints(@table, @columns, constraints)
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def add_constraints(table, column, constraints)
|
25
|
+
constraints.each_pair do |type, options|
|
26
|
+
sql = "alter table #{table} add constraint #{make_title(table, column, type)} " +
|
27
|
+
SexyPgConstraints::Constraints.send(type, table, column, options) + ';'
|
28
|
+
|
29
|
+
execute sql
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
module SexyPgConstraints
|
2
|
+
module Constraints
|
3
|
+
module_function
|
4
|
+
|
5
|
+
##
|
6
|
+
# Only allow listed values.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
# constrain :books, :variation, :whitelist => %w(hardcover softcover)
|
10
|
+
#
|
11
|
+
def whitelist(table, column, options)
|
12
|
+
"check (#{table}.#{column} in (#{ options.collect{|v| "'#{v}'"}.join(',') }))"
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Prohibit listed values.
|
17
|
+
#
|
18
|
+
# Example:
|
19
|
+
# constrain :books, :isbn, :blacklist => %w(invalid_isbn1 invalid_isbn2)
|
20
|
+
#
|
21
|
+
def blacklist(table, column, options)
|
22
|
+
"check (#{table}.#{column} not in (#{ options.collect{|v| "'#{v}'"}.join(',') }))"
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# The value must have at least 1 non-space character.
|
27
|
+
#
|
28
|
+
# Example:
|
29
|
+
# constrain :books, :title, :not_blank => true
|
30
|
+
#
|
31
|
+
def not_blank(table, column, options)
|
32
|
+
"check ( length(trim(both from #{table}.#{column})) > 0 )"
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# The numeric value must be within given range.
|
37
|
+
#
|
38
|
+
# Example:
|
39
|
+
# constrain :books, :year, :within => 1980..2008
|
40
|
+
# constrain :books, :year, :within => 1980...2009
|
41
|
+
# (the two lines above do the same thing)
|
42
|
+
#
|
43
|
+
def within(table, column, options)
|
44
|
+
column_ref = column.to_s.include?('.') ? column : "#{table}.#{column}"
|
45
|
+
"check (#{column_ref} >= #{options.begin} and #{column_ref} #{options.exclude_end? ? ' < ' : ' <= '} #{options.end})"
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Check the length of strings/text to be within the range.
|
50
|
+
#
|
51
|
+
# Example:
|
52
|
+
# constrain :books, :author, :length_within => 4..50
|
53
|
+
#
|
54
|
+
def length_within(table, column, options)
|
55
|
+
within(table, "length(#{table}.#{column})", options)
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Allow only valid email format.
|
60
|
+
#
|
61
|
+
# Example:
|
62
|
+
# constrain :books, :author, :email => true
|
63
|
+
#
|
64
|
+
def email(table, column, options)
|
65
|
+
"check (((#{table}.#{column})::text ~ E'^([-a-z0-9]+)@([-a-z0-9]+[.]+[a-z]{2,4})$'::text))"
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Allow only alphanumeric values.
|
70
|
+
#
|
71
|
+
# Example:
|
72
|
+
# constrain :books, :author, :alphanumeric => true
|
73
|
+
#
|
74
|
+
def alphanumeric(table, column, options)
|
75
|
+
"check (((#{table}.#{column})::text ~* '^[a-z0-9]+$'::text))"
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Allow only lower case values.
|
80
|
+
#
|
81
|
+
# Example:
|
82
|
+
# constrain :books, :author, :lowercase => true
|
83
|
+
#
|
84
|
+
def lowercase(table, column, options)
|
85
|
+
"check (#{table}.#{column} = lower(#{table}.#{column}))"
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Allow only positive values.
|
90
|
+
#
|
91
|
+
# Example:
|
92
|
+
# constrain :books, :quantity, :positive => true
|
93
|
+
#
|
94
|
+
def positive(table, column, options)
|
95
|
+
"check (#{table}.#{column} >= 0)"
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Allow only odd values.
|
100
|
+
#
|
101
|
+
# Example:
|
102
|
+
# constrain :books, :quantity, :odd => true
|
103
|
+
#
|
104
|
+
def odd(table, column, options)
|
105
|
+
"check (mod(#{table}.#{column}, 2) != 0)"
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Allow only even values.
|
110
|
+
#
|
111
|
+
# Example:
|
112
|
+
# constrain :books, :quantity, :even => true
|
113
|
+
#
|
114
|
+
def even(table, column, options)
|
115
|
+
"check (mod(#{table}.#{column}, 2) = 0)"
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# Make sure every entry in the column is unique.
|
120
|
+
#
|
121
|
+
# Example:
|
122
|
+
# constrain :books, :isbn, :unique => true
|
123
|
+
#
|
124
|
+
def unique(table, column, options)
|
125
|
+
column = Array(column).map {|c| %{"#{c}"} }.join(', ')
|
126
|
+
"unique (#{column})"
|
127
|
+
end
|
128
|
+
|
129
|
+
##
|
130
|
+
# Allow only one of the values in the given columns to be true.
|
131
|
+
# Only reasonable with more than one column.
|
132
|
+
# See Enterprise Rails, Chapter 10 for details.
|
133
|
+
#
|
134
|
+
# Example:
|
135
|
+
# constrain :books, [], :xor => true
|
136
|
+
#
|
137
|
+
def xor(table, column, options)
|
138
|
+
addition = Array(column).map {|c| %{("#{c}" is not null)::integer} }.join(' + ')
|
139
|
+
|
140
|
+
"check (#{addition} = 1)"
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
# Allow only text/strings of the exact length specified, no more, no less.
|
145
|
+
#
|
146
|
+
# Example:
|
147
|
+
# constrain :books, :hash, :exact_length => 32
|
148
|
+
#
|
149
|
+
def exact_length(table, column, options)
|
150
|
+
"check ( length(trim(both from #{table}.#{column})) = #{options} )"
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# Allow only values that match the regular expression.
|
155
|
+
#
|
156
|
+
# Example:
|
157
|
+
# constrain :orders, :visa, :format => /^([4]{1})([0-9]{12,15})$/
|
158
|
+
#
|
159
|
+
def format(table, column, options)
|
160
|
+
"check (((#{table}.#{column})::text #{options.casefold? ? '~*' : '~'} E'#{options.source}'::text ))"
|
161
|
+
end
|
162
|
+
|
163
|
+
##
|
164
|
+
# Add foreign key constraint.
|
165
|
+
#
|
166
|
+
# Example:
|
167
|
+
# constrain :books, :author_id, :reference => {:authors => :id, :on_delete => :cascade}
|
168
|
+
#
|
169
|
+
def reference(table, column, options)
|
170
|
+
on_delete = options.delete(:on_delete)
|
171
|
+
fk_table = options.keys.first
|
172
|
+
fk_column = options[fk_table]
|
173
|
+
|
174
|
+
on_delete = "on delete #{on_delete}" if on_delete
|
175
|
+
|
176
|
+
%{foreign key ("#{column}") references #{fk_table} (#{fk_column}) #{on_delete}}
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|