activerecord 4.2.0 → 4.2.11

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 (110) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -1
  3. data/lib/active_record.rb +3 -0
  4. data/lib/active_record/aggregations.rb +6 -3
  5. data/lib/active_record/association_relation.rb +13 -0
  6. data/lib/active_record/associations.rb +5 -4
  7. data/lib/active_record/associations/association.rb +15 -3
  8. data/lib/active_record/associations/association_scope.rb +1 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +13 -5
  10. data/lib/active_record/associations/builder/association.rb +1 -1
  11. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -4
  13. data/lib/active_record/associations/collection_association.rb +35 -15
  14. data/lib/active_record/associations/collection_proxy.rb +15 -9
  15. data/lib/active_record/associations/foreign_association.rb +11 -0
  16. data/lib/active_record/associations/has_many_association.rb +30 -15
  17. data/lib/active_record/associations/has_many_through_association.rb +11 -2
  18. data/lib/active_record/associations/has_one_association.rb +1 -0
  19. data/lib/active_record/associations/join_dependency.rb +8 -2
  20. data/lib/active_record/associations/join_dependency/join_association.rb +7 -1
  21. data/lib/active_record/associations/preloader.rb +4 -4
  22. data/lib/active_record/associations/preloader/association.rb +5 -1
  23. data/lib/active_record/associations/singular_association.rb +2 -8
  24. data/lib/active_record/associations/through_association.rb +11 -6
  25. data/lib/active_record/attribute.rb +15 -1
  26. data/lib/active_record/attribute_assignment.rb +2 -2
  27. data/lib/active_record/attribute_methods.rb +4 -8
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +5 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +14 -4
  30. data/lib/active_record/attribute_methods/time_zone_conversion.rb +5 -1
  31. data/lib/active_record/attribute_methods/write.rb +1 -1
  32. data/lib/active_record/attribute_set.rb +4 -0
  33. data/lib/active_record/attribute_set/builder.rb +32 -12
  34. data/lib/active_record/attributes.rb +8 -0
  35. data/lib/active_record/autosave_association.rb +24 -9
  36. data/lib/active_record/base.rb +4 -5
  37. data/lib/active_record/callbacks.rb +1 -1
  38. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +12 -6
  39. data/lib/active_record/connection_adapters/abstract/database_statements.rb +23 -3
  40. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -0
  42. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  43. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -16
  44. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +87 -24
  45. data/lib/active_record/connection_adapters/abstract/transaction.rb +2 -6
  46. data/lib/active_record/connection_adapters/abstract_adapter.rb +25 -7
  47. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +73 -10
  48. data/lib/active_record/connection_adapters/column.rb +2 -2
  49. data/lib/active_record/connection_adapters/mysql2_adapter.rb +7 -21
  50. data/lib/active_record/connection_adapters/mysql_adapter.rb +10 -3
  51. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
  52. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -1
  53. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -0
  54. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -0
  55. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -1
  56. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -0
  58. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +17 -5
  59. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +3 -3
  60. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +21 -13
  61. data/lib/active_record/connection_adapters/postgresql_adapter.rb +12 -12
  62. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +12 -28
  63. data/lib/active_record/connection_handling.rb +1 -1
  64. data/lib/active_record/core.rb +28 -15
  65. data/lib/active_record/counter_cache.rb +1 -1
  66. data/lib/active_record/enum.rb +2 -3
  67. data/lib/active_record/errors.rb +6 -5
  68. data/lib/active_record/explain_subscriber.rb +1 -1
  69. data/lib/active_record/fixtures.rb +9 -7
  70. data/lib/active_record/gem_version.rb +1 -1
  71. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  72. data/lib/active_record/locking/optimistic.rb +16 -14
  73. data/lib/active_record/migration.rb +38 -10
  74. data/lib/active_record/model_schema.rb +4 -2
  75. data/lib/active_record/nested_attributes.rb +13 -3
  76. data/lib/active_record/no_touching.rb +1 -1
  77. data/lib/active_record/persistence.rb +7 -4
  78. data/lib/active_record/railtie.rb +5 -3
  79. data/lib/active_record/railties/databases.rake +17 -24
  80. data/lib/active_record/reflection.rb +40 -28
  81. data/lib/active_record/relation.rb +3 -2
  82. data/lib/active_record/relation/calculations.rb +10 -3
  83. data/lib/active_record/relation/delegation.rb +1 -1
  84. data/lib/active_record/relation/finder_methods.rb +4 -16
  85. data/lib/active_record/relation/merger.rb +24 -1
  86. data/lib/active_record/relation/predicate_builder.rb +32 -3
  87. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -2
  88. data/lib/active_record/relation/query_methods.rb +29 -27
  89. data/lib/active_record/relation/spawn_methods.rb +7 -3
  90. data/lib/active_record/schema_dumper.rb +1 -1
  91. data/lib/active_record/schema_migration.rb +1 -4
  92. data/lib/active_record/scoping/default.rb +1 -0
  93. data/lib/active_record/tasks/database_tasks.rb +5 -2
  94. data/lib/active_record/tasks/mysql_database_tasks.rb +30 -16
  95. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -8
  96. data/lib/active_record/transactions.rb +21 -11
  97. data/lib/active_record/type/boolean.rb +1 -0
  98. data/lib/active_record/type/date.rb +4 -0
  99. data/lib/active_record/type/date_time.rb +14 -3
  100. data/lib/active_record/type/decimal.rb +27 -3
  101. data/lib/active_record/type/hash_lookup_type_map.rb +8 -2
  102. data/lib/active_record/type/integer.rb +9 -5
  103. data/lib/active_record/type/numeric.rb +1 -1
  104. data/lib/active_record/type/serialized.rb +7 -1
  105. data/lib/active_record/type/string.rb +4 -0
  106. data/lib/active_record/type/value.rb +9 -0
  107. data/lib/active_record/validations/uniqueness.rb +16 -6
  108. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -3
  109. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -6
  110. metadata +9 -7
@@ -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,8 @@ module ActiveRecord
129
135
  @columns_hash = nil
130
136
  @content_columns = nil
131
137
  @default_attributes = nil
138
+ @persistable_attribute_names = nil
139
+ @attribute_names = nil
132
140
  end
133
141
 
134
142
  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
 
@@ -303,7 +318,7 @@ module ActiveRecord
303
318
  # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
304
319
  # enabled records if they're marked_for_destruction? or destroyed.
305
320
  def association_valid?(reflection, record)
306
- return true if record.destroyed? || record.marked_for_destruction?
321
+ return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
307
322
 
308
323
  validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
309
324
  unless valid = record.valid?(validation_context)
@@ -119,23 +119,22 @@ module ActiveRecord #:nodoc:
119
119
  # All column values are automatically available through basic accessors on the Active Record
120
120
  # object, but sometimes you want to specialize this behavior. This can be done by overwriting
121
121
  # the default accessors (using the same name as the attribute) and calling
122
- # <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually
123
- # change things.
122
+ # +super+ to actually change things.
124
123
  #
125
124
  # class Song < ActiveRecord::Base
126
125
  # # Uses an integer of seconds to hold the length of the song
127
126
  #
128
127
  # def length=(minutes)
129
- # write_attribute(:length, minutes.to_i * 60)
128
+ # super(minutes.to_i * 60)
130
129
  # end
131
130
  #
132
131
  # def length
133
- # read_attribute(:length) / 60
132
+ # super / 60
134
133
  # end
135
134
  # end
136
135
  #
137
136
  # You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
138
- # instead of <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
137
+ # or <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
139
138
  #
140
139
  # == Attribute query methods
141
140
  #
@@ -199,7 +199,7 @@ module ActiveRecord
199
199
  # == Canceling callbacks
200
200
  #
201
201
  # If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are
202
- # cancelled. If an <tt>after_*</tt> callback returns +false+, all the later callbacks are cancelled.
202
+ # cancelled.
203
203
  # Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
204
204
  # methods on the model, which are called last.
205
205
  #
@@ -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
@@ -454,6 +456,10 @@ module ActiveRecord
454
456
  c.verify!
455
457
  end
456
458
  c
459
+ rescue
460
+ remove c
461
+ c.disconnect!
462
+ raise
457
463
  end
458
464
  end
459
465
 
@@ -631,7 +637,7 @@ module ActiveRecord
631
637
  end
632
638
 
633
639
  def pool_from_any_process_for(owner)
634
- owner_to_pool = @owner_to_pool.values.find { |v| v[owner.name] }
640
+ owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[owner.name] }
635
641
  owner_to_pool && owner_to_pool[owner.name]
636
642
  end
637
643
  end
@@ -234,6 +234,10 @@ module ActiveRecord
234
234
  current_transaction.add_record(record)
235
235
  end
236
236
 
237
+ def transaction_state
238
+ current_transaction.state
239
+ end
240
+
237
241
  # Begins the transaction (and turns off auto-committing).
238
242
  def begin_db_transaction() end
239
243
 
@@ -258,7 +262,18 @@ module ActiveRecord
258
262
 
259
263
  # Rolls back the transaction (and turns on auto-committing). Must be
260
264
  # done if the transaction block raises an exception or returns false.
261
- def rollback_db_transaction() end
265
+ def rollback_db_transaction
266
+ exec_rollback_db_transaction
267
+ end
268
+
269
+ def exec_rollback_db_transaction() end #:nodoc:
270
+
271
+ def rollback_to_savepoint(name = nil)
272
+ exec_rollback_to_savepoint(name)
273
+ end
274
+
275
+ def exec_rollback_to_savepoint(name = nil) #:nodoc:
276
+ end
262
277
 
263
278
  def default_sequence_name(table, column)
264
279
  nil
@@ -272,12 +287,17 @@ module ActiveRecord
272
287
  # Inserts the given fixture into the table. Overridden in adapters that require
273
288
  # something beyond a simple insert (eg. Oracle).
274
289
  def insert_fixture(fixture, table_name)
290
+ fixture = fixture.stringify_keys
275
291
  columns = schema_cache.columns_hash(table_name)
276
292
 
277
293
  key_list = []
278
294
  value_list = fixture.map do |name, value|
279
- key_list << quote_column_name(name)
280
- quote(value, columns[name])
295
+ if column = columns[name]
296
+ key_list << quote_column_name(name)
297
+ quote(value, column)
298
+ else
299
+ raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.)
300
+ end
281
301
  end
282
302
 
283
303
  execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert'
@@ -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)
@@ -1,4 +1,5 @@
1
1
  require 'active_support/core_ext/big_decimal/conversions'
2
+ require "active_support/multibyte/chars"
2
3
 
3
4
  module ActiveRecord
4
5
  module ConnectionAdapters # :nodoc:
@@ -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
 
@@ -57,11 +57,11 @@ module ActiveRecord
57
57
  end
58
58
 
59
59
  module TimestampDefaultDeprecation # :nodoc:
60
- def emit_warning_if_null_unspecified(options)
60
+ def emit_warning_if_null_unspecified(sym, options)
61
61
  return if options.key?(:null)
62
62
 
63
63
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
64
- `#timestamp` was called without specifying an option for `null`. In Rails 5,
64
+ `##{sym}` was called without specifying an option for `null`. In Rails 5,
65
65
  this behavior will change to `null: false`. You should manually specify
66
66
  `null: true` to prevent the behavior of your existing migrations from changing.
67
67
  MSG
@@ -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
@@ -255,6 +256,7 @@ module ActiveRecord
255
256
  def column(name, type, options = {})
256
257
  name = name.to_s
257
258
  type = type.to_sym
259
+ options = options.dup
258
260
 
259
261
  if @columns_hash[name] && @columns_hash[name].primary_key?
260
262
  raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
@@ -286,35 +288,46 @@ module ActiveRecord
286
288
  indexes[column_name] = options
287
289
  end
288
290
 
291
+ def foreign_key(table_name, options = {}) # :nodoc:
292
+ foreign_keys.push([table_name, options])
293
+ end
294
+
289
295
  # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
290
296
  # <tt>:updated_at</tt> to the table. See SchemaStatements#add_timestamps
291
297
  #
292
298
  # t.timestamps null: false
293
299
  def timestamps(*args)
294
300
  options = args.extract_options!
295
- emit_warning_if_null_unspecified(options)
301
+ emit_warning_if_null_unspecified(:timestamps, options)
296
302
  column(:created_at, :datetime, options)
297
303
  column(:updated_at, :datetime, options)
298
304
  end
299
305
 
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.
306
+ # Adds a reference.
303
307
  #
304
308
  # t.references(:user)
305
- # t.references(:user, type: "string")
306
- # t.belongs_to(:supplier, polymorphic: true)
309
+ # t.belongs_to(:supplier, foreign_key: true)
307
310
  #
308
- # See SchemaStatements#add_reference
311
+ # See SchemaStatements#add_reference for details of the options you can use.
309
312
  def references(*args)
310
313
  options = args.extract_options!
311
314
  polymorphic = options.delete(:polymorphic)
312
315
  index_options = options.delete(:index)
316
+ foreign_key_options = options.delete(:foreign_key)
313
317
  type = options.delete(:type) || :integer
318
+
319
+ if polymorphic && foreign_key_options
320
+ raise ArgumentError, "Cannot add a foreign key on a polymorphic relation"
321
+ end
322
+
314
323
  args.each do |col|
315
324
  column("#{col}_id", type, options)
316
325
  column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
317
326
  index(polymorphic ? %w(type id).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
327
+ if foreign_key_options
328
+ to_table = Base.pluralize_table_names ? col.to_s.pluralize : col.to_s
329
+ foreign_key(to_table, foreign_key_options.is_a?(Hash) ? foreign_key_options : {})
330
+ end
318
331
  end
319
332
  end
320
333
  alias :belongs_to :references
@@ -520,15 +533,12 @@ module ActiveRecord
520
533
  @base.rename_column(name, column_name, new_column_name)
521
534
  end
522
535
 
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.
536
+ # Adds a reference.
526
537
  #
527
538
  # t.references(:user)
528
- # t.references(:user, type: "string")
529
- # t.belongs_to(:supplier, polymorphic: true)
539
+ # t.belongs_to(:supplier, foreign_key: true)
530
540
  #
531
- # See SchemaStatements#add_reference
541
+ # See SchemaStatements#add_reference for details of the options you can use.
532
542
  def references(*args)
533
543
  options = args.extract_options!
534
544
  args.each do |ref_name|
@@ -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:
@@ -17,6 +19,20 @@ module ActiveRecord
17
19
  table_name[0...table_alias_length].tr('.', '_')
18
20
  end
19
21
 
22
+ # Returns the relation names useable to back Active Record models.
23
+ # For most adapters this means all tables and views.
24
+ def data_sources
25
+ tables
26
+ end
27
+
28
+ # Checks to see if the data source +name+ exists on the database.
29
+ #
30
+ # data_source_exists?(:ebooks)
31
+ #
32
+ def data_source_exists?(name)
33
+ data_sources.include?(name.to_s)
34
+ end
35
+
20
36
  # Checks to see if the table +table_name+ exists on the database.
21
37
  #
22
38
  # table_exists?(:developers)
@@ -204,7 +220,17 @@ module ActiveRecord
204
220
  end
205
221
 
206
222
  result = execute schema_creation.accept td
207
- td.indexes.each_pair { |c, o| add_index(table_name, c, o) } unless supports_indexes_in_create?
223
+
224
+ unless supports_indexes_in_create?
225
+ td.indexes.each_pair do |column_name, index_options|
226
+ add_index(table_name, column_name, index_options)
227
+ end
228
+ end
229
+
230
+ td.foreign_keys.each do |other_table_name, foreign_key_options|
231
+ add_foreign_key(table_name, other_table_name, foreign_key_options)
232
+ end
233
+
208
234
  result
209
235
  end
210
236
 
@@ -518,6 +544,8 @@ module ActiveRecord
518
544
  #
519
545
  # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
520
546
  #
547
+ # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
548
+ #
521
549
  # ====== Creating an index with a specific method
522
550
  #
523
551
  # add_index(:developers, :name, using: 'btree')
@@ -576,9 +604,8 @@ module ActiveRecord
576
604
  # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
577
605
  #
578
606
  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
607
+ validate_index_length!(table_name, new_name)
608
+
582
609
  # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
583
610
  old_index_def = indexes(table_name).detect { |i| i.name == old_name }
584
611
  return unless old_index_def
@@ -610,11 +637,21 @@ module ActiveRecord
610
637
  indexes(table_name).detect { |i| i.name == index_name }
611
638
  end
612
639
 
613
- # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
614
- # The reference column is an +integer+ by default, the <tt>:type</tt> option can be used to specify
615
- # a different type.
640
+ # Adds a reference. The reference column is an integer by default,
641
+ # the <tt>:type</tt> option can be used to specify a different type.
642
+ # Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
616
643
  # <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
617
644
  #
645
+ # The +options+ hash can include the following keys:
646
+ # [<tt>:type</tt>]
647
+ # The reference column type. Defaults to +:integer+.
648
+ # [<tt>:index</tt>]
649
+ # Add an appropriate index. Defaults to false.
650
+ # [<tt>:foreign_key</tt>]
651
+ # Add an appropriate foreign key. Defaults to false.
652
+ # [<tt>:polymorphic</tt>]
653
+ # Wether an additional +_type+ column should be added. Defaults to false.
654
+ #
618
655
  # ====== Create a user_id integer column
619
656
  #
620
657
  # add_reference(:products, :user)
@@ -623,11 +660,7 @@ module ActiveRecord
623
660
  #
624
661
  # add_reference(:products, :user, type: :string)
625
662
  #
626
- # ====== Create a supplier_id and supplier_type columns
627
- #
628
- # add_belongs_to(:products, :supplier, polymorphic: true)
629
- #
630
- # ====== Create a supplier_id, supplier_type columns and appropriate index
663
+ # ====== Create supplier_id, supplier_type columns and appropriate index
631
664
  #
632
665
  # add_reference(:products, :supplier, polymorphic: true, index: true)
633
666
  #
@@ -635,9 +668,19 @@ module ActiveRecord
635
668
  polymorphic = options.delete(:polymorphic)
636
669
  index_options = options.delete(:index)
637
670
  type = options.delete(:type) || :integer
671
+ foreign_key_options = options.delete(:foreign_key)
672
+
673
+ if polymorphic && foreign_key_options
674
+ raise ArgumentError, "Cannot add a foreign key to a polymorphic relation"
675
+ end
676
+
638
677
  add_column(table_name, "#{ref_name}_id", type, options)
639
678
  add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
640
679
  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
680
+ if foreign_key_options
681
+ to_table = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
682
+ add_foreign_key(table_name, to_table, foreign_key_options.is_a?(Hash) ? foreign_key_options : {})
683
+ end
641
684
  end
642
685
  alias :add_belongs_to :add_reference
643
686
 
@@ -652,7 +695,16 @@ module ActiveRecord
652
695
  #
653
696
  # remove_reference(:products, :supplier, polymorphic: true)
654
697
  #
698
+ # ====== Remove the reference with a foreign key
699
+ #
700
+ # remove_reference(:products, :user, index: true, foreign_key: true)
701
+ #
655
702
  def remove_reference(table_name, ref_name, options = {})
703
+ if options[:foreign_key]
704
+ to_table = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
705
+ remove_foreign_key(table_name, to_table)
706
+ end
707
+
656
708
  remove_column(table_name, "#{ref_name}_id")
657
709
  remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
658
710
  end
@@ -668,8 +720,8 @@ module ActiveRecord
668
720
  # +to_table+ contains the referenced primary key.
669
721
  #
670
722
  # The foreign key will be named after the following pattern: <tt>fk_rails_<identifier></tt>.
671
- # +identifier+ is a 10 character long random string. A custom name can be specified with
672
- # the <tt>:name</tt> option.
723
+ # +identifier+ is a 10 character long string which is deterministically generated from the
724
+ # +from_table+ and +column+. A custom name can be specified with the <tt>:name</tt> option.
673
725
  #
674
726
  # ====== Creating a simple foreign key
675
727
  #
@@ -681,7 +733,7 @@ module ActiveRecord
681
733
  #
682
734
  # ====== Creating a foreign key on a specific column
683
735
  #
684
- # add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id"
736
+ # add_foreign_key :articles, :users, column: :author_id, primary_key: :lng_id
685
737
  #
686
738
  # generates:
687
739
  #
@@ -764,7 +816,10 @@ module ActiveRecord
764
816
  end
765
817
 
766
818
  def foreign_key_column_for(table_name) # :nodoc:
767
- "#{table_name.to_s.singularize}_id"
819
+ prefix = Base.table_name_prefix
820
+ suffix = Base.table_name_suffix
821
+ name = table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s
822
+ "#{name.singularize}_id"
768
823
  end
769
824
 
770
825
  def dump_schema_information #:nodoc:
@@ -786,10 +841,9 @@ module ActiveRecord
786
841
  version = version.to_i
787
842
  sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
788
843
 
789
- migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i }
790
- paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" }
791
- versions = Dir[*paths].map do |filename|
792
- filename.split('/').last.split('_').first.to_i
844
+ migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
845
+ versions = ActiveRecord::Migrator.migration_files(migrations_paths).map do |file|
846
+ ActiveRecord::Migrator.parse_migration_filename(file).first.to_i
793
847
  end
794
848
 
795
849
  unless migrated.include?(version)
@@ -835,11 +889,12 @@ module ActiveRecord
835
889
  end
836
890
 
837
891
  # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
838
- # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax - they
892
+ # PostgreSQL, MySQL, and Oracle overrides this for custom DISTINCT syntax - they
839
893
  # require the order columns appear in the SELECT.
840
894
  #
841
895
  # columns_for_distinct("posts.id", ["posts.created_at desc"])
842
- def columns_for_distinct(columns, orders) #:nodoc:
896
+ #
897
+ def columns_for_distinct(columns, orders) # :nodoc:
843
898
  columns
844
899
  end
845
900
 
@@ -850,7 +905,7 @@ module ActiveRecord
850
905
  # add_timestamps(:suppliers, null: false)
851
906
  #
852
907
  def add_timestamps(table_name, options = {})
853
- emit_warning_if_null_unspecified(options)
908
+ emit_warning_if_null_unspecified(:add_timestamps, options)
854
909
  add_column table_name, :created_at, :datetime, options
855
910
  add_column table_name, :updated_at, :datetime, options
856
911
  end
@@ -982,8 +1037,16 @@ module ActiveRecord
982
1037
  end
983
1038
 
984
1039
  def foreign_key_name(table_name, options) # :nodoc:
1040
+ identifier = "#{table_name}_#{options.fetch(:column)}_fk"
1041
+ hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
985
1042
  options.fetch(:name) do
986
- "fk_rails_#{SecureRandom.hex(5)}"
1043
+ "fk_rails_#{hashed_identifier}"
1044
+ end
1045
+ end
1046
+
1047
+ def validate_index_length!(table_name, new_name)
1048
+ if new_name.length > allowed_index_name_length
1049
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
987
1050
  end
988
1051
  end
989
1052
  end