miguel 0.1.0.pre4 → 0.1.0.pre5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -0
- data/README.md +7 -5
- data/lib/miguel/importer.rb +44 -38
- data/lib/miguel/migrator.rb +2 -2
- data/lib/miguel/schema.rb +29 -6
- data/miguel.gemspec +4 -1
- data/test/data/db.yml +17 -0
- data/test/data/schema.rb +3 -2
- data/test/data/schema.txt +24 -23
- data/test/data/schema_bare.txt +128 -0
- data/test/data/schema_change.txt +132 -0
- data/test/data/schema_down.txt +32 -0
- data/test/data/schema_full.txt +166 -0
- data/test/data/seq_0.rb +18 -0
- data/test/data/seq_0.txt +17 -0
- data/test/data/seq_1.rb +33 -0
- data/test/data/seq_1.txt +60 -0
- data/test/data/seq_2.rb +38 -0
- data/test/data/seq_2.txt +51 -0
- data/test/data/simple.txt +5 -5
- data/test/helper.rb +24 -0
- data/test/test_importer.rb +66 -0
- data/test/test_migrator.rb +53 -0
- data/test/test_schema.rb +473 -8
- metadata +57 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0ecd54548a6934b77b3f075d8b1fc74db7c555b
|
4
|
+
data.tar.gz: 87c76a4d8e883f01be334d3d07fd3be0598d5097
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d8c0640ab68fe0d19b45a4f6ab05b34acba429d21dd3de9d30341eb64aab8127e2535ce416eb29ecdfb741d8d1f07fe624ba249ba45fce35a1d1689faaf0ad5
|
7
|
+
data.tar.gz: e3598249193c4e2edd85800f9b48c584526ab56045516c3910969a82ccf8ae11e79ca7dc27675e734727907303bcdf09bf43b4fe95368cbb3c43db1f4c208dc9
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -102,7 +102,7 @@ end
|
|
102
102
|
```
|
103
103
|
|
104
104
|
One enhancement is that it allows you to define `NULL` columns simply by adding `?` to the type name.
|
105
|
-
Anything else is implicitly `NOT NULL`, which is a really wise default.
|
105
|
+
Anything else is implicitly `NOT NULL`, which is a really wise default for many reasons.
|
106
106
|
|
107
107
|
Another enhancement is that it allows you to set defaults and
|
108
108
|
define custom shortcuts for types which you use frequently.
|
@@ -118,24 +118,26 @@ set_defaults :False, :TrueClass, default: false
|
|
118
118
|
set_defaults :Signed, :integer, unsigned: false
|
119
119
|
set_defaults :Unsigned, :integer, unsigned: true
|
120
120
|
set_defaults :Text, :String, text: true
|
121
|
-
set_defaults :Time, :timestamp, default: '
|
121
|
+
set_defaults :Time, :timestamp, default: '2000-01-01 00:00:00'
|
122
122
|
set_defaults :Time?, :timestamp, default: nil
|
123
123
|
|
124
124
|
set_defaults :unique, :index, unique: true
|
125
125
|
|
126
|
+
set_defaults :Key, :integer, unsigned: false
|
126
127
|
set_defaults :primary_key, type: :integer, unsigned: false
|
127
128
|
set_defaults :foreign_key, key: :id, type: :integer, unsigned: false
|
128
129
|
```
|
129
130
|
|
130
131
|
If you prefer unsigned keys instead and your database engine supports it,
|
131
|
-
you can pass the `unsigned_keys: true` option to `define` to make it happen.
|
132
|
+
you can pass the `unsigned_keys: true` option to `Schema.define` to make it happen.
|
132
133
|
If you don't want any of these defaults set up for you,
|
133
|
-
pass the `use_defaults: false` option to define instead.
|
134
|
+
pass the `use_defaults: false` option to `define` instead.
|
134
135
|
|
135
136
|
Finally, the `timestamps` helper can be used to create the
|
136
137
|
`create_time` and `update_time` timestamps for you.
|
137
138
|
If you pass the `mysql_timestamps: true` option to `define`,
|
138
|
-
the `update_time` timestamp will have the MySQL auto-update feature enabled
|
139
|
+
the `update_time` timestamp will have the MySQL auto-update feature enabled,
|
140
|
+
and timestamps will use the `'0000-00-00 00:00:00'` default by default.
|
139
141
|
|
140
142
|
## Using the command
|
141
143
|
|
data/lib/miguel/importer.rb
CHANGED
@@ -105,27 +105,24 @@ module Miguel
|
|
105
105
|
|
106
106
|
# Convert given database default of given type to default used by our schema definitions.
|
107
107
|
def revert_default( type, default, ruby_default )
|
108
|
-
default = ruby_default unless ruby_default.nil?
|
109
|
-
return if default.nil?
|
110
|
-
|
111
|
-
case default
|
112
|
-
when String, Numeric, TrueClass, FalseClass
|
113
|
-
when DateTime
|
114
|
-
default = default.strftime( '%F %T' )
|
115
|
-
else
|
116
|
-
default = default.to_s
|
117
|
-
end
|
118
|
-
|
119
108
|
if type.to_s =~ /date|time/
|
120
109
|
case default
|
121
|
-
when /\A'([-: \d]+)'\z/
|
122
|
-
default = $1
|
123
110
|
when 'CURRENT_TIMESTAMP'
|
124
111
|
# This matches our use of MySQL timestamps in schema definitions.
|
125
|
-
|
112
|
+
return Sequel.lit('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')
|
126
113
|
end
|
127
114
|
end
|
128
|
-
|
115
|
+
|
116
|
+
default = ruby_default unless ruby_default.nil?
|
117
|
+
|
118
|
+
case default
|
119
|
+
when nil, String, Numeric, TrueClass, FalseClass
|
120
|
+
return default
|
121
|
+
when DateTime
|
122
|
+
return default.strftime( '%F %T' )
|
123
|
+
else
|
124
|
+
return default.to_s
|
125
|
+
end
|
129
126
|
end
|
130
127
|
|
131
128
|
# Import indexes of given table.
|
@@ -156,46 +153,55 @@ module Miguel
|
|
156
153
|
# These are usually just schema hints which the user normally doesn't specify.
|
157
154
|
IGNORED_OPTS = [ :max_length ]
|
158
155
|
|
159
|
-
# Import
|
160
|
-
def
|
161
|
-
|
156
|
+
# Import column type and options.
|
157
|
+
def import_column_type_and_options( opts )
|
158
|
+
opts = opts.dup
|
162
159
|
|
163
|
-
#
|
160
|
+
# Discard anything we don't need.
|
164
161
|
|
165
|
-
|
162
|
+
opts.delete_if{ |key, value| IGNORED_OPTS.include? key }
|
166
163
|
|
167
|
-
|
164
|
+
# Import type.
|
168
165
|
|
169
|
-
|
166
|
+
type = opts.delete( :type )
|
167
|
+
db_type = opts.delete( :db_type )
|
170
168
|
|
171
|
-
|
169
|
+
type, type_opts = revert_type_literal( db_type, type )
|
170
|
+
opts.merge!( type_opts ) if type_opts
|
172
171
|
|
173
|
-
|
172
|
+
# Import NULL option.
|
174
173
|
|
175
|
-
|
174
|
+
opts[ :null ] = opts.delete( :allow_null )
|
176
175
|
|
177
|
-
|
176
|
+
# Import default value.
|
178
177
|
|
179
|
-
|
178
|
+
default = opts.delete( :default )
|
179
|
+
ruby_default = opts.delete( :ruby_default )
|
180
|
+
|
181
|
+
default = revert_default( type, default, ruby_default )
|
182
|
+
|
183
|
+
opts[ :default ] = default unless default.nil?
|
184
|
+
|
185
|
+
[ type, opts ]
|
186
|
+
end
|
180
187
|
|
181
|
-
|
182
|
-
|
188
|
+
# Import columns of given table.
|
189
|
+
def import_columns( table )
|
190
|
+
schema = db.schema( table.name )
|
183
191
|
|
184
|
-
|
185
|
-
opts.merge!( type_opts ) if type_opts
|
192
|
+
# Get info about primary key columns.
|
186
193
|
|
187
|
-
|
194
|
+
primary_key_columns = schema.select{ |name, opts| opts[ :primary_key ] }
|
188
195
|
|
189
|
-
|
196
|
+
multi_primary_key = ( primary_key_columns.count > 1 )
|
190
197
|
|
191
|
-
|
198
|
+
# Import each column in sequence.
|
192
199
|
|
193
|
-
|
194
|
-
ruby_default = opts.delete( :ruby_default )
|
200
|
+
for name, opts in schema
|
195
201
|
|
196
|
-
|
202
|
+
# Import column type and options.
|
197
203
|
|
198
|
-
opts
|
204
|
+
type, opts = import_column_type_and_options( opts )
|
199
205
|
|
200
206
|
# Deal with primary keys, which is a bit obscure because of the auto-increment handling.
|
201
207
|
|
data/lib/miguel/migrator.rb
CHANGED
@@ -111,7 +111,7 @@ module Miguel
|
|
111
111
|
# Generate code for dropping given columns.
|
112
112
|
def dump_drop_columns( out, columns )
|
113
113
|
for column in columns
|
114
|
-
if column.
|
114
|
+
if column.primary_key_constraint?
|
115
115
|
out << "drop_constraint #{column.out_name}, :type => :primary_key#{column.out_opts(' # ')}"
|
116
116
|
else
|
117
117
|
out << "drop_column #{column.out_name} # #{column.out_type}#{column.out_opts}"
|
@@ -125,7 +125,7 @@ module Miguel
|
|
125
125
|
if column.type == :primary_key
|
126
126
|
out << "add_primary_key #{column.out_name}#{column.out_opts}"
|
127
127
|
else
|
128
|
-
out << "add_column #{column.out_name}, #{column.out_type}#{column.
|
128
|
+
out << "add_column #{column.out_name}, #{column.out_type}#{column.out_default_opts}"
|
129
129
|
end
|
130
130
|
end
|
131
131
|
end
|
data/lib/miguel/schema.rb
CHANGED
@@ -9,9 +9,12 @@ module Miguel
|
|
9
9
|
# Class for defining database schema.
|
10
10
|
class Schema
|
11
11
|
|
12
|
-
# String denoting zero time.
|
12
|
+
# String denoting zero time in MySQL.
|
13
13
|
ZERO_TIME = '0000-00-00 00:00:00'.freeze
|
14
14
|
|
15
|
+
# String denoting default time.
|
16
|
+
DEFAULT_TIME = '2000-01-01 00:00:00'.freeze
|
17
|
+
|
15
18
|
# Module for pretty printing of names, types, and especially options.
|
16
19
|
module Output
|
17
20
|
|
@@ -45,6 +48,10 @@ module Miguel
|
|
45
48
|
out_hash( canonic_opts, prefix )
|
46
49
|
end
|
47
50
|
|
51
|
+
def out_default_opts( prefix = ', ' )
|
52
|
+
out_hash( default_opts, prefix )
|
53
|
+
end
|
54
|
+
|
48
55
|
def out_name
|
49
56
|
name.inspect
|
50
57
|
end
|
@@ -81,6 +88,11 @@ module Miguel
|
|
81
88
|
@opts = opts
|
82
89
|
end
|
83
90
|
|
91
|
+
# Test if the column is in fact just a primary key constraint.
|
92
|
+
def primary_key_constraint?
|
93
|
+
type == :primary_key && name.is_a?( Array )
|
94
|
+
end
|
95
|
+
|
84
96
|
# Get the column default.
|
85
97
|
def default
|
86
98
|
d = opts[ :default ]
|
@@ -159,7 +171,7 @@ module Miguel
|
|
159
171
|
|
160
172
|
# Get the column options in a canonic way.
|
161
173
|
def canonic_opts
|
162
|
-
return {} if
|
174
|
+
return {} if primary_key_constraint?
|
163
175
|
o = { :type => canonic_type, :default => default, :null => true }
|
164
176
|
o.merge!( DEFAULT_OPTS[ canonic_type ] || {} )
|
165
177
|
o.merge!( opts )
|
@@ -167,6 +179,11 @@ module Miguel
|
|
167
179
|
o.delete_if{ |key, value| IGNORED_OPTS.include? key }
|
168
180
|
end
|
169
181
|
|
182
|
+
# Get options with default value included.
|
183
|
+
def default_opts
|
184
|
+
{ :default => default }.merge( opts )
|
185
|
+
end
|
186
|
+
|
170
187
|
# Compare one column with another one.
|
171
188
|
def == other
|
172
189
|
other.is_a?( Column ) &&
|
@@ -240,7 +257,7 @@ module Miguel
|
|
240
257
|
|
241
258
|
# Options we ignore when comparing.
|
242
259
|
# These are usually tied to the underlying column, not constraint.
|
243
|
-
IGNORED_OPTS = [ :null, :unsigned, :type ]
|
260
|
+
IGNORED_OPTS = [ :null, :unsigned, :type, :default ]
|
244
261
|
|
245
262
|
# Get the foreign key options, in a canonic way.
|
246
263
|
def canonic_opts
|
@@ -475,6 +492,7 @@ module Miguel
|
|
475
492
|
# Also note that the defaults are applied in the instant the +table+ block is evaluated,
|
476
493
|
# so it is eventually possible (though not necessarily recommended) to change them in between.
|
477
494
|
def set_defaults( name, *args, &block )
|
495
|
+
clear_defaults( name )
|
478
496
|
@aliases[ name ] = args.shift if args.first.is_a? Symbol
|
479
497
|
@defaults[ name ] = args.pop if args.last.is_a? Hash
|
480
498
|
@callbacks[ name ] = block
|
@@ -512,6 +530,7 @@ module Miguel
|
|
512
530
|
#
|
513
531
|
# :unique, :index, :unique => true
|
514
532
|
#
|
533
|
+
# :Key, :integer, :unsigned => false
|
515
534
|
# :primary_key, :type => :integer, :unsigned => false
|
516
535
|
# :foreign_key, :key => :id, :type => :integer, :unsigned => false
|
517
536
|
#
|
@@ -528,14 +547,17 @@ module Miguel
|
|
528
547
|
# Unfortunately, :unsigned currently works only with :integer,
|
529
548
|
# not the default :Integer, and :integer can't be specified for compound keys,
|
530
549
|
# so we have to use the callback to set the type only at correct times.
|
550
|
+
# Furthermore, Postgres's autoincrementing serials only work with Integer,
|
551
|
+
# so we set the type only as long as the unsigned keys are not requested.
|
531
552
|
|
532
553
|
unsigned_keys = !! opts[ :unsigned_keys ]
|
533
554
|
|
555
|
+
set_defaults :Key, :integer, :unsigned => unsigned_keys
|
534
556
|
set_defaults :primary_key, :unsigned => unsigned_keys do |opts,args,table|
|
535
|
-
opts[ :type ] ||= :integer unless args.first.is_a? Array
|
557
|
+
opts[ :type ] ||= :integer unless args.first.is_a? Array or not opts[ :unsigned ]
|
536
558
|
end
|
537
559
|
set_defaults :foreign_key, :key => :id, :unsigned => unsigned_keys do |opts,args,table|
|
538
|
-
opts[ :type ] ||= :integer unless args.first.is_a? Array
|
560
|
+
opts[ :type ] ||= :integer unless args.first.is_a? Array or not opts[ :unsigned ]
|
539
561
|
end
|
540
562
|
|
541
563
|
# Save some typing for unique indexes.
|
@@ -557,7 +579,8 @@ module Miguel
|
|
557
579
|
# we have to be careful to turn off the MySQL autoupdate behavior.
|
558
580
|
# That's why we have to set defaults explicitly.
|
559
581
|
|
560
|
-
|
582
|
+
default_time = opts[ :mysql_timestamps ] ? ZERO_TIME : DEFAULT_TIME
|
583
|
+
set_defaults :Time, :timestamp, :default => default_time
|
561
584
|
set_defaults :Time?, :timestamp, :default => nil
|
562
585
|
|
563
586
|
self
|
data/miguel.gemspec
CHANGED
@@ -4,7 +4,7 @@ require File.expand_path( '../lib/miguel/version', __FILE__ )
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = 'miguel'
|
7
|
-
s.version = Miguel::VERSION + '.
|
7
|
+
s.version = Miguel::VERSION + '.pre5'
|
8
8
|
s.summary = 'Database migrator and migration generator for Sequel.'
|
9
9
|
s.description = <<EOT
|
10
10
|
This gem makes it easy to create and maintain an up-to-date database schema
|
@@ -22,6 +22,9 @@ EOT
|
|
22
22
|
s.required_ruby_version = '>= 1.9.3'
|
23
23
|
s.add_runtime_dependency 'sequel', '~> 4.0'
|
24
24
|
s.add_development_dependency 'bacon', '~> 1.2'
|
25
|
+
s.add_development_dependency 'sqlite3'
|
26
|
+
s.add_development_dependency 'mysql2'
|
27
|
+
s.add_development_dependency 'pg'
|
25
28
|
end
|
26
29
|
|
27
30
|
# EOF #
|
data/test/data/db.yml
ADDED
data/test/data/schema.rb
CHANGED
@@ -29,6 +29,7 @@ Miguel::Schema.define( use_defaults: false ) do
|
|
29
29
|
set_defaults :Custom, :String, fixed: true, size: 3
|
30
30
|
|
31
31
|
table :miguel_types do
|
32
|
+
Key :key
|
32
33
|
String :string
|
33
34
|
Text :text
|
34
35
|
File :blob
|
@@ -74,8 +75,8 @@ Miguel::Schema.define( use_defaults: false ) do
|
|
74
75
|
end
|
75
76
|
|
76
77
|
table :compound do
|
77
|
-
|
78
|
-
|
78
|
+
Integer :a
|
79
|
+
Integer :b
|
79
80
|
primary_key [:a, :b]
|
80
81
|
foreign_key [:b, :a], :compound, key: [:a, :b]
|
81
82
|
String :c
|
data/test/data/schema.txt
CHANGED
@@ -19,6 +19,7 @@ table :sequel_types do
|
|
19
19
|
FalseClass :l
|
20
20
|
end
|
21
21
|
table :miguel_types do
|
22
|
+
integer :key, :null => false, :unsigned => false
|
22
23
|
String :string, :null => false
|
23
24
|
String :text, :null => false, :text => true
|
24
25
|
File :blob, :null => false
|
@@ -29,7 +30,7 @@ table :miguel_types do
|
|
29
30
|
TrueClass :bool, :null => false
|
30
31
|
TrueClass :true, :null => false, :default => true
|
31
32
|
TrueClass :false, :null => false, :default => false
|
32
|
-
timestamp :time, :null => false, :default => "
|
33
|
+
timestamp :time, :null => false, :default => "2000-01-01 00:00:00"
|
33
34
|
String :custom, :null => false, :fixed => true, :size => 3
|
34
35
|
end
|
35
36
|
table :native_types do
|
@@ -39,29 +40,29 @@ table :native_types do
|
|
39
40
|
timestamp :timestamp, :null => false, :default => "1970-01-02 00:00:00"
|
40
41
|
end
|
41
42
|
table :timestamps do
|
42
|
-
timestamp :t1, :null => false, :default => "
|
43
|
+
timestamp :t1, :null => false, :default => "2000-01-01 00:00:00"
|
43
44
|
timestamp :t2, :null => true, :default => nil
|
44
|
-
timestamp :create_time, :null => false, :default => "
|
45
|
-
timestamp :update_time, :null => false, :default => "
|
45
|
+
timestamp :create_time, :null => false, :default => "2000-01-01 00:00:00"
|
46
|
+
timestamp :update_time, :null => false, :default => "2000-01-01 00:00:00"
|
46
47
|
end
|
47
48
|
table :users do
|
48
|
-
primary_key :id, :null => false, :unsigned => false
|
49
|
+
primary_key :id, :null => false, :unsigned => false
|
49
50
|
String :name, :null => false
|
50
51
|
index [:name], :null => false, :unique => true
|
51
52
|
end
|
52
53
|
table :simple do
|
53
|
-
primary_key :id, :null => false, :unsigned => false
|
54
|
-
integer :user_id, :null => false, :key => [:id], :unsigned => false
|
54
|
+
primary_key :id, :null => false, :unsigned => false
|
55
|
+
integer :user_id, :null => false, :key => [:id], :unsigned => false
|
55
56
|
index [:user_id], :null => false
|
56
|
-
foreign_key [:user_id], :users, :null => false, :key => [:id], :unsigned => false
|
57
|
+
foreign_key [:user_id], :users, :null => false, :key => [:id], :unsigned => false
|
57
58
|
end
|
58
59
|
table :reuse do
|
59
|
-
primary_key :user_id, :null => false, :unsigned => false
|
60
|
+
primary_key :user_id, :null => false, :unsigned => false
|
60
61
|
foreign_key [:user_id], :users, :null => false, :key => [:id], :unsigned => false
|
61
62
|
end
|
62
63
|
table :compound do
|
63
|
-
|
64
|
-
|
64
|
+
Integer :a, :null => false
|
65
|
+
Integer :b, :null => false
|
65
66
|
primary_key [:a, :b], :null => false, :unsigned => false
|
66
67
|
String :c, :null => false
|
67
68
|
integer :d, :null => false, :unsigned => false
|
@@ -82,8 +83,8 @@ table :null do
|
|
82
83
|
TrueClass :true, :null => true, :default => true
|
83
84
|
TrueClass :false, :null => true, :default => false
|
84
85
|
timestamp :t, :null => true, :default => nil
|
85
|
-
integer :user_id, :null => true, :key => [:id], :unsigned => false
|
86
|
-
foreign_key [:user_id], :users, :null => true, :key => [:id], :unsigned => false
|
86
|
+
integer :user_id, :null => true, :key => [:id], :unsigned => false
|
87
|
+
foreign_key [:user_id], :users, :null => true, :key => [:id], :unsigned => false
|
87
88
|
end
|
88
89
|
table :defaults do
|
89
90
|
String :string, :null => false, :default => "abc"
|
@@ -95,21 +96,21 @@ table :defaults do
|
|
95
96
|
timestamp :time, :null => false, :default => "2037-12-31 23:59:59"
|
96
97
|
end
|
97
98
|
table :simple_users do
|
98
|
-
integer :user_id, :null => false, :key => [:id], :unsigned => false
|
99
|
-
integer :simple_id, :null => false, :key => [:id], :unsigned => false
|
99
|
+
integer :user_id, :null => false, :key => [:id], :unsigned => false
|
100
|
+
integer :simple_id, :null => false, :key => [:id], :unsigned => false
|
100
101
|
primary_key [:user_id, :simple_id], :null => false, :unsigned => false
|
101
102
|
index [:simple_id, :user_id], :null => false, :unique => true
|
102
|
-
foreign_key [:user_id], :users, :null => false, :key => [:id], :unsigned => false
|
103
|
-
foreign_key [:simple_id], :simple, :null => false, :key => [:id], :unsigned => false
|
103
|
+
foreign_key [:user_id], :users, :null => false, :key => [:id], :unsigned => false
|
104
|
+
foreign_key [:simple_id], :simple, :null => false, :key => [:id], :unsigned => false
|
104
105
|
end
|
105
106
|
table :self_join do
|
106
|
-
integer :left_id, :null => false, :key => [:id], :unsigned => false
|
107
|
-
integer :right_id, :null => false, :key => [:id], :unsigned => false
|
107
|
+
integer :left_id, :null => false, :key => [:id], :unsigned => false
|
108
|
+
integer :right_id, :null => false, :key => [:id], :unsigned => false
|
108
109
|
primary_key [:left_id, :right_id], :null => false, :unsigned => false
|
109
|
-
timestamp :create_time, :null => false, :default => "
|
110
|
-
timestamp :update_time, :null => false, :default => "
|
110
|
+
timestamp :create_time, :null => false, :default => "2000-01-01 00:00:00"
|
111
|
+
timestamp :update_time, :null => false, :default => "2000-01-01 00:00:00"
|
111
112
|
index [:right_id, :left_id], :null => false, :unique => true
|
112
113
|
index [:create_time], :null => false
|
113
|
-
foreign_key [:left_id], :users, :null => false, :key => [:id], :unsigned => false
|
114
|
-
foreign_key [:right_id], :users, :null => false, :key => [:id], :unsigned => false
|
114
|
+
foreign_key [:left_id], :users, :null => false, :key => [:id], :unsigned => false
|
115
|
+
foreign_key [:right_id], :users, :null => false, :key => [:id], :unsigned => false
|
115
116
|
end
|