schema_plus 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
-
|