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 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, :unique => true
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, :index => true
56
- t.string :product_code, :index => { :unique => true }
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, :index => { :unique => true, :name => "my_index_name" }
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, :index => { :with => :first_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 :index => { :with => [:country_code, :area_code], :unique => true }
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, :index => { :with => :first_name, :order => {:first_name => :desc, :last_name => :asc}}
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, :index => :unique
79
+ t.string :product_code, index: :unique
80
80
 
81
81
  which is equivalent to
82
82
 
83
- t.string :product_code, :index => { :unique => true }
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, :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)'
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, :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 }
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, :foreign_key => { :name => "my_fk", :on_delete => :no_action }
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, :on_delete => :cascade
153
- t.integer :author_id, :references => nil
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(:comment => :user).where(:users => {:role => 'staff'}).uniq
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, :default => { :expr => 'NOW()' }
188
- t.datetime :seen_at, :default => { :value => "2011-12-11 00:00:00" }
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, :default => "2011-12-11 00:00:00"
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, :default => :now
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(:category => ActiveRecord::DB_DEFAULT)
212
- post.update_attributes(:category => ActiveRecord::DB_DEFAULT)
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
- === master (to be released)
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 (suggeste by https://github.com/mtalcott)
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. You can also manually pick a specific version of rails, ruby, and db to use, such as:
321
- $ rvm use 1.9.2
322
- $ export BUNDLE_GEMFILE=gemfiles/rails-3.2/Gemfile.sqlite3
323
- $ bundle exec rake spec
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
- require 'rdoc/task'
16
- Rake::RDocTask.new do |rdoc|
17
- require File.dirname(__FILE__) + '/lib/schema_plus/version'
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
- rdoc.rdoc_dir = 'rdoc'
20
- rdoc.title = "schema_plus #{SchemaPlus::VERSION}"
21
- rdoc.rdoc_files.include('README*')
22
- rdoc.rdoc_files.include('lib/**/*.rb')
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, index.columns)}
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[:lengths], options[:orders])
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[:lengths] = lengths unless lengths.nil?
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 - if +false+ then the index will be created on LOWER(column_name)
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" if options[:expression].blank?
73
- raise ArgumentError, "Index name not given. Pass :name option" if options[:name].blank?
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
- # extract column name from the expression, for a
142
- # case-insensitive
143
- if md = expression.try(:match, /^lower\(\(?([^)]+)\)?(::text)?\)$/i)
144
- column_names << md[1]
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 => !(expression =~ /lower/i),
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], nil, column_names, ::ActiveRecord::Migrator.proper_table_name(references_table_name), references_column_names, options[:on_update], options[:on_delete], options[:deferrable])
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
 
@@ -1,3 +1,3 @@
1
1
  module SchemaPlus
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.1"
3
3
  end
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")
@@ -145,7 +145,7 @@ describe "Foreign Key" do
145
145
  before(:each) do
146
146
  migration.suppress_messages do
147
147
  migration.create_table :references, :force => true do |t|
148
- t.integer :post_id
148
+ t.integer :post_id, :foreign_key => false
149
149
  end
150
150
  end
151
151
  end
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
@@ -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 definitions" do
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
@@ -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, :name => "posts_user_body_index", :expression => "USING btree (LOWER(body))" do
167
- dump_posts.should match(to_regexp(%q{t.index ["body"], :name => "posts_user_body_index", :case_sensitive => false}))
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
- @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
- (@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.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: 2012-12-04 00:00:00.000000000 Z
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: '0'
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:
@@ -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
-