activerecord 5.0.0.beta3 → 5.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +225 -8
  3. data/examples/performance.rb +0 -1
  4. data/examples/simple.rb +0 -1
  5. data/lib/active_record.rb +0 -1
  6. data/lib/active_record/associations.rb +10 -6
  7. data/lib/active_record/associations/association.rb +1 -1
  8. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  9. data/lib/active_record/associations/join_dependency.rb +1 -1
  10. data/lib/active_record/associations/preloader.rb +1 -0
  11. data/lib/active_record/associations/preloader/through_association.rb +15 -8
  12. data/lib/active_record/attribute/user_provided_default.rb +10 -5
  13. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  14. data/lib/active_record/attributes.rb +3 -3
  15. data/lib/active_record/autosave_association.rb +1 -1
  16. data/lib/active_record/base.rb +2 -1
  17. data/lib/active_record/collection_cache_key.rb +1 -1
  18. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +0 -19
  19. data/lib/active_record/connection_adapters/abstract/database_statements.rb +7 -8
  20. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  21. data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -1
  22. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  23. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +16 -2
  24. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -4
  25. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +20 -10
  26. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +85 -20
  27. data/lib/active_record/connection_adapters/abstract/transaction.rb +13 -1
  28. data/lib/active_record/connection_adapters/abstract_adapter.rb +37 -16
  29. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +119 -108
  30. data/lib/active_record/connection_adapters/column.rb +5 -6
  31. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  32. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  33. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +27 -6
  34. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -13
  35. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -55
  36. data/lib/active_record/connection_adapters/postgresql/column.rb +0 -1
  37. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +2 -0
  38. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  39. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +7 -10
  40. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +65 -25
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +59 -30
  42. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  43. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +7 -0
  44. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +19 -56
  45. data/lib/active_record/connection_adapters/statement_pool.rb +6 -4
  46. data/lib/active_record/core.rb +13 -4
  47. data/lib/active_record/enum.rb +1 -1
  48. data/lib/active_record/errors.rb +9 -0
  49. data/lib/active_record/fixture_set/file.rb +7 -1
  50. data/lib/active_record/gem_version.rb +1 -1
  51. data/lib/active_record/locking/optimistic.rb +4 -0
  52. data/lib/active_record/log_subscriber.rb +1 -1
  53. data/lib/active_record/migration.rb +4 -4
  54. data/lib/active_record/migration/compatibility.rb +1 -1
  55. data/lib/active_record/model_schema.rb +12 -0
  56. data/lib/active_record/nested_attributes.rb +1 -1
  57. data/lib/active_record/persistence.rb +1 -1
  58. data/lib/active_record/query_cache.rb +13 -16
  59. data/lib/active_record/querying.rb +1 -1
  60. data/lib/active_record/railtie.rb +8 -11
  61. data/lib/active_record/railties/databases.rake +5 -5
  62. data/lib/active_record/reflection.rb +21 -4
  63. data/lib/active_record/relation.rb +10 -10
  64. data/lib/active_record/relation/batches.rb +29 -9
  65. data/lib/active_record/relation/delegation.rb +0 -1
  66. data/lib/active_record/relation/finder_methods.rb +27 -8
  67. data/lib/active_record/relation/predicate_builder.rb +4 -2
  68. data/lib/active_record/relation/where_clause.rb +2 -1
  69. data/lib/active_record/schema_dumper.rb +39 -24
  70. data/lib/active_record/scoping/default.rb +2 -1
  71. data/lib/active_record/suppressor.rb +5 -1
  72. data/lib/active_record/tasks/database_tasks.rb +6 -4
  73. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  74. data/lib/active_record/type.rb +1 -1
  75. data/lib/active_record/type/internal/abstract_json.rb +1 -5
  76. data/lib/active_record/type/time.rb +12 -0
  77. data/lib/active_record/validations/uniqueness.rb +1 -4
  78. data/lib/rails/generators/active_record/model/model_generator.rb +15 -11
  79. data/lib/rails/generators/active_record/model/templates/application_record.rb +2 -0
  80. metadata +11 -8
@@ -104,7 +104,7 @@ module ActiveRecord
104
104
 
105
105
  To silence this deprecation warning, add the following:
106
106
 
107
- config.active_record.time_zone_aware_types << :time
107
+ config.active_record.time_zone_aware_types = [:datetime, :time]
108
108
  MESSAGE
109
109
  end
110
110
 
@@ -119,7 +119,7 @@ module ActiveRecord
119
119
  #
120
120
  # class MoneyType < ActiveRecord::Type::Integer
121
121
  # def cast(value)
122
- # if !value.kind_of(Numeric) && value.include?('$')
122
+ # if !value.kind_of?(Numeric) && value.include?('$')
123
123
  # price_in_dollars = value.gsub(/\$/, '').to_f
124
124
  # super(price_in_dollars * 100)
125
125
  # else
@@ -154,7 +154,7 @@ module ActiveRecord
154
154
  # end
155
155
  #
156
156
  # class MoneyType < Type::Value
157
- # def initialize(currency_converter)
157
+ # def initialize(currency_converter:)
158
158
  # @currency_converter = currency_converter
159
159
  # end
160
160
  #
@@ -171,7 +171,7 @@ module ActiveRecord
171
171
  #
172
172
  # class Product < ActiveRecord::Base
173
173
  # currency_converter = ConversionRatesFromTheInternet.new
174
- # attribute :price_in_bitcoins, :money, currency_converter
174
+ # attribute :price_in_bitcoins, :money, currency_converter: currency_converter
175
175
  # end
176
176
  #
177
177
  # Product.where(price_in_bitcoins: Money.new(5, "USD"))
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
  #
23
23
  # == Validation
24
24
  #
25
- # Children records are validated unless <tt>:validate</tt> is +false+.
25
+ # Child records are validated unless <tt>:validate</tt> is +false+.
26
26
  #
27
27
  # == Callbacks
28
28
  #
@@ -169,7 +169,8 @@ module ActiveRecord #:nodoc:
169
169
  # ActiveRecord::RecordNotFound error if they do not return any records,
170
170
  # like <tt>Person.find_by_last_name!</tt>.
171
171
  #
172
- # It's also possible to use multiple attributes in the same find by separating them with "_and_".
172
+ # It's also possible to use multiple attributes in the same <tt>find_by_</tt> by separating them with
173
+ # "_and_".
173
174
  #
174
175
  # Person.find_by(user_name: user_name, password: password)
175
176
  # Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
@@ -16,7 +16,7 @@ module ActiveRecord
16
16
 
17
17
  query = collection
18
18
  .unscope(:select)
19
- .select("COUNT(*) AS size", "MAX(#{column}) AS timestamp")
19
+ .select("COUNT(*) AS #{connection.quote_column_name("size")}", "MAX(#{column}) AS timestamp")
20
20
  .unscope(:order)
21
21
  result = connection.select_one(query)
22
22
 
@@ -951,24 +951,5 @@ module ActiveRecord
951
951
  owner_to_pool && owner_to_pool[owner.name]
952
952
  end
953
953
  end
954
-
955
- class ConnectionManagement
956
- def initialize(app)
957
- @app = app
958
- end
959
-
960
- def call(env)
961
- testing = env['rack.test']
962
-
963
- status, headers, body = @app.call(env)
964
- proxy = ::Rack::BodyProxy.new(body) do
965
- ActiveRecord::Base.clear_active_connections! unless testing
966
- end
967
- [status, headers, proxy]
968
- rescue Exception
969
- ActiveRecord::Base.clear_active_connections! unless testing
970
- raise
971
- end
972
- end
973
954
  end
974
955
  end
@@ -66,8 +66,8 @@ module ActiveRecord
66
66
  # Returns an array of arrays containing the field values.
67
67
  # Order is the same as that returned by +columns+.
68
68
  def select_rows(sql, name = nil, binds = [])
69
+ exec_query(sql, name, binds).rows
69
70
  end
70
- undef_method :select_rows
71
71
 
72
72
  # Executes the SQL statement in the context of this connection and returns
73
73
  # the raw result from the connection adapter.
@@ -75,13 +75,14 @@ module ActiveRecord
75
75
  # method may be manually memory managed. Consider using the exec_query
76
76
  # wrapper instead.
77
77
  def execute(sql, name = nil)
78
+ raise NotImplementedError
78
79
  end
79
- undef_method :execute
80
80
 
81
81
  # Executes +sql+ statement in the context of this connection using
82
82
  # +binds+ as the bind substitutes. +name+ is logged along with
83
83
  # the executed +sql+ statement.
84
84
  def exec_query(sql, name = 'SQL', binds = [], prepare: false)
85
+ raise NotImplementedError
85
86
  end
86
87
 
87
88
  # Executes insert +sql+ statement in the context of this connection using
@@ -125,18 +126,21 @@ module ActiveRecord
125
126
  end
126
127
  alias create insert
127
128
  alias insert_sql insert
129
+ deprecate insert_sql: :insert
128
130
 
129
131
  # Executes the update statement and returns the number of rows affected.
130
132
  def update(arel, name = nil, binds = [])
131
133
  exec_update(to_sql(arel, binds), name, binds)
132
134
  end
133
135
  alias update_sql update
136
+ deprecate update_sql: :update
134
137
 
135
138
  # Executes the delete statement and returns the number of rows affected.
136
139
  def delete(arel, name = nil, binds = [])
137
140
  exec_delete(to_sql(arel, binds), name, binds)
138
141
  end
139
142
  alias delete_sql delete
143
+ deprecate delete_sql: :delete
140
144
 
141
145
  # Returns +true+ when the connection adapter supports prepared statement
142
146
  # caching, otherwise returns +false+
@@ -217,9 +221,7 @@ module ActiveRecord
217
221
  # * You are creating a nested (savepoint) transaction
218
222
  #
219
223
  # The mysql2 and postgresql adapters support setting the transaction
220
- # isolation level. However, support is disabled for MySQL versions below 5,
221
- # because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170]
222
- # which means the isolation level gets persisted outside the transaction.
224
+ # isolation level.
223
225
  def transaction(requires_new: nil, isolation: nil, joinable: true)
224
226
  if !requires_new && current_transaction.joinable?
225
227
  if isolation
@@ -289,9 +291,6 @@ module ActiveRecord
289
291
  exec_rollback_to_savepoint(name)
290
292
  end
291
293
 
292
- def exec_rollback_to_savepoint(name = nil) #:nodoc:
293
- end
294
-
295
294
  def default_sequence_name(table, column)
296
295
  nil
297
296
  end
@@ -65,7 +65,7 @@ module ActiveRecord
65
65
  if @query_cache_enabled && !locked?(arel)
66
66
  arel, binds = binds_from_relation arel, binds
67
67
  sql = to_sql(arel, binds)
68
- cache_sql(sql, binds) { super(sql, name, binds, preparable: visitor.preparable) }
68
+ cache_sql(sql, binds) { super(sql, name, binds, preparable: preparable) }
69
69
  else
70
70
  super
71
71
  end
@@ -82,7 +82,7 @@ module ActiveRecord
82
82
 
83
83
  # Quotes the column name. Defaults to no quoting.
84
84
  def quote_column_name(column_name)
85
- column_name
85
+ column_name.to_s
86
86
  end
87
87
 
88
88
  # Quotes the table name. Defaults to column name quoting.
@@ -146,6 +146,10 @@ module ActiveRecord
146
146
  end
147
147
  end
148
148
 
149
+ def quoted_time(value) # :nodoc:
150
+ quoted_date(value).sub(/\A2000-01-01 /, '')
151
+ end
152
+
149
153
  def prepare_binds_for_database(binds) # :nodoc:
150
154
  binds.map(&:value_for_database)
151
155
  end
@@ -166,6 +170,7 @@ module ActiveRecord
166
170
  # BigDecimals need to be put in a non-normalized form and quoted.
167
171
  when BigDecimal then value.to_s('F')
168
172
  when Numeric, ActiveSupport::Duration then value.to_s
173
+ when Type::Time::Value then "'#{quoted_time(value)}'"
169
174
  when Date, Time then "'#{quoted_date(value)}'"
170
175
  when Symbol then "'#{quote_string(value.to_s)}'"
171
176
  when Class then "'#{value}'"
@@ -181,6 +186,7 @@ module ActiveRecord
181
186
  when false then unquoted_false
182
187
  # BigDecimals need to be put in a non-normalized form and quoted.
183
188
  when BigDecimal then value.to_s('F')
189
+ when Type::Time::Value then quoted_time(value)
184
190
  when Date, Time then quoted_date(value)
185
191
  when *types_which_need_no_typecasting
186
192
  value
@@ -1,8 +1,8 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
- module Savepoints #:nodoc:
4
- def supports_savepoints?
5
- true
3
+ module Savepoints
4
+ def current_savepoint_name
5
+ current_transaction.savepoint_name
6
6
  end
7
7
 
8
8
  def create_savepoint(name = current_savepoint_name)
@@ -53,8 +53,8 @@ module ActiveRecord
53
53
  statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
54
54
  end
55
55
 
56
- create_sql << "(#{statements.join(', ')}) " if statements.present?
57
- create_sql << "#{o.options}"
56
+ create_sql << "(#{statements.join(', ')})" if statements.present?
57
+ add_table_options!(create_sql, table_options(o))
58
58
  create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
59
59
  create_sql
60
60
  end
@@ -82,6 +82,19 @@ module ActiveRecord
82
82
  "DROP CONSTRAINT #{quote_column_name(name)}"
83
83
  end
84
84
 
85
+ def table_options(o)
86
+ table_options = {}
87
+ table_options[:comment] = o.comment
88
+ table_options[:options] = o.options
89
+ table_options
90
+ end
91
+
92
+ def add_table_options!(create_sql, options)
93
+ if options_sql = options[:options]
94
+ create_sql << " #{options_sql}"
95
+ end
96
+ end
97
+
85
98
  def column_options(o)
86
99
  column_options = {}
87
100
  column_options[:null] = o.null unless o.null.nil?
@@ -92,6 +105,7 @@ module ActiveRecord
92
105
  column_options[:auto_increment] = o.auto_increment
93
106
  column_options[:primary_key] = o.primary_key
94
107
  column_options[:collation] = o.collation
108
+ column_options[:comment] = o.comment
95
109
  column_options
96
110
  end
97
111
 
@@ -3,14 +3,14 @@ module ActiveRecord
3
3
  # Abstract representation of an index definition on a table. Instances of
4
4
  # this type are typically created and returned by methods in database
5
5
  # adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
6
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using) #:nodoc:
6
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc:
7
7
  end
8
8
 
9
9
  # Abstract representation of a column definition. Instances of this type
10
10
  # are typically created by methods in TableDefinition, and added to the
11
11
  # +columns+ attribute of said TableDefinition object, in order to be used
12
12
  # for generating a number of table creation or table changing SQL statements.
13
- class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type) #:nodoc:
13
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type, :comment) #:nodoc:
14
14
 
15
15
  def primary_key?
16
16
  primary_key || type.to_sym == :primary_key
@@ -207,9 +207,9 @@ module ActiveRecord
207
207
  include ColumnMethods
208
208
 
209
209
  attr_accessor :indexes
210
- attr_reader :name, :temporary, :options, :as, :foreign_keys
210
+ attr_reader :name, :temporary, :options, :as, :foreign_keys, :comment
211
211
 
212
- def initialize(name, temporary, options, as = nil)
212
+ def initialize(name, temporary = false, options = nil, as = nil, comment: nil)
213
213
  @columns_hash = {}
214
214
  @indexes = {}
215
215
  @foreign_keys = []
@@ -218,6 +218,7 @@ module ActiveRecord
218
218
  @options = options
219
219
  @as = as
220
220
  @name = name
221
+ @comment = comment
221
222
  end
222
223
 
223
224
  def primary_keys(name = nil) # :nodoc:
@@ -330,6 +331,9 @@ module ActiveRecord
330
331
  end
331
332
 
332
333
  def foreign_key(table_name, options = {}) # :nodoc:
334
+ table_name_prefix = ActiveRecord::Base.table_name_prefix
335
+ table_name_suffix = ActiveRecord::Base.table_name_suffix
336
+ table_name = "#{table_name_prefix}#{table_name}#{table_name_suffix}"
333
337
  foreign_keys.push([table_name, options])
334
338
  end
335
339
 
@@ -373,6 +377,7 @@ module ActiveRecord
373
377
  column.auto_increment = options[:auto_increment]
374
378
  column.primary_key = type == :primary_key || options[:primary_key]
375
379
  column.collation = options[:collation]
380
+ column.comment = options[:comment]
376
381
  column
377
382
  end
378
383
 
@@ -7,15 +7,16 @@ module ActiveRecord
7
7
  # Adapter level by over-writing this code inside the database specific adapters
8
8
  module ColumnDumper
9
9
  def column_spec(column)
10
- spec = prepare_column_options(column)
11
- (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k}: ")}
10
+ spec = Hash[prepare_column_options(column).map { |k, v| [k, "#{k}: #{v}"] }]
11
+ spec[:name] = column.name.inspect
12
+ spec[:type] = schema_type(column).to_s
12
13
  spec
13
14
  end
14
15
 
15
16
  def column_spec_for_primary_key(column)
16
- return if column.type == :integer
17
+ return {} if default_primary_key?(column)
17
18
  spec = { id: schema_type(column).inspect }
18
- spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type].include?(key) })
19
+ spec.merge!(prepare_column_options(column).except!(:null))
19
20
  end
20
21
 
21
22
  # This can be overridden on an Adapter level basis to support other
@@ -23,9 +24,6 @@ module ActiveRecord
23
24
  # PostgreSQL::ColumnDumper)
24
25
  def prepare_column_options(column)
25
26
  spec = {}
26
- spec[:name] = column.name.inspect
27
- spec[:type] = schema_type(column).to_s
28
- spec[:null] = 'false' unless column.null
29
27
 
30
28
  if limit = schema_limit(column)
31
29
  spec[:limit] = limit
@@ -42,26 +40,38 @@ module ActiveRecord
42
40
  default = schema_default(column) if column.has_default?
43
41
  spec[:default] = default unless default.nil?
44
42
 
43
+ spec[:null] = 'false' unless column.null
44
+
45
45
  if collation = schema_collation(column)
46
46
  spec[:collation] = collation
47
47
  end
48
48
 
49
+ spec[:comment] = column.comment.inspect if column.comment.present?
50
+
49
51
  spec
50
52
  end
51
53
 
52
54
  # Lists the valid migration options
53
55
  def migration_keys
54
- [:name, :limit, :precision, :scale, :default, :null, :collation]
56
+ [:name, :limit, :precision, :scale, :default, :null, :collation, :comment]
55
57
  end
56
58
 
57
59
  private
58
60
 
61
+ def default_primary_key?(column)
62
+ schema_type(column) == :integer
63
+ end
64
+
59
65
  def schema_type(column)
60
- column.type
66
+ if column.bigint?
67
+ :bigint
68
+ else
69
+ column.type
70
+ end
61
71
  end
62
72
 
63
73
  def schema_limit(column)
64
- limit = column.limit
74
+ limit = column.limit unless column.bigint?
65
75
  limit.inspect if limit && limit != native_database_types[column.type][:limit]
66
76
  end
67
77
 
@@ -18,6 +18,11 @@ module ActiveRecord
18
18
  nil
19
19
  end
20
20
 
21
+ # Returns the table comment that's stored in database metadata.
22
+ def table_comment(table_name)
23
+ nil
24
+ end
25
+
21
26
  # Truncates a table alias according to the limits of the current adapter.
22
27
  def table_alias_for(table_name)
23
28
  table_name[0...table_alias_length].tr('.', '_')
@@ -254,8 +259,8 @@ module ActiveRecord
254
259
  # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
255
260
  #
256
261
  # See also TableDefinition#column for details on how to create columns.
257
- def create_table(table_name, options = {})
258
- td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
262
+ def create_table(table_name, comment: nil, **options)
263
+ td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment
259
264
 
260
265
  if options[:id] != false && !options[:as]
261
266
  pk = options.fetch(:primary_key) do
@@ -283,6 +288,14 @@ module ActiveRecord
283
288
  end
284
289
  end
285
290
 
291
+ if supports_comments? && !supports_comments_in_create?
292
+ change_table_comment(table_name, comment) if comment
293
+
294
+ td.columns.each do |column|
295
+ change_column_comment(table_name, column.name, column.comment) if column.comment
296
+ end
297
+ end
298
+
286
299
  result
287
300
  end
288
301
 
@@ -329,12 +342,13 @@ module ActiveRecord
329
342
 
330
343
  column_options = options.delete(:column_options) || {}
331
344
  column_options.reverse_merge!(null: false)
345
+ type = column_options.delete(:type) || :integer
332
346
 
333
347
  t1_column, t2_column = [table_1, table_2].map{ |t| t.to_s.singularize.foreign_key }
334
348
 
335
349
  create_table(join_table_name, options.merge!(id: false)) do |td|
336
- td.integer t1_column, column_options
337
- td.integer t2_column, column_options
350
+ td.send type, t1_column, column_options
351
+ td.send type, t2_column, column_options
338
352
  yield td if block_given?
339
353
  end
340
354
  end
@@ -776,7 +790,8 @@ module ActiveRecord
776
790
  # [<tt>:type</tt>]
777
791
  # The reference column type. Defaults to +:integer+.
778
792
  # [<tt>:index</tt>]
779
- # Add an appropriate index. Defaults to false.
793
+ # Add an appropriate index. Defaults to false.
794
+ # See #add_index for usage of this option.
780
795
  # [<tt>:foreign_key</tt>]
781
796
  # Add an appropriate foreign key constraint. Defaults to false.
782
797
  # [<tt>:polymorphic</tt>]
@@ -796,6 +811,14 @@ module ActiveRecord
796
811
  #
797
812
  # add_reference(:products, :supplier, polymorphic: true, index: true)
798
813
  #
814
+ # ====== Create a supplier_id column with a unique index
815
+ #
816
+ # add_reference(:products, :supplier, index: { unique: true })
817
+ #
818
+ # ====== Create a supplier_id column with a named index
819
+ #
820
+ # add_reference(:products, :supplier, index: { name: "my_supplier_index" })
821
+ #
799
822
  # ====== Create a supplier_id column and appropriate foreign key
800
823
  #
801
824
  # add_reference(:products, :supplier, foreign_key: true)
@@ -854,7 +877,7 @@ module ActiveRecord
854
877
  #
855
878
  # generates:
856
879
  #
857
- # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
880
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
858
881
  #
859
882
  # ====== Creating a foreign key on a specific column
860
883
  #
@@ -870,7 +893,7 @@ module ActiveRecord
870
893
  #
871
894
  # generates:
872
895
  #
873
- # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
896
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
874
897
  #
875
898
  # The +options+ hash can include the following keys:
876
899
  # [<tt>:column</tt>]
@@ -962,11 +985,23 @@ module ActiveRecord
962
985
  end
963
986
 
964
987
  def dump_schema_information #:nodoc:
988
+ versions = ActiveRecord::SchemaMigration.order('version').pluck(:version)
989
+ insert_versions_sql(versions)
990
+ end
991
+
992
+ def insert_versions_sql(versions) # :nodoc:
965
993
  sm_table = ActiveRecord::Migrator.schema_migrations_table_name
966
994
 
967
- sql = "INSERT INTO #{sm_table} (version) VALUES "
968
- sql << ActiveRecord::SchemaMigration.order('version').pluck(:version).map {|v| "('#{v}')" }.join(', ')
969
- sql << ";\n\n"
995
+ if supports_multi_insert?
996
+ sql = "INSERT INTO #{sm_table} (version) VALUES "
997
+ sql << versions.map {|v| "('#{v}')" }.join(', ')
998
+ sql << ";\n\n"
999
+ sql
1000
+ else
1001
+ versions.map { |version|
1002
+ "INSERT INTO #{sm_table} (version) VALUES ('#{version}');"
1003
+ }.join "\n\n"
1004
+ end
970
1005
  end
971
1006
 
972
1007
  # Should not be called normally, but this operation is non-destructive.
@@ -1003,7 +1038,7 @@ module ActiveRecord
1003
1038
  if (duplicate = inserting.detect {|v| inserting.count(v) > 1})
1004
1039
  raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
1005
1040
  end
1006
- execute "INSERT INTO #{sm_table} (version) VALUES #{inserting.map {|v| "('#{v}')"}.join(', ') }"
1041
+ execute insert_versions_sql(inserting)
1007
1042
  end
1008
1043
  end
1009
1044
 
@@ -1051,9 +1086,9 @@ module ActiveRecord
1051
1086
  end
1052
1087
 
1053
1088
  # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
1054
- # Additional options (like <tt>null: false</tt>) are forwarded to #add_column.
1089
+ # Additional options (like +:null+) are forwarded to #add_column.
1055
1090
  #
1056
- # add_timestamps(:suppliers, null: false)
1091
+ # add_timestamps(:suppliers, null: true)
1057
1092
  #
1058
1093
  def add_timestamps(table_name, options = {})
1059
1094
  options[:null] = false if options[:null].nil?
@@ -1075,15 +1110,19 @@ module ActiveRecord
1075
1110
  Table.new(table_name, base)
1076
1111
  end
1077
1112
 
1078
- def add_index_options(table_name, column_name, options = {}) #:nodoc:
1079
- column_names = Array(column_name)
1113
+ def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
1114
+ if column_name.is_a?(String) && /\W/ === column_name
1115
+ column_names = column_name
1116
+ else
1117
+ column_names = Array(column_name)
1118
+ end
1080
1119
 
1081
1120
  options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
1082
1121
 
1083
1122
  index_type = options[:type].to_s if options.key?(:type)
1084
1123
  index_type ||= options[:unique] ? "UNIQUE" : ""
1085
1124
  index_name = options[:name].to_s if options.key?(:name)
1086
- index_name ||= index_name(table_name, column: column_names)
1125
+ index_name ||= index_name(table_name, index_name_options(column_names))
1087
1126
  max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
1088
1127
 
1089
1128
  if options.key?(:algorithm)
@@ -1106,13 +1145,23 @@ module ActiveRecord
1106
1145
  end
1107
1146
  index_columns = quoted_columns_for_index(column_names, options).join(", ")
1108
1147
 
1109
- [index_name, index_type, index_columns, index_options, algorithm, using]
1148
+ [index_name, index_type, index_columns, index_options, algorithm, using, comment]
1110
1149
  end
1111
1150
 
1112
1151
  def options_include_default?(options)
1113
1152
  options.include?(:default) && !(options[:null] == false && options[:default].nil?)
1114
1153
  end
1115
1154
 
1155
+ # Changes the comment for a table or removes it if +nil+.
1156
+ def change_table_comment(table_name, comment)
1157
+ raise NotImplementedError, "#{self.class} does not support changing table comments"
1158
+ end
1159
+
1160
+ # Changes the comment for a column or removes it if +nil+.
1161
+ def change_column_comment(table_name, column_name, comment) #:nodoc:
1162
+ raise NotImplementedError, "#{self.class} does not support changing column comments"
1163
+ end
1164
+
1116
1165
  protected
1117
1166
  def add_index_sort_order(option_strings, column_names, options = {})
1118
1167
  if options.is_a?(Hash) && order = options[:order]
@@ -1129,6 +1178,8 @@ module ActiveRecord
1129
1178
 
1130
1179
  # Overridden by the MySQL adapter for supporting index lengths
1131
1180
  def quoted_columns_for_index(column_names, options = {})
1181
+ return [column_names] if column_names.is_a?(String)
1182
+
1132
1183
  option_strings = Hash[column_names.map {|name| [name, '']}]
1133
1184
 
1134
1185
  # add index sort order if supported
@@ -1140,6 +1191,8 @@ module ActiveRecord
1140
1191
  end
1141
1192
 
1142
1193
  def index_name_for_remove(table_name, options = {})
1194
+ return options[:name] if can_remove_index_by_name?(options)
1195
+
1143
1196
  # if the adapter doesn't support the indexes call the best we can do
1144
1197
  # is return the default index name for the options provided
1145
1198
  return index_name(table_name, options) unless respond_to?(:indexes)
@@ -1147,7 +1200,7 @@ module ActiveRecord
1147
1200
  checks = []
1148
1201
 
1149
1202
  if options.is_a?(Hash)
1150
- checks << lambda { |i| i.name == options[:name].to_s } if options.has_key?(:name)
1203
+ checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1151
1204
  column_names = Array(options[:column]).map(&:to_s)
1152
1205
  else
1153
1206
  column_names = Array(options).map(&:to_s)
@@ -1194,14 +1247,22 @@ module ActiveRecord
1194
1247
  end
1195
1248
 
1196
1249
  private
1197
- def create_table_definition(name, temporary = false, options = nil, as = nil)
1198
- TableDefinition.new(name, temporary, options, as)
1250
+ def create_table_definition(*args)
1251
+ TableDefinition.new(*args)
1199
1252
  end
1200
1253
 
1201
1254
  def create_alter_table(name)
1202
1255
  AlterTable.new create_table_definition(name)
1203
1256
  end
1204
1257
 
1258
+ def index_name_options(column_names) # :nodoc:
1259
+ if column_names.is_a?(String)
1260
+ column_names = column_names.scan(/\w+/).join('_')
1261
+ end
1262
+
1263
+ { column: column_names }
1264
+ end
1265
+
1205
1266
  def foreign_key_name(table_name, options) # :nodoc:
1206
1267
  identifier = "#{table_name}_#{options.fetch(:column)}_fk"
1207
1268
  hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
@@ -1223,6 +1284,10 @@ module ActiveRecord
1223
1284
  default_or_changes
1224
1285
  end
1225
1286
  end
1287
+
1288
+ def can_remove_index_by_name?(options)
1289
+ options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
1290
+ end
1226
1291
  end
1227
1292
  end
1228
1293
  end