schema_plus 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +61 -43
- data/Rakefile +10 -7
- data/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb +3 -3
- data/lib/schema_plus/active_record/connection_adapters/foreign_key_definition.rb +4 -0
- data/lib/schema_plus/active_record/connection_adapters/index_definition.rb +2 -2
- data/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb +2 -0
- data/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb +19 -8
- data/lib/schema_plus/active_record/connection_adapters/table_definition.rb +1 -1
- data/lib/schema_plus/version.rb +1 -1
- data/runspecs +8 -2
- data/schema_plus.gemspec +1 -1
- data/spec/foreign_key_spec.rb +1 -1
- data/spec/index_spec.rb +7 -2
- data/spec/migration_spec.rb +51 -1
- data/spec/schema_dumper_spec.rb +10 -3
- data/spec/support/matchers/reference.rb +12 -11
- metadata +6 -6
- data/spec/support/reference.rb +0 -66
data/README.rdoc
CHANGED
@@ -46,48 +46,48 @@ With standard rails migrations, you specify indexes separately from the table de
|
|
46
46
|
end
|
47
47
|
|
48
48
|
add_index :parts, :name # index repeats table and column names and is defined separately
|
49
|
-
add_index :parts, :product_code, :
|
49
|
+
add_index :parts, :product_code, unique: true
|
50
50
|
|
51
51
|
But with SchemaPlus you can specify your indexes when you define each column, with options as desired
|
52
52
|
|
53
53
|
# More DRY way...
|
54
54
|
create_table :parts do |t|
|
55
|
-
t.string :name, :
|
56
|
-
t.string :product_code, :
|
55
|
+
t.string :name, index: true
|
56
|
+
t.string :product_code, index: { unique: true }
|
57
57
|
end
|
58
58
|
|
59
59
|
The options hash can include an index name:
|
60
60
|
|
61
|
-
t.string :product_code, :
|
61
|
+
t.string :product_code, index: { unique: true, name: "my_index_name" }
|
62
62
|
|
63
63
|
You can also create multi-column indexes, for example:
|
64
64
|
|
65
65
|
t.string :first_name
|
66
|
-
t.string :last_name, :
|
66
|
+
t.string :last_name, index: { with: :first_name }
|
67
67
|
|
68
68
|
t.string :country_code
|
69
69
|
t.string :area_code
|
70
|
-
t.string :local_number :
|
70
|
+
t.string :local_number index: { with: [:country_code, :area_code], unique: true }
|
71
71
|
|
72
72
|
And you can specify index orders:
|
73
73
|
|
74
74
|
t.string :first_name
|
75
|
-
t.string :last_name, :
|
75
|
+
t.string :last_name, index: { with: :first_name, order: { first_name: :desc, last_name: :asc}}
|
76
76
|
|
77
77
|
As a convenient shorthand, the :unique option can be specified as
|
78
78
|
|
79
|
-
t.string :product_code, :
|
79
|
+
t.string :product_code, index: :unique
|
80
80
|
|
81
81
|
which is equivalent to
|
82
82
|
|
83
|
-
t.string :product_code, :
|
83
|
+
t.string :product_code, index: { unique: true }
|
84
84
|
|
85
85
|
If you're using Postgresql, SchemaPlus provides support for conditions, expressions, index methods, and case-insensitive indexes:
|
86
86
|
|
87
|
-
t.string :last_name, :
|
88
|
-
t.string :last_name, :
|
89
|
-
t.string :last_name, :
|
90
|
-
t.string :last_name, :
|
87
|
+
t.string :last_name, index: { conditions: 'deleted_at IS NULL' }
|
88
|
+
t.string :last_name, index: { expression: 'upper(last_name)' }
|
89
|
+
t.string :last_name, index: { kind: 'hash' }
|
90
|
+
t.string :last_name, index: { case_sensitive: false } # shorthand for expression: 'lower(last_name)'
|
91
91
|
|
92
92
|
These features are available also in ActiveRecord::Migration.add_index. See
|
93
93
|
doc at SchemaPlus::ActiveRecord::ConnectionAdapters::PostgresqlAdapter and
|
@@ -128,29 +128,29 @@ Here are some examples:
|
|
128
128
|
|
129
129
|
t.integer :author_id # automatically references table 'authors', key id
|
130
130
|
t.integer :parent_id # special name parent_id automatically references its own table (for tree nodes)
|
131
|
-
t.integer :author_id, :
|
132
|
-
t.integer :author, :
|
133
|
-
t.integer :author_id, :
|
134
|
-
|
135
|
-
t.integer :author_id, :
|
136
|
-
t.integer :author, :
|
137
|
-
t.integer :author_id, :
|
138
|
-
t.integer :author_id, :
|
139
|
-
t.integer :author_id, :
|
140
|
-
t.integer :author_id, :
|
141
|
-
t.integer :author_id, :
|
142
|
-
t.integer :author_id, :
|
143
|
-
t.integer :author_id, :
|
144
|
-
t.integer :author_id, :
|
131
|
+
t.integer :author_id, foreign_key: true # same as default automatic behavior
|
132
|
+
t.integer :author, foreign_key: true # non-conventional column name needs to force creation, table name is assumed to be 'authors'
|
133
|
+
t.integer :author_id, foreign_key: false # don't create a constraint
|
134
|
+
|
135
|
+
t.integer :author_id, foreign_key: { references: :authors } # same as automatic behavior
|
136
|
+
t.integer :author, foreign_key: { reference: :authors} # same default name
|
137
|
+
t.integer :author_id, foreign_key: { references: [:authors, :id] } # same as automatic behavior
|
138
|
+
t.integer :author_id, foreign_key: { references: :people } # override table name
|
139
|
+
t.integer :author_id, foreign_key: { references: [:people, :ssn] } # override table name and key
|
140
|
+
t.integer :author_id, foreign_key: { references: nil } # don't create a constraint
|
141
|
+
t.integer :author_id, foreign_key: { name: "my_fk" } # override default generated constraint name
|
142
|
+
t.integer :author_id, foreign_key: { on_delete: :cascade }
|
143
|
+
t.integer :author_id, foreign_key: { on_update: :set_null }
|
144
|
+
t.integer :author_id, foreign_key: { deferrable: true }
|
145
145
|
|
146
146
|
Of course the options can be combined, e.g.
|
147
147
|
|
148
|
-
t.integer :author_id, :
|
148
|
+
t.integer :author_id, foreign_key: { name: "my_fk", on_delete: :no_action }
|
149
149
|
|
150
150
|
As a shorthand, all options except +:name+ can be specified without placing them in a hash, e.g.
|
151
151
|
|
152
|
-
t.integer :author_id, :
|
153
|
-
t.integer :author_id, :
|
152
|
+
t.integer :author_id, on_delete: :cascade
|
153
|
+
t.integer :author_id, references: nil
|
154
154
|
|
155
155
|
The foreign key behavior can be configured globally (see Config) or per-table (see create_table).
|
156
156
|
|
@@ -163,7 +163,7 @@ that reference a given table. See SchemaPlus::ActiveRecord::ConnectionAdapters:
|
|
163
163
|
|
164
164
|
SchemaPlus provides support for creating and dropping views. In a migration, a view can be created using a rails relation or literal sql:
|
165
165
|
|
166
|
-
create_view :posts_commented_by_staff, Post.joins(:
|
166
|
+
create_view :posts_commented_by_staff, Post.joins(comment: user).where(users: {role: 'staff'}).uniq
|
167
167
|
create_view :uncommented_posts, "SELECT * FROM posts LEFT OUTER JOIN comments ON comments.post_id = posts.id WHERE comments.id IS NULL"
|
168
168
|
|
169
169
|
And can be dropped:
|
@@ -184,19 +184,19 @@ ActiveRecord works with views the same as with ordinary tables. That is, for th
|
|
184
184
|
|
185
185
|
SchemaPlus allows defaults to be set using expressions or constant values:
|
186
186
|
|
187
|
-
t.datetime :seen_at, :
|
188
|
-
t.datetime :seen_at, :
|
187
|
+
t.datetime :seen_at, default: { expr: 'NOW()' }
|
188
|
+
t.datetime :seen_at, default: { value: "2011-12-11 00:00:00" }
|
189
189
|
|
190
190
|
Note that in MySQL only the TIMESTAMP column data type accepts SQL column
|
191
191
|
defaults and Rails uses DATETIME, so expressions can't be used with MySQL.
|
192
192
|
|
193
193
|
The standard syntax will still work as usual:
|
194
194
|
|
195
|
-
t.datetime :seen_at, :
|
195
|
+
t.datetime :seen_at, default: "2011-12-11 00:00:00"
|
196
196
|
|
197
197
|
Also, as a convenience
|
198
198
|
|
199
|
-
t.datetime :seen_at, :
|
199
|
+
t.datetime :seen_at, default: :now
|
200
200
|
|
201
201
|
resolves to:
|
202
202
|
|
@@ -208,8 +208,8 @@ resolves to:
|
|
208
208
|
|
209
209
|
SchemaPlus introduces a constant <tt>ActiveRecord::DB_DEFAULT</tt> that you can use to explicitly instruct the database to use the column default value (or expression). For example:
|
210
210
|
|
211
|
-
Post.create(:
|
212
|
-
post.update_attributes(:
|
211
|
+
Post.create(category: ActiveRecord::DB_DEFAULT)
|
212
|
+
post.update_attributes(category: ActiveRecord::DB_DEFAULT)
|
213
213
|
|
214
214
|
(Without <tt>ActiveRecord::DB_DEFAULT</tt>, you can update a value to <tt>NULL</tt> but not to its default value.)
|
215
215
|
|
@@ -246,7 +246,24 @@ take advantage of auto-creation of foreign key constraints, you can re-enable it
|
|
246
246
|
|
247
247
|
== Release notes:
|
248
248
|
|
249
|
-
|
249
|
+
|
250
|
+
=== 1.0.1
|
251
|
+
|
252
|
+
* README cleanups (thanks to https://github.com/denispeplin)
|
253
|
+
|
254
|
+
* Now raises ArgumentError if index has both :case_sensitive => false and an :expression
|
255
|
+
|
256
|
+
* Now creates consistent default name for foreign key constraints
|
257
|
+
|
258
|
+
* Bug fix: respect :length keyword for index (thanks to https://github.com/teleological )
|
259
|
+
|
260
|
+
* Bug fix: renaming table with multiple foreign key constraints (thanks to https://github.com/teleological )
|
261
|
+
|
262
|
+
* Bug fix: don't dump :case_sensitive => false for index with an expression that includes "lower(name)".
|
263
|
+
|
264
|
+
* Bug fix: Properly dump multi-column case-insensitive indexes
|
265
|
+
|
266
|
+
=== 1.0.0
|
250
267
|
|
251
268
|
* No longer support rails < 3.2 and ruby < 1.9
|
252
269
|
|
@@ -268,7 +285,7 @@ take advantage of auto-creation of foreign key constraints, you can re-enable it
|
|
268
285
|
|
269
286
|
* Bug fix: when removing a foreign key constraint, remove its auto-generated index.
|
270
287
|
|
271
|
-
* Bug fix: SchemaDumper.ignore_tables needs to support regexps (
|
288
|
+
* Bug fix: SchemaDumper.ignore_tables needs to support regexps (suggested by https://github.com/mtalcott)
|
272
289
|
|
273
290
|
* Bug fix: More robust handling of Postgresql schema_search path (suggested by https://github.com/mtalcott)
|
274
291
|
|
@@ -317,10 +334,11 @@ Then:
|
|
317
334
|
$ ./runspecs --install # do this once, it runs 'bundle install' for all versions
|
318
335
|
$ ./runspecs # as many times as you like
|
319
336
|
|
320
|
-
See <tt>./runspecs --help</tt> for more options.
|
321
|
-
|
322
|
-
|
323
|
-
|
337
|
+
See <tt>./runspecs --help</tt> for more options. In particular, to run
|
338
|
+
rspec on a specific file or example (rather than running the full suite)
|
339
|
+
you can do, e.g.
|
340
|
+
|
341
|
+
$ ./runspecs [other options] --rspec -- spec/migration_spec.rb -e 'default name'
|
324
342
|
|
325
343
|
Code coverage results will be in coverage/index.html -- it should be at 100% coverage if you're running against all databases
|
326
344
|
|
data/Rakefile
CHANGED
@@ -12,14 +12,17 @@ task :spec do
|
|
12
12
|
Rake::Task["#{adapter}:spec"].invoke
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
# work around a bug in rake 10.0.3 with ruby 1.9.2
|
16
|
+
unless RUBY_VERSION == "1.9.2"
|
17
|
+
require 'rdoc/task'
|
18
|
+
Rake::RDocTask.new do |rdoc|
|
19
|
+
require File.dirname(__FILE__) + '/lib/schema_plus/version'
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
rdoc.rdoc_dir = 'rdoc'
|
22
|
+
rdoc.title = "schema_plus #{SchemaPlus::VERSION}"
|
23
|
+
rdoc.rdoc_files.include('README*')
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
|
+
end
|
23
26
|
end
|
24
27
|
|
25
28
|
require 'rspec/core/rake_task'
|
@@ -69,7 +69,7 @@ module SchemaPlus
|
|
69
69
|
# it's created. If you're using Sqlite3, this method will raise an
|
70
70
|
# error.)
|
71
71
|
def add_foreign_key(table_name, column_names, references_table_name, references_column_names, options = {})
|
72
|
-
foreign_key = ForeignKeyDefinition.new(options[:name], table_name, column_names, ::ActiveRecord::Migrator.proper_table_name(references_table_name), references_column_names, options[:on_update], options[:on_delete], options[:deferrable])
|
72
|
+
foreign_key = ForeignKeyDefinition.new(options[:name] || ForeignKeyDefinition.default_name(table_name, column_names), table_name, column_names, ::ActiveRecord::Migrator.proper_table_name(references_table_name), references_column_names, options[:on_update], options[:on_delete], options[:deferrable])
|
73
73
|
execute "ALTER TABLE #{quote_table_name(table_name)} ADD #{foreign_key.to_sql}"
|
74
74
|
end
|
75
75
|
|
@@ -102,7 +102,7 @@ module SchemaPlus
|
|
102
102
|
rename_index(newname, index.name, index_name(newname, index.columns))
|
103
103
|
end
|
104
104
|
foreign_keys(newname).each do |fk|
|
105
|
-
index = indexes(newname).find{|index| index.name == ForeignKeyDefinition.auto_index_name(oldname,
|
105
|
+
index = indexes(newname).find{|index| index.name == ForeignKeyDefinition.auto_index_name(oldname, fk.column_names)}
|
106
106
|
begin
|
107
107
|
remove_foreign_key(newname, fk.name)
|
108
108
|
rescue NotImplementedError
|
@@ -113,7 +113,7 @@ module SchemaPlus
|
|
113
113
|
# if the index is on a foreign key constraint
|
114
114
|
rename_index(newname, index.name, ForeignKeyDefinition.auto_index_name(newname, index.columns)) if index
|
115
115
|
begin
|
116
|
-
add_foreign_key(newname, fk.column_names, fk.references_table_name, fk.references_column_names, :name => newname, :on_update => fk.on_update, :on_delete => fk.on_delete, :deferrable => fk.deferrable)
|
116
|
+
add_foreign_key(newname, fk.column_names, fk.references_table_name, fk.references_column_names, :name => fk.name.sub(/#{oldname}/, newname), :on_update => fk.on_update, :on_delete => fk.on_delete, :deferrable => fk.deferrable)
|
117
117
|
rescue NotImplementedError
|
118
118
|
# sqlite3 can't add foreign keys, so just skip it
|
119
119
|
end
|
@@ -106,6 +106,10 @@ module SchemaPlus
|
|
106
106
|
value.to_s.sub(/^["`](.*)["`]$/, '\1')
|
107
107
|
end
|
108
108
|
|
109
|
+
def self.default_name(table_name, column_names)
|
110
|
+
"fk_#{table_name}_#{Array.wrap(column_names).join('_and_')}"
|
111
|
+
end
|
112
|
+
|
109
113
|
def self.auto_index_name(table_name, column_name)
|
110
114
|
"fk__#{table_name}_#{Array.wrap(column_name).join('_and_')}"
|
111
115
|
end
|
@@ -22,7 +22,7 @@ module SchemaPlus
|
|
22
22
|
# same args as add_index(table_name, column_names, options)
|
23
23
|
if args.length == 3 and Hash === args.last
|
24
24
|
table_name, column_names, options = args + [{}]
|
25
|
-
initialize_without_schema_plus(table_name, options[:name], options[:unique], column_names, options[:
|
25
|
+
initialize_without_schema_plus(table_name, options[:name], options[:unique], column_names, options[:length], options[:orders])
|
26
26
|
@conditions = options[:conditions]
|
27
27
|
@expression = options[:expression]
|
28
28
|
@kind = options[:kind]
|
@@ -38,7 +38,7 @@ module SchemaPlus
|
|
38
38
|
opts = {}
|
39
39
|
opts[:name] = name unless name.nil?
|
40
40
|
opts[:unique] = unique unless unique.nil?
|
41
|
-
opts[:
|
41
|
+
opts[:length] = lengths unless lengths.nil?
|
42
42
|
opts[:conditions] = conditions unless conditions.nil?
|
43
43
|
opts[:expression] = expression unless expression.nil?
|
44
44
|
opts[:kind] = kind unless kind.nil?
|
@@ -33,6 +33,8 @@ module SchemaPlus
|
|
33
33
|
rename_indexes_and_foreign_keys(oldname, newname)
|
34
34
|
end
|
35
35
|
|
36
|
+
# used only for mysql not mysql2. the quoting methods on ActiveRecord::DB_DEFAULT are
|
37
|
+
# sufficient for mysql2
|
36
38
|
def exec_stmt_with_schema_plus(sql, name, binds, &block)
|
37
39
|
if binds.any?{ |col, val| val.equal? ::ActiveRecord::DB_DEFAULT}
|
38
40
|
binds.each_with_index do |(col, val), i|
|
@@ -60,17 +60,21 @@ module SchemaPlus
|
|
60
60
|
# * +:conditions+ - SQL conditions for the WHERE clause of the index
|
61
61
|
# * +:expression+ - SQL expression to index. column_name can be nil or ommitted, in which case :name must be provided
|
62
62
|
# * +:kind+ - index method for Postgresql to use
|
63
|
-
# * +:case_sensitive -
|
63
|
+
# * +:case_sensitive - setting to +false+ is a shorthand for :expression => 'LOWER(column_name)'
|
64
64
|
#
|
65
65
|
# The <tt>:case_sensitive => false</tt> option ties in with Rails built-in support for case-insensitive searching:
|
66
66
|
# validates_uniqueness_of :name, :case_sensitive => false
|
67
67
|
#
|
68
|
+
# Since since <tt>:case_sensitive => false</tt> is implemented by
|
69
|
+
# using <tt>:expression</tt>, this raises an ArgumentError if both
|
70
|
+
# are specified simultaneously.
|
71
|
+
#
|
68
72
|
def add_index(table_name, column_name, options = {})
|
69
73
|
column_name, options = [], column_name if column_name.is_a?(Hash)
|
70
74
|
column_names = Array(column_name).compact
|
71
75
|
if column_names.empty?
|
72
|
-
raise ArgumentError, "No columns and :expression missing from options - cannot create index"
|
73
|
-
raise ArgumentError, "Index name not given. Pass :name option"
|
76
|
+
raise ArgumentError, "No columns and :expression missing from options - cannot create index" unless options[:expression]
|
77
|
+
raise ArgumentError, "Index name not given. Pass :name option" unless options[:name]
|
74
78
|
end
|
75
79
|
|
76
80
|
index_type = options[:unique] ? "UNIQUE" : ""
|
@@ -79,6 +83,7 @@ module SchemaPlus
|
|
79
83
|
kind = options[:kind]
|
80
84
|
|
81
85
|
if expression = options[:expression] then
|
86
|
+
raise ArgumentError, "Cannot specify :case_sensitive => false with an expression. Use LOWER(column_name)" if options[:case_sensitive] == false
|
82
87
|
# Wrap expression in parentheses if necessary
|
83
88
|
expression = "(#{expression})" if expression !~ /(using|with|tablespace|where)/i
|
84
89
|
expression = "USING #{kind} #{expression}" if kind
|
@@ -138,10 +143,16 @@ module SchemaPlus
|
|
138
143
|
SQL
|
139
144
|
|
140
145
|
column_names = columns.values_at(*index_keys).compact
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
146
|
+
case_sensitive = true
|
147
|
+
|
148
|
+
# extract column names from the expression, for a
|
149
|
+
# case-insensitive index
|
150
|
+
if expression
|
151
|
+
rexp_lower = %r{\blower\(\(?([^)]+)(\)::text)?\)}
|
152
|
+
if expression.match /^(#{rexp_lower}(, )?)+$/
|
153
|
+
column_names = expression.scan(rexp_lower).map(&:first)
|
154
|
+
case_sensitive = false
|
155
|
+
end
|
145
156
|
end
|
146
157
|
|
147
158
|
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
|
@@ -153,7 +164,7 @@ module SchemaPlus
|
|
153
164
|
:unique => unique,
|
154
165
|
:orders => orders,
|
155
166
|
:conditions => conditions,
|
156
|
-
:case_sensitive =>
|
167
|
+
:case_sensitive => case_sensitive,
|
157
168
|
:kind => kind.downcase == "btree" ? nil : kind,
|
158
169
|
:expression => expression)
|
159
170
|
end
|
@@ -127,7 +127,7 @@ module SchemaPlus::ActiveRecord::ConnectionAdapters
|
|
127
127
|
end
|
128
128
|
|
129
129
|
def foreign_key(column_names, references_table_name, references_column_names, options = {})
|
130
|
-
@foreign_keys << ForeignKeyDefinition.new(options[:name],
|
130
|
+
@foreign_keys << ForeignKeyDefinition.new(options[:name] || ForeignKeyDefinition.default_name(self.name, column_names), self.name, column_names, ::ActiveRecord::Migrator.proper_table_name(references_table_name), references_column_names, options[:on_update], options[:on_delete], options[:deferrable])
|
131
131
|
self
|
132
132
|
end
|
133
133
|
|
data/lib/schema_plus/version.rb
CHANGED
data/runspecs
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'optparse'
|
4
4
|
require 'ostruct'
|
5
|
+
require 'shellwords'
|
5
6
|
require 'tempfile'
|
6
7
|
|
7
8
|
RUBY_VERSIONS = %W[1.9.2 1.9.3]
|
@@ -52,12 +53,15 @@ OptionParser.new do |opts|
|
|
52
53
|
o.db_adapters = ["postgresql"]
|
53
54
|
end
|
54
55
|
|
56
|
+
opts.on("--rspec", "run rspec rather than rake") do |v|
|
57
|
+
o.rspec = v
|
58
|
+
end
|
59
|
+
|
55
60
|
end.parse!
|
56
61
|
|
57
62
|
|
58
63
|
Combo = Struct.new(:ruby, :rails, :db_adapter)
|
59
64
|
|
60
|
-
|
61
65
|
combos = o.ruby_versions.product(o.rails_versions, o.db_adapters).map{|product| Combo.new(*product)}.select {|combo|
|
62
66
|
case
|
63
67
|
when combo.rails >= "3.2" && combo.ruby <= "1.8.7" then false # no longer happens, just keeping it as an example
|
@@ -78,11 +82,13 @@ combos.each_with_index do |combo, n|
|
|
78
82
|
"bundle update"
|
79
83
|
when o.install
|
80
84
|
"bundle install"
|
85
|
+
when o.rspec
|
86
|
+
"bundle exec rspec -Ispec/connections/#{db_adapter}"
|
81
87
|
else
|
82
88
|
"bundle exec rake #{db_adapter}:spec"
|
83
89
|
end
|
84
90
|
|
85
|
-
command = %Q{BUNDLE_GEMFILE="#{File.join(GEMFILES_DIR, "rails-#{rails}", "Gemfile.#{db_adapter}")}" rvm #{ruby} do #{cmd}}
|
91
|
+
command = %Q{BUNDLE_GEMFILE="#{File.join(GEMFILES_DIR, "rails-#{rails}", "Gemfile.#{db_adapter}")}" rvm #{ruby} do #{cmd} #{Shellwords.join(ARGV)}}
|
86
92
|
|
87
93
|
puts "\n\n*** ruby version #{ruby} - rails version #{rails} - db adapter: #{db_adapter} [#{n+1} of #{combos.size}]\n\n#{command}"
|
88
94
|
|
data/schema_plus.gemspec
CHANGED
@@ -6,13 +6,13 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.name = "schema_plus"
|
7
7
|
s.version = SchemaPlus::VERSION
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
|
+
s.required_ruby_version = ">= 1.9.2"
|
9
10
|
s.authors = ["Ronen Barzel", "Michal Lomnicki"]
|
10
11
|
s.email = ["ronen@barzel.org", "michal.lomnicki@gmail.com"]
|
11
12
|
s.homepage = "https://github.com/lomba/schema_plus"
|
12
13
|
s.summary = "Enhances ActiveRecord schema mechanism, including more DRY index creation and support for foreign key constraints and views."
|
13
14
|
s.description = "SchemaPlus is an ActiveRecord extension that provides enhanced capabilities for schema definition and querying, including: enhanced and more DRY index capabilities, support and automation for foreign key constraints, and support for views."
|
14
15
|
|
15
|
-
|
16
16
|
s.rubyforge_project = "schema_plus"
|
17
17
|
|
18
18
|
s.files = `git ls-files`.split("\n")
|
data/spec/foreign_key_spec.rb
CHANGED
data/spec/index_spec.rb
CHANGED
@@ -108,13 +108,18 @@ describe "add_index" do
|
|
108
108
|
end
|
109
109
|
|
110
110
|
it "should raise if no column given and expression is missing" do
|
111
|
-
expect { add_index(:users, :name => 'users_login_index') }.to raise_error(ArgumentError)
|
111
|
+
expect { add_index(:users, :name => 'users_login_index') }.to raise_error(ArgumentError, /expression/)
|
112
112
|
end
|
113
113
|
|
114
114
|
it "should raise if expression without name is given" do
|
115
|
-
expect { add_index(:users, :expression => "USING btree (login)") }.to raise_error(ArgumentError)
|
115
|
+
expect { add_index(:users, :expression => "USING btree (login)") }.to raise_error(ArgumentError, /name/)
|
116
116
|
end
|
117
117
|
|
118
|
+
it "should raise if expression is given and case_sensitive is false" do
|
119
|
+
expect { add_index(:users, :name => 'users_login_index', :expression => "USING btree (login)", :case_sensitive => false) }.to raise_error(ArgumentError, /use LOWER/i)
|
120
|
+
end
|
121
|
+
|
122
|
+
|
118
123
|
end # of postgresql specific examples
|
119
124
|
|
120
125
|
protected
|
data/spec/migration_spec.rb
CHANGED
@@ -78,6 +78,13 @@ describe ActiveRecord::Migration do
|
|
78
78
|
@model.should reference(:users, :id).on(:author_id)
|
79
79
|
end
|
80
80
|
|
81
|
+
it "should create foreign key with default name" do
|
82
|
+
recreate_table @model do |t|
|
83
|
+
t.integer :user_id, :foreign_key => true
|
84
|
+
end
|
85
|
+
@model.should reference(:users, :id).with_name("fk_#{@model.table_name}_user_id")
|
86
|
+
end
|
87
|
+
|
81
88
|
it "should create foreign key with specified name" do
|
82
89
|
recreate_table @model do |t|
|
83
90
|
t.integer :user_id, :foreign_key => { :name => "wugga" }
|
@@ -162,6 +169,17 @@ describe ActiveRecord::Migration do
|
|
162
169
|
@model.should have_unique_index.on(:state)
|
163
170
|
end
|
164
171
|
|
172
|
+
if SchemaPlusHelpers.mysql?
|
173
|
+
it "should pass index length option properly" do
|
174
|
+
recreate_table(@model) do |t|
|
175
|
+
t.string :foo
|
176
|
+
t.string :bar, :index => { :with => :foo, :length => { :foo => 8, :bar => 12 }}
|
177
|
+
end
|
178
|
+
index = @model.indexes.first
|
179
|
+
Hash[index.columns.zip(index.lengths.map(&:to_i))].should == { "foo" => 8, "bar" => 12}
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
165
183
|
it "should create an index if specified explicitly" do
|
166
184
|
recreate_table(@model) do |t|
|
167
185
|
t.integer :state
|
@@ -672,12 +690,44 @@ describe ActiveRecord::Migration do
|
|
672
690
|
end
|
673
691
|
|
674
692
|
unless SchemaPlusHelpers.sqlite3?
|
675
|
-
it "should rename foreign key
|
693
|
+
it "should rename foreign key constraints" do
|
676
694
|
ActiveRecord::Base.connection.foreign_keys(:newname).first.name.should =~ /newname/
|
677
695
|
end
|
678
696
|
end
|
679
697
|
|
680
698
|
end
|
699
|
+
|
700
|
+
unless SchemaPlusHelpers.sqlite3?
|
701
|
+
|
702
|
+
context "when table with more than one fk constraint is renamed" do
|
703
|
+
|
704
|
+
before(:each) do
|
705
|
+
@model = Comment
|
706
|
+
recreate_table @model do |t|
|
707
|
+
t.integer :user_id
|
708
|
+
t.integer :member_id
|
709
|
+
end
|
710
|
+
ActiveRecord::Migration.suppress_messages do
|
711
|
+
ActiveRecord::Migration.rename_table @model.table_name, :newname
|
712
|
+
end
|
713
|
+
end
|
714
|
+
|
715
|
+
around(:each) do |example|
|
716
|
+
begin
|
717
|
+
example.run
|
718
|
+
ensure
|
719
|
+
ActiveRecord::Migration.suppress_messages do
|
720
|
+
ActiveRecord::Migration.rename_table :newname, :comments
|
721
|
+
end
|
722
|
+
end
|
723
|
+
end
|
724
|
+
it "should rename foreign key constraints" do
|
725
|
+
names = ActiveRecord::Base.connection.foreign_keys(:newname).map(&:name)
|
726
|
+
names.grep(/newname/).should == names
|
727
|
+
end
|
728
|
+
end
|
729
|
+
|
730
|
+
end
|
681
731
|
|
682
732
|
def recreate_table(model, opts={}, &block)
|
683
733
|
ActiveRecord::Migration.suppress_messages do
|
data/spec/schema_dumper_spec.rb
CHANGED
@@ -163,8 +163,8 @@ describe "Schema dump" do
|
|
163
163
|
if SchemaPlusHelpers.postgresql?
|
164
164
|
|
165
165
|
it "should define case insensitive index" do
|
166
|
-
with_index Post, :
|
167
|
-
dump_posts.should match(to_regexp(%q{t.index ["body"], :name => "
|
166
|
+
with_index Post, [:body, :string_no_default], :case_sensitive => false do
|
167
|
+
dump_posts.should match(to_regexp(%q{t.index ["body", "string_no_default"], :name => "index_posts_on_body_and_string_no_default", :case_sensitive => false}))
|
168
168
|
end
|
169
169
|
end
|
170
170
|
|
@@ -180,6 +180,13 @@ describe "Schema dump" do
|
|
180
180
|
end
|
181
181
|
end
|
182
182
|
|
183
|
+
it "should not define :case_sensitive => false with non-trivial expression" do
|
184
|
+
with_index Post, :name => "posts_user_body_index", :expression => "BTRIM(LOWER(body))" do
|
185
|
+
dump_posts.should match(%r{#{to_regexp(%q{t.index :name => "posts_user_body_index", :expression => "btrim(lower(body))"})}$})
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
|
183
190
|
it "should define kind" do
|
184
191
|
with_index Post, :name => "posts_body_index", :expression => "USING hash (body)" do
|
185
192
|
dump_posts.should match(to_regexp(%q{t.index ["body"], :name => "posts_body_index", :kind => "hash"}))
|
@@ -277,7 +284,7 @@ describe "Schema dump" do
|
|
277
284
|
def determine_index_name(model, columns, options)
|
278
285
|
name = columns[:name] if columns.is_a?(Hash)
|
279
286
|
name ||= options[:name]
|
280
|
-
name ||= model.indexes.detect { |index| index.table == model.table_name.to_s && index.columns == Array(columns).collect(&:to_s) }.name
|
287
|
+
name ||= model.indexes.detect { |index| index.table == model.table_name.to_s && index.columns.sort == Array(columns).collect(&:to_s).sort }.name
|
281
288
|
name
|
282
289
|
end
|
283
290
|
|
@@ -19,23 +19,24 @@ module SchemaPlusMatchers
|
|
19
19
|
else
|
20
20
|
@result = @model.foreign_keys
|
21
21
|
end
|
22
|
-
if @column_names
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
(@name ? fk.name == @name : true)
|
28
|
-
end
|
29
|
-
else
|
30
|
-
!@result.empty?
|
31
|
-
end
|
22
|
+
@result.keep_if {|fk| fk.column_names == @column_names } if @column_names
|
23
|
+
@result.keep_if {|fk| fk.on_update == @on_update } if @on_update
|
24
|
+
@result.keep_if {|fk| fk.on_delete == @on_delete } if @on_delete
|
25
|
+
@result.keep_if {|fk| fk.name == @name } if @name
|
26
|
+
!@result.empty?
|
32
27
|
end
|
33
28
|
|
34
29
|
def failure_message_for_should(should_not = false)
|
35
30
|
target_column_names = @column_names.present? ? "(#{@column_names.join(', ')})" : ""
|
36
31
|
destinantion_column_names = @references_table_name ? "#{@references_table_name}(#{@references_column_names.join(', ')})" : "anything"
|
37
32
|
invert = should_not ? 'not' : ''
|
38
|
-
"Expected #{@model.table_name}#{target_column_names} to #{invert} reference #{destinantion_column_names}"
|
33
|
+
msg = "Expected #{@model.table_name}#{target_column_names} to #{invert} reference #{destinantion_column_names}"
|
34
|
+
with = []
|
35
|
+
with << "on_update=#{@on_update.inspect}" if @on_update
|
36
|
+
with << "on_delete=#{@on_delete.inspect}" if @on_delete
|
37
|
+
with << "name=#{@name.inspect}" if @name
|
38
|
+
msg += " with #{with.join(" and ")}" if with.any?
|
39
|
+
msg
|
39
40
|
end
|
40
41
|
|
41
42
|
def failure_message_for_should_not
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: schema_plus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2013-02-04 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rails
|
@@ -173,7 +173,6 @@ files:
|
|
173
173
|
- spec/support/matchers/automatic_foreign_key_matchers.rb
|
174
174
|
- spec/support/matchers/have_index.rb
|
175
175
|
- spec/support/matchers/reference.rb
|
176
|
-
- spec/support/reference.rb
|
177
176
|
- spec/views_spec.rb
|
178
177
|
homepage: https://github.com/lomba/schema_plus
|
179
178
|
licenses: []
|
@@ -186,13 +185,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
186
185
|
requirements:
|
187
186
|
- - ! '>='
|
188
187
|
- !ruby/object:Gem::Version
|
189
|
-
version:
|
188
|
+
version: 1.9.2
|
190
189
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
191
190
|
none: false
|
192
191
|
requirements:
|
193
192
|
- - ! '>='
|
194
193
|
- !ruby/object:Gem::Version
|
195
194
|
version: '0'
|
195
|
+
segments:
|
196
|
+
- 0
|
197
|
+
hash: 1266061864634900724
|
196
198
|
requirements: []
|
197
199
|
rubyforge_project: schema_plus
|
198
200
|
rubygems_version: 1.8.24
|
@@ -223,6 +225,4 @@ test_files:
|
|
223
225
|
- spec/support/matchers/automatic_foreign_key_matchers.rb
|
224
226
|
- spec/support/matchers/have_index.rb
|
225
227
|
- spec/support/matchers/reference.rb
|
226
|
-
- spec/support/reference.rb
|
227
228
|
- spec/views_spec.rb
|
228
|
-
has_rdoc:
|
data/spec/support/reference.rb
DELETED
@@ -1,66 +0,0 @@
|
|
1
|
-
module SchemaPlusMatchers
|
2
|
-
|
3
|
-
class Reference
|
4
|
-
def initialize(expected)
|
5
|
-
@column_names = nil
|
6
|
-
unless expected.empty?
|
7
|
-
@references_column_names = Array(expected).collect(&:to_s)
|
8
|
-
@references_table_name = @references_column_names.shift
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def matches?(model)
|
13
|
-
@model = model
|
14
|
-
if @references_table_name
|
15
|
-
@result = @model.foreign_keys.select do |fk|
|
16
|
-
fk.references_table_name == @references_table_name &&
|
17
|
-
@references_column_names.empty? ? true : fk.references_column_names == @references_column_names
|
18
|
-
end
|
19
|
-
else
|
20
|
-
@result = @model.foreign_keys
|
21
|
-
end
|
22
|
-
if @column_names
|
23
|
-
@result.any? do |fk|
|
24
|
-
fk.column_names == @column_names &&
|
25
|
-
(@on_update ? fk.on_update == @on_update : true) &&
|
26
|
-
(@on_delete ? fk.on_delete == @on_delete : true)
|
27
|
-
end
|
28
|
-
else
|
29
|
-
!@result.empty?
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def failure_message_for_should(should_not = false)
|
34
|
-
target_column_names = @column_names.present? ? "(#{@column_names.join(', ')})" : ""
|
35
|
-
destinantion_column_names = @references_table_name ? "#{@references_table_name}(#{@references_column_names.join(', ')})" : "anything"
|
36
|
-
invert = should_not ? 'not' : ''
|
37
|
-
"Expected #{@model.table_name}#{target_column_names} to #{invert} reference #{destinantion_column_names}"
|
38
|
-
end
|
39
|
-
|
40
|
-
def failure_message_for_should_not
|
41
|
-
failure_message_for_should(true)
|
42
|
-
end
|
43
|
-
|
44
|
-
def on(*column_names)
|
45
|
-
@column_names = column_names.collect(&:to_s)
|
46
|
-
self
|
47
|
-
end
|
48
|
-
|
49
|
-
def on_update(action)
|
50
|
-
@on_update = action
|
51
|
-
self
|
52
|
-
end
|
53
|
-
|
54
|
-
def on_delete(action)
|
55
|
-
@on_delete = action
|
56
|
-
self
|
57
|
-
end
|
58
|
-
|
59
|
-
end
|
60
|
-
|
61
|
-
def reference(*expect)
|
62
|
-
Reference.new(expect)
|
63
|
-
end
|
64
|
-
|
65
|
-
end
|
66
|
-
|