activerecord 4.2.0 → 4.2.1.rc1

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +206 -1
  3. data/lib/active_record/associations.rb +4 -3
  4. data/lib/active_record/associations/belongs_to_association.rb +9 -5
  5. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  6. data/lib/active_record/associations/collection_association.rb +17 -2
  7. data/lib/active_record/associations/collection_proxy.rb +5 -0
  8. data/lib/active_record/associations/foreign_association.rb +11 -0
  9. data/lib/active_record/associations/has_many_association.rb +22 -14
  10. data/lib/active_record/associations/has_many_through_association.rb +2 -2
  11. data/lib/active_record/associations/has_one_association.rb +1 -0
  12. data/lib/active_record/associations/through_association.rb +11 -0
  13. data/lib/active_record/attribute.rb +15 -1
  14. data/lib/active_record/attribute_methods/before_type_cast.rb +5 -0
  15. data/lib/active_record/attribute_methods/dirty.rb +7 -3
  16. data/lib/active_record/attribute_methods/time_zone_conversion.rb +5 -1
  17. data/lib/active_record/attribute_set/builder.rb +11 -1
  18. data/lib/active_record/attributes.rb +7 -0
  19. data/lib/active_record/autosave_association.rb +23 -8
  20. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +7 -5
  21. data/lib/active_record/connection_adapters/abstract/database_statements.rb +12 -1
  22. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  23. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  24. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +25 -7
  25. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +38 -6
  26. data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -1
  27. data/lib/active_record/connection_adapters/abstract_adapter.rb +13 -6
  28. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +23 -1
  29. data/lib/active_record/connection_adapters/column.rb +1 -1
  30. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -1
  31. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
  32. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -0
  33. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -0
  34. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -1
  35. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -0
  36. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +3 -3
  37. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +6 -7
  38. data/lib/active_record/connection_adapters/postgresql_adapter.rb +3 -3
  39. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +10 -16
  40. data/lib/active_record/connection_handling.rb +1 -1
  41. data/lib/active_record/core.rb +13 -7
  42. data/lib/active_record/counter_cache.rb +1 -1
  43. data/lib/active_record/fixtures.rb +1 -1
  44. data/lib/active_record/gem_version.rb +2 -2
  45. data/lib/active_record/locking/optimistic.rb +16 -14
  46. data/lib/active_record/migration.rb +1 -1
  47. data/lib/active_record/nested_attributes.rb +1 -1
  48. data/lib/active_record/no_touching.rb +1 -1
  49. data/lib/active_record/persistence.rb +2 -1
  50. data/lib/active_record/railties/databases.rake +2 -2
  51. data/lib/active_record/reflection.rb +1 -1
  52. data/lib/active_record/relation.rb +1 -1
  53. data/lib/active_record/relation/finder_methods.rb +1 -1
  54. data/lib/active_record/relation/predicate_builder.rb +15 -0
  55. data/lib/active_record/relation/predicate_builder/array_handler.rb +1 -1
  56. data/lib/active_record/relation/query_methods.rb +19 -18
  57. data/lib/active_record/schema_dumper.rb +1 -1
  58. data/lib/active_record/transactions.rb +6 -8
  59. data/lib/active_record/type/date_time.rb +14 -3
  60. data/lib/active_record/type/decimal.rb +9 -1
  61. data/lib/active_record/type/integer.rb +9 -5
  62. data/lib/active_record/type/numeric.rb +1 -1
  63. data/lib/active_record/type/serialized.rb +1 -1
  64. data/lib/active_record/type/string.rb +4 -0
  65. data/lib/active_record/type/value.rb +4 -0
  66. data/lib/active_record/validations/uniqueness.rb +1 -1
  67. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -3
  68. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -6
  69. metadata +10 -9
@@ -188,9 +188,9 @@ module ActiveRecord
188
188
 
189
189
  if through_reflection.collection? && update_through_counter?(method)
190
190
  update_counter(-count, through_reflection)
191
+ else
192
+ update_counter(-count)
191
193
  end
192
-
193
- update_counter(-count)
194
194
  end
195
195
 
196
196
  def through_records_for(record)
@@ -2,6 +2,7 @@ module ActiveRecord
2
2
  # = Active Record Belongs To Has One Association
3
3
  module Associations
4
4
  class HasOneAssociation < SingularAssociation #:nodoc:
5
+ include ForeignAssociation
5
6
 
6
7
  def handle_dependency
7
8
  case options[:dependent]
@@ -91,6 +91,17 @@ module ActiveRecord
91
91
  raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
92
92
  end
93
93
  end
94
+
95
+ def build_record(attributes)
96
+ inverse = source_reflection.inverse_of
97
+ target = through_association.target
98
+
99
+ if inverse && target && !target.is_a?(Array)
100
+ attributes[inverse.foreign_key] = target.id
101
+ end
102
+
103
+ super(attributes)
104
+ end
94
105
  end
95
106
  end
96
107
  end
@@ -51,7 +51,7 @@ module ActiveRecord
51
51
  end
52
52
 
53
53
  def changed_in_place_from?(old_value)
54
- type.changed_in_place?(old_value, value)
54
+ has_been_read? && type.changed_in_place?(old_value, value)
55
55
  end
56
56
 
57
57
  def with_value_from_user(value)
@@ -74,6 +74,10 @@ module ActiveRecord
74
74
  true
75
75
  end
76
76
 
77
+ def came_from_user?
78
+ false
79
+ end
80
+
77
81
  def ==(other)
78
82
  self.class == other.class &&
79
83
  name == other.name &&
@@ -89,6 +93,12 @@ module ActiveRecord
89
93
  end
90
94
  end
91
95
 
96
+ private
97
+
98
+ def has_been_read?
99
+ defined?(@value)
100
+ end
101
+
92
102
  class FromDatabase < Attribute # :nodoc:
93
103
  def type_cast(value)
94
104
  type.type_cast_from_database(value)
@@ -99,6 +109,10 @@ module ActiveRecord
99
109
  def type_cast(value)
100
110
  type.type_cast_from_user(value)
101
111
  end
112
+
113
+ def came_from_user?
114
+ true
115
+ end
102
116
  end
103
117
 
104
118
  class WithCastValue < Attribute # :nodoc:
@@ -28,6 +28,7 @@ module ActiveRecord
28
28
 
29
29
  included do
30
30
  attribute_method_suffix "_before_type_cast"
31
+ attribute_method_suffix "_came_from_user?"
31
32
  end
32
33
 
33
34
  # Returns the value of the attribute identified by +attr_name+ before
@@ -66,6 +67,10 @@ module ActiveRecord
66
67
  def attribute_before_type_cast(attribute_name)
67
68
  read_attribute_before_type_cast(attribute_name)
68
69
  end
70
+
71
+ def attribute_came_from_user?(attribute_name)
72
+ @attributes[attribute_name].came_from_user?
73
+ end
69
74
  end
70
75
  end
71
76
  end
@@ -76,6 +76,10 @@ module ActiveRecord
76
76
 
77
77
  private
78
78
 
79
+ def changes_include?(attr_name)
80
+ super || attribute_changed_in_place?(attr_name)
81
+ end
82
+
79
83
  def calculate_changes_from_defaults
80
84
  @changed_attributes = nil
81
85
  self.class.column_defaults.each do |attr, orig_value|
@@ -104,7 +108,7 @@ module ActiveRecord
104
108
  end
105
109
 
106
110
  def save_changed_attribute(attr, old_value)
107
- if attribute_changed?(attr)
111
+ if attribute_changed_by_setter?(attr)
108
112
  clear_attribute_changes(attr) unless _field_changed?(attr, old_value)
109
113
  else
110
114
  set_attribute_was(attr, old_value) if _field_changed?(attr, old_value)
@@ -130,7 +134,7 @@ module ActiveRecord
130
134
  # Serialized attributes should always be written in case they've been
131
135
  # changed in place.
132
136
  def keys_for_partial_write
133
- changed
137
+ changed & persistable_attribute_names
134
138
  end
135
139
 
136
140
  def _field_changed?(attr, old_value)
@@ -161,7 +165,7 @@ module ActiveRecord
161
165
  end
162
166
 
163
167
  def store_original_raw_attribute(attr_name)
164
- original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database
168
+ original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database rescue nil
165
169
  end
166
170
 
167
171
  def store_original_raw_attributes
@@ -12,7 +12,11 @@ module ActiveRecord
12
12
  if value.is_a?(Array)
13
13
  value.map { |v| type_cast_from_user(v) }
14
14
  elsif value.respond_to?(:in_time_zone)
15
- value.in_time_zone || super
15
+ begin
16
+ value.in_time_zone || super
17
+ rescue ArgumentError
18
+ nil
19
+ end
16
20
  end
17
21
  end
18
22
 
@@ -20,7 +20,7 @@ module ActiveRecord
20
20
  end
21
21
 
22
22
  class LazyAttributeHash # :nodoc:
23
- delegate :select, :transform_values, to: :materialize
23
+ delegate :transform_values, to: :materialize
24
24
 
25
25
  def initialize(types, values, additional_types)
26
26
  @types = types
@@ -54,6 +54,16 @@ module ActiveRecord
54
54
  super
55
55
  end
56
56
 
57
+ def select
58
+ keys = types.keys | values.keys | delegate_hash.keys
59
+ keys.each_with_object({}) do |key, hash|
60
+ attribute = self[key]
61
+ if yield(key, attribute)
62
+ hash[key] = attribute
63
+ end
64
+ end
65
+ end
66
+
57
67
  protected
58
68
 
59
69
  attr_reader :types, :values, :additional_types, :delegate_hash
@@ -9,6 +9,8 @@ module ActiveRecord
9
9
  class_attribute :user_provided_defaults, instance_accessor: false # :internal:
10
10
  self.user_provided_columns = {}
11
11
  self.user_provided_defaults = {}
12
+
13
+ delegate :persistable_attribute_names, to: :class
12
14
  end
13
15
 
14
16
  module ClassMethods # :nodoc:
@@ -96,6 +98,10 @@ module ActiveRecord
96
98
  @columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
97
99
  end
98
100
 
101
+ def persistable_attribute_names # :nodoc:
102
+ @persistable_attribute_names ||= connection.schema_cache.columns_hash(table_name).keys
103
+ end
104
+
99
105
  def reset_column_information # :nodoc:
100
106
  super
101
107
  clear_caches_calculated_from_columns
@@ -129,6 +135,7 @@ module ActiveRecord
129
135
  @columns_hash = nil
130
136
  @content_columns = nil
131
137
  @default_attributes = nil
138
+ @persistable_attribute_names = nil
132
139
  end
133
140
 
134
141
  def raw_default_values
@@ -177,10 +177,8 @@ module ActiveRecord
177
177
  # before actually defining them.
178
178
  def add_autosave_association_callbacks(reflection)
179
179
  save_method = :"autosave_associated_records_for_#{reflection.name}"
180
- validation_method = :"validate_associated_records_for_#{reflection.name}"
181
- collection = reflection.collection?
182
180
 
183
- if collection
181
+ if reflection.collection?
184
182
  before_save :before_save_collection_association
185
183
 
186
184
  define_non_cyclic_method(save_method) { save_collection_association(reflection) }
@@ -204,8 +202,18 @@ module ActiveRecord
204
202
  before_save save_method
205
203
  end
206
204
 
205
+ define_autosave_validation_callbacks(reflection)
206
+ end
207
+
208
+ def define_autosave_validation_callbacks(reflection)
209
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
207
210
  if reflection.validate? && !method_defined?(validation_method)
208
- method = (collection ? :validate_collection_association : :validate_single_association)
211
+ if reflection.collection?
212
+ method = :validate_collection_association
213
+ else
214
+ method = :validate_single_association
215
+ end
216
+
209
217
  define_non_cyclic_method(validation_method) { send(method, reflection) }
210
218
  validate validation_method
211
219
  end
@@ -272,11 +280,18 @@ module ActiveRecord
272
280
  # go through nested autosave associations that are loaded in memory (without loading
273
281
  # any new ones), and return true if is changed for autosave
274
282
  def nested_records_changed_for_autosave?
275
- self.class._reflections.values.any? do |reflection|
276
- if reflection.options[:autosave]
277
- association = association_instance_get(reflection.name)
278
- association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
283
+ @_nested_records_changed_for_autosave_already_called ||= false
284
+ return false if @_nested_records_changed_for_autosave_already_called
285
+ begin
286
+ @_nested_records_changed_for_autosave_already_called = true
287
+ self.class._reflections.values.any? do |reflection|
288
+ if reflection.options[:autosave]
289
+ association = association_instance_get(reflection.name)
290
+ association && Array.wrap(association.target).any?(&:changed_for_autosave?)
291
+ end
279
292
  end
293
+ ensure
294
+ @_nested_records_changed_for_autosave_already_called = false
280
295
  end
281
296
  end
282
297
 
@@ -236,7 +236,7 @@ module ActiveRecord
236
236
  @spec = spec
237
237
 
238
238
  @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
239
- @reaper = Reaper.new self, spec.config[:reaping_frequency]
239
+ @reaper = Reaper.new(self, (spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f))
240
240
  @reaper.run
241
241
 
242
242
  # default max pool size to 5
@@ -365,7 +365,7 @@ module ActiveRecord
365
365
  conn.expire
366
366
  end
367
367
 
368
- release owner
368
+ release conn, owner
369
369
 
370
370
  @available.add conn
371
371
  end
@@ -378,7 +378,7 @@ module ActiveRecord
378
378
  @connections.delete conn
379
379
  @available.delete conn
380
380
 
381
- release conn.owner
381
+ release conn, conn.owner
382
382
 
383
383
  @available.add checkout_new_connection if @available.any_waiting?
384
384
  end
@@ -426,10 +426,12 @@ module ActiveRecord
426
426
  end
427
427
  end
428
428
 
429
- def release(owner)
429
+ def release(conn, owner)
430
430
  thread_id = owner.object_id
431
431
 
432
- @reserved_connections.delete thread_id
432
+ if @reserved_connections[thread_id] == conn
433
+ @reserved_connections.delete thread_id
434
+ end
433
435
  end
434
436
 
435
437
  def new_connection
@@ -258,7 +258,18 @@ module ActiveRecord
258
258
 
259
259
  # Rolls back the transaction (and turns on auto-committing). Must be
260
260
  # done if the transaction block raises an exception or returns false.
261
- def rollback_db_transaction() end
261
+ def rollback_db_transaction
262
+ exec_rollback_db_transaction
263
+ end
264
+
265
+ def exec_rollback_db_transaction() end #:nodoc:
266
+
267
+ def rollback_to_savepoint(name = nil)
268
+ exec_rollback_to_savepoint(name)
269
+ end
270
+
271
+ def exec_rollback_to_savepoint(name = nil) #:nodoc:
272
+ end
262
273
 
263
274
  def default_sequence_name(table, column)
264
275
  nil
@@ -3,7 +3,7 @@ module ActiveRecord
3
3
  module QueryCache
4
4
  class << self
5
5
  def included(base) #:nodoc:
6
- dirties_query_cache base, :insert, :update, :delete
6
+ dirties_query_cache base, :insert, :update, :delete, :rollback_to_savepoint, :rollback_db_transaction
7
7
  end
8
8
 
9
9
  def dirties_query_cache(base, *method_names)
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  execute("SAVEPOINT #{name}")
10
10
  end
11
11
 
12
- def rollback_to_savepoint(name = current_savepoint_name)
12
+ def exec_rollback_to_savepoint(name = current_savepoint_name)
13
13
  execute("ROLLBACK TO SAVEPOINT #{name}")
14
14
  end
15
15
 
@@ -94,11 +94,12 @@ module ActiveRecord
94
94
  # An array of ColumnDefinition objects, representing the column changes
95
95
  # that have been defined.
96
96
  attr_accessor :indexes
97
- attr_reader :name, :temporary, :options, :as
97
+ attr_reader :name, :temporary, :options, :as, :foreign_keys
98
98
 
99
99
  def initialize(types, name, temporary, options, as = nil)
100
100
  @columns_hash = {}
101
101
  @indexes = {}
102
+ @foreign_keys = {}
102
103
  @native = types
103
104
  @temporary = temporary
104
105
  @options = options
@@ -286,6 +287,10 @@ module ActiveRecord
286
287
  indexes[column_name] = options
287
288
  end
288
289
 
290
+ def foreign_key(table_name, options = {}) # :nodoc:
291
+ foreign_keys[table_name] = options
292
+ end
293
+
289
294
  # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
290
295
  # <tt>:updated_at</tt> to the table. See SchemaStatements#add_timestamps
291
296
  #
@@ -297,9 +302,12 @@ module ActiveRecord
297
302
  column(:updated_at, :datetime, options)
298
303
  end
299
304
 
300
- # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
301
- # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+
302
- # by default, the <tt>:type</tt> option can be used to specify a different type.
305
+ # Adds a reference. Optionally adds a +type+ column, if
306
+ # <tt>:polymorphic</tt> option is provided. <tt>references</tt> and
307
+ # <tt>belongs_to</tt> are acceptable. The reference column will be an
308
+ # +integer+ by default, the <tt>:type</tt> option can be used to specify
309
+ # a different type. A foreign key will be created if a +foreign_key+
310
+ # option is passed.
303
311
  #
304
312
  # t.references(:user)
305
313
  # t.references(:user, type: "string")
@@ -310,11 +318,18 @@ module ActiveRecord
310
318
  options = args.extract_options!
311
319
  polymorphic = options.delete(:polymorphic)
312
320
  index_options = options.delete(:index)
321
+ foreign_key_options = options.delete(:foreign_key)
313
322
  type = options.delete(:type) || :integer
323
+
324
+ if polymorphic && foreign_key_options
325
+ raise ArgumentError, "Cannot add a foreign key on a polymorphic relation"
326
+ end
327
+
314
328
  args.each do |col|
315
329
  column("#{col}_id", type, options)
316
330
  column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
317
331
  index(polymorphic ? %w(type id).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
332
+ foreign_key(col.to_s.pluralize, foreign_key_options.is_a?(Hash) ? foreign_key_options : {}) if foreign_key_options
318
333
  end
319
334
  end
320
335
  alias :belongs_to :references
@@ -520,9 +535,12 @@ module ActiveRecord
520
535
  @base.rename_column(name, column_name, new_column_name)
521
536
  end
522
537
 
523
- # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
524
- # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+
525
- # by default, the <tt>:type</tt> option can be used to specify a different type.
538
+ # Adds a reference. Optionally adds a +type+ column, if
539
+ # <tt>:polymorphic</tt> option is provided. <tt>references</tt> and
540
+ # <tt>belongs_to</tt> are acceptable. The reference column will be an
541
+ # +integer+ by default, the <tt>:type</tt> option can be used to specify
542
+ # a different type. A foreign key will be created if a +foreign_key+
543
+ # option is passed.
526
544
  #
527
545
  # t.references(:user)
528
546
  # t.references(:user, type: "string")
@@ -1,4 +1,6 @@
1
1
  require 'active_record/migration/join_table'
2
+ require 'active_support/core_ext/string/access'
3
+ require 'digest'
2
4
 
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters # :nodoc:
@@ -204,7 +206,17 @@ module ActiveRecord
204
206
  end
205
207
 
206
208
  result = execute schema_creation.accept td
207
- td.indexes.each_pair { |c, o| add_index(table_name, c, o) } unless supports_indexes_in_create?
209
+
210
+ unless supports_indexes_in_create?
211
+ td.indexes.each_pair do |column_name, index_options|
212
+ add_index(table_name, column_name, index_options)
213
+ end
214
+ end
215
+
216
+ td.foreign_keys.each_pair do |other_table_name, foreign_key_options|
217
+ add_foreign_key(table_name, other_table_name, foreign_key_options)
218
+ end
219
+
208
220
  result
209
221
  end
210
222
 
@@ -576,9 +588,8 @@ module ActiveRecord
576
588
  # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
577
589
  #
578
590
  def rename_index(table_name, old_name, new_name)
579
- if new_name.length > allowed_index_name_length
580
- raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
581
- end
591
+ validate_index_length!(table_name, new_name)
592
+
582
593
  # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
583
594
  old_index_def = indexes(table_name).detect { |i| i.name == old_name }
584
595
  return unless old_index_def
@@ -627,7 +638,7 @@ module ActiveRecord
627
638
  #
628
639
  # add_belongs_to(:products, :supplier, polymorphic: true)
629
640
  #
630
- # ====== Create a supplier_id, supplier_type columns and appropriate index
641
+ # ====== Create supplier_id, supplier_type columns and appropriate index
631
642
  #
632
643
  # add_reference(:products, :supplier, polymorphic: true, index: true)
633
644
  #
@@ -635,9 +646,16 @@ module ActiveRecord
635
646
  polymorphic = options.delete(:polymorphic)
636
647
  index_options = options.delete(:index)
637
648
  type = options.delete(:type) || :integer
649
+ foreign_key_options = options.delete(:foreign_key)
650
+
651
+ if polymorphic && foreign_key_options
652
+ raise ArgumentError, "Cannot add a foreign key to a polymorphic relation"
653
+ end
654
+
638
655
  add_column(table_name, "#{ref_name}_id", type, options)
639
656
  add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
640
657
  add_index(table_name, polymorphic ? %w[type id].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
658
+ add_foreign_key(table_name, ref_name.to_s.pluralize, foreign_key_options.is_a?(Hash) ? foreign_key_options : {}) if foreign_key_options
641
659
  end
642
660
  alias :add_belongs_to :add_reference
643
661
 
@@ -652,7 +670,13 @@ module ActiveRecord
652
670
  #
653
671
  # remove_reference(:products, :supplier, polymorphic: true)
654
672
  #
673
+ # ====== Remove the reference with a foreign key
674
+ #
675
+ # remove_reference(:products, :user, index: true, foreign_key: true)
676
+ #
655
677
  def remove_reference(table_name, ref_name, options = {})
678
+ remove_foreign_key table_name, ref_name.to_s.pluralize if options[:foreign_key]
679
+
656
680
  remove_column(table_name, "#{ref_name}_id")
657
681
  remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
658
682
  end
@@ -982,8 +1006,16 @@ module ActiveRecord
982
1006
  end
983
1007
 
984
1008
  def foreign_key_name(table_name, options) # :nodoc:
1009
+ identifier = "#{table_name}_#{options.fetch(:column)}_fk"
1010
+ hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
985
1011
  options.fetch(:name) do
986
- "fk_rails_#{SecureRandom.hex(5)}"
1012
+ "fk_rails_#{hashed_identifier}"
1013
+ end
1014
+ end
1015
+
1016
+ def validate_index_length!(table_name, new_name)
1017
+ if new_name.length > allowed_index_name_length
1018
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
987
1019
  end
988
1020
  end
989
1021
  end