activerecord 4.1.16 → 4.2.11.3

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 (185) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1162 -1801
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +15 -8
  5. data/lib/active_record/association_relation.rb +13 -0
  6. data/lib/active_record/associations/alias_tracker.rb +3 -12
  7. data/lib/active_record/associations/association.rb +16 -4
  8. data/lib/active_record/associations/association_scope.rb +83 -38
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -10
  10. data/lib/active_record/associations/builder/association.rb +15 -4
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -13
  14. data/lib/active_record/associations/builder/has_many.rb +1 -1
  15. data/lib/active_record/associations/builder/has_one.rb +2 -2
  16. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  17. data/lib/active_record/associations/collection_association.rb +63 -27
  18. data/lib/active_record/associations/collection_proxy.rb +29 -35
  19. data/lib/active_record/associations/foreign_association.rb +11 -0
  20. data/lib/active_record/associations/has_many_association.rb +83 -22
  21. data/lib/active_record/associations/has_many_through_association.rb +49 -26
  22. data/lib/active_record/associations/has_one_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
  24. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  25. data/lib/active_record/associations/join_dependency.rb +26 -13
  26. data/lib/active_record/associations/preloader/association.rb +14 -11
  27. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  28. data/lib/active_record/associations/preloader.rb +36 -26
  29. data/lib/active_record/associations/singular_association.rb +17 -2
  30. data/lib/active_record/associations/through_association.rb +5 -12
  31. data/lib/active_record/associations.rb +158 -49
  32. data/lib/active_record/attribute.rb +163 -0
  33. data/lib/active_record/attribute_assignment.rb +19 -11
  34. data/lib/active_record/attribute_decorators.rb +66 -0
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  36. data/lib/active_record/attribute_methods/dirty.rb +107 -43
  37. data/lib/active_record/attribute_methods/primary_key.rb +7 -8
  38. data/lib/active_record/attribute_methods/query.rb +1 -1
  39. data/lib/active_record/attribute_methods/read.rb +22 -59
  40. data/lib/active_record/attribute_methods/serialization.rb +16 -150
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -40
  42. data/lib/active_record/attribute_methods/write.rb +9 -24
  43. data/lib/active_record/attribute_methods.rb +56 -94
  44. data/lib/active_record/attribute_set/builder.rb +106 -0
  45. data/lib/active_record/attribute_set.rb +81 -0
  46. data/lib/active_record/attributes.rb +147 -0
  47. data/lib/active_record/autosave_association.rb +19 -12
  48. data/lib/active_record/base.rb +13 -24
  49. data/lib/active_record/callbacks.rb +6 -6
  50. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +84 -52
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
  54. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +138 -56
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +268 -71
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +171 -59
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +293 -139
  62. data/lib/active_record/connection_adapters/column.rb +29 -240
  63. data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
  64. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  65. data/lib/active_record/connection_adapters/mysql_adapter.rb +67 -144
  66. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  67. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
  69. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  97. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  98. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +131 -43
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
  101. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
  103. data/lib/active_record/connection_handling.rb +1 -1
  104. data/lib/active_record/core.rb +163 -39
  105. data/lib/active_record/counter_cache.rb +60 -6
  106. data/lib/active_record/enum.rb +9 -11
  107. data/lib/active_record/errors.rb +53 -30
  108. data/lib/active_record/explain.rb +1 -1
  109. data/lib/active_record/explain_subscriber.rb +1 -1
  110. data/lib/active_record/fixtures.rb +55 -69
  111. data/lib/active_record/gem_version.rb +4 -4
  112. data/lib/active_record/inheritance.rb +35 -10
  113. data/lib/active_record/integration.rb +4 -4
  114. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  115. data/lib/active_record/locking/optimistic.rb +46 -26
  116. data/lib/active_record/migration/command_recorder.rb +19 -2
  117. data/lib/active_record/migration/join_table.rb +1 -1
  118. data/lib/active_record/migration.rb +71 -46
  119. data/lib/active_record/model_schema.rb +52 -58
  120. data/lib/active_record/nested_attributes.rb +5 -5
  121. data/lib/active_record/no_touching.rb +1 -1
  122. data/lib/active_record/persistence.rb +46 -26
  123. data/lib/active_record/query_cache.rb +3 -3
  124. data/lib/active_record/querying.rb +10 -7
  125. data/lib/active_record/railtie.rb +18 -11
  126. data/lib/active_record/railties/databases.rake +50 -51
  127. data/lib/active_record/readonly_attributes.rb +0 -1
  128. data/lib/active_record/reflection.rb +273 -114
  129. data/lib/active_record/relation/batches.rb +0 -2
  130. data/lib/active_record/relation/calculations.rb +41 -37
  131. data/lib/active_record/relation/finder_methods.rb +70 -47
  132. data/lib/active_record/relation/merger.rb +39 -29
  133. data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
  134. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -5
  135. data/lib/active_record/relation/predicate_builder.rb +16 -8
  136. data/lib/active_record/relation/query_methods.rb +114 -65
  137. data/lib/active_record/relation/spawn_methods.rb +3 -0
  138. data/lib/active_record/relation.rb +57 -25
  139. data/lib/active_record/result.rb +18 -7
  140. data/lib/active_record/sanitization.rb +12 -2
  141. data/lib/active_record/schema.rb +0 -1
  142. data/lib/active_record/schema_dumper.rb +59 -28
  143. data/lib/active_record/schema_migration.rb +5 -4
  144. data/lib/active_record/scoping/default.rb +6 -4
  145. data/lib/active_record/scoping/named.rb +4 -0
  146. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  147. data/lib/active_record/statement_cache.rb +95 -10
  148. data/lib/active_record/store.rb +5 -5
  149. data/lib/active_record/tasks/database_tasks.rb +61 -6
  150. data/lib/active_record/tasks/mysql_database_tasks.rb +20 -11
  151. data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
  152. data/lib/active_record/timestamp.rb +9 -7
  153. data/lib/active_record/transactions.rb +53 -27
  154. data/lib/active_record/type/big_integer.rb +13 -0
  155. data/lib/active_record/type/binary.rb +50 -0
  156. data/lib/active_record/type/boolean.rb +31 -0
  157. data/lib/active_record/type/date.rb +50 -0
  158. data/lib/active_record/type/date_time.rb +54 -0
  159. data/lib/active_record/type/decimal.rb +64 -0
  160. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  161. data/lib/active_record/type/decorator.rb +14 -0
  162. data/lib/active_record/type/float.rb +19 -0
  163. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  164. data/lib/active_record/type/integer.rb +59 -0
  165. data/lib/active_record/type/mutable.rb +16 -0
  166. data/lib/active_record/type/numeric.rb +36 -0
  167. data/lib/active_record/type/serialized.rb +62 -0
  168. data/lib/active_record/type/string.rb +40 -0
  169. data/lib/active_record/type/text.rb +11 -0
  170. data/lib/active_record/type/time.rb +26 -0
  171. data/lib/active_record/type/time_value.rb +38 -0
  172. data/lib/active_record/type/type_map.rb +64 -0
  173. data/lib/active_record/type/unsigned_integer.rb +15 -0
  174. data/lib/active_record/type/value.rb +110 -0
  175. data/lib/active_record/type.rb +23 -0
  176. data/lib/active_record/validations/associated.rb +5 -3
  177. data/lib/active_record/validations/presence.rb +5 -3
  178. data/lib/active_record/validations/uniqueness.rb +25 -29
  179. data/lib/active_record/validations.rb +25 -19
  180. data/lib/active_record.rb +4 -0
  181. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  182. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +66 -11
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -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)
@@ -43,13 +59,14 @@ module ActiveRecord
43
59
  # index_exists?(:suppliers, :company_id, name: "idx_company_id")
44
60
  #
45
61
  def index_exists?(table_name, column_name, options = {})
46
- column_names = Array(column_name)
47
- index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
48
- if options[:unique]
49
- indexes(table_name).any?{ |i| i.unique && i.name == index_name }
50
- else
51
- indexes(table_name).any?{ |i| i.name == index_name }
52
- end
62
+ column_names = Array(column_name).map(&:to_s)
63
+ index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, column: column_names)
64
+ checks = []
65
+ checks << lambda { |i| i.name == index_name }
66
+ checks << lambda { |i| i.columns == column_names }
67
+ checks << lambda { |i| i.unique } if options[:unique]
68
+
69
+ indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
53
70
  end
54
71
 
55
72
  # Returns an array of Column objects for the table specified by +table_name+.
@@ -71,7 +88,8 @@ module ActiveRecord
71
88
  # column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
72
89
  #
73
90
  def column_exists?(table_name, column_name, type = nil, options = {})
74
- columns(table_name).any?{ |c| c.name == column_name.to_s &&
91
+ column_name = column_name.to_s
92
+ columns(table_name).any?{ |c| c.name == column_name &&
75
93
  (!type || c.type == type) &&
76
94
  (!options.key?(:limit) || c.limit == options[:limit]) &&
77
95
  (!options.key?(:precision) || c.precision == options[:precision]) &&
@@ -130,6 +148,7 @@ module ActiveRecord
130
148
  # Make a temporary table.
131
149
  # [<tt>:force</tt>]
132
150
  # Set to true to drop the table before creating it.
151
+ # Set to +:cascade+ to drop dependent objects as well.
133
152
  # Defaults to false.
134
153
  # [<tt>:as</tt>]
135
154
  # SQL to use to generate the table. When this option is used, the block is
@@ -186,24 +205,33 @@ module ActiveRecord
186
205
  def create_table(table_name, options = {})
187
206
  td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
188
207
 
189
- if !options[:as]
190
- unless options[:id] == false
191
- pk = options.fetch(:primary_key) {
192
- Base.get_primary_key table_name.to_s.singularize
193
- }
194
-
195
- td.primary_key pk, options.fetch(:id, :primary_key), options
208
+ if options[:id] != false && !options[:as]
209
+ pk = options.fetch(:primary_key) do
210
+ Base.get_primary_key table_name.to_s.singularize
196
211
  end
197
212
 
198
- yield td if block_given?
213
+ td.primary_key pk, options.fetch(:id, :primary_key), options
199
214
  end
200
215
 
216
+ yield td if block_given?
217
+
201
218
  if options[:force] && table_exists?(table_name)
202
219
  drop_table(table_name, options)
203
220
  end
204
221
 
205
- execute schema_creation.accept td
206
- td.indexes.each_pair { |c,o| add_index table_name, c, o }
222
+ result = execute schema_creation.accept td
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
+
234
+ result
207
235
  end
208
236
 
209
237
  # Creates a new join table with the name created using the lexical order of the first two
@@ -360,8 +388,12 @@ module ActiveRecord
360
388
 
361
389
  # Drops a table from the database.
362
390
  #
363
- # Although this command ignores +options+ and the block if one is given, it can be helpful
364
- # to provide these in a migration's +change+ method so it can be reverted.
391
+ # [<tt>:force</tt>]
392
+ # Set to +:cascade+ to drop dependent objects as well.
393
+ # Defaults to false.
394
+ #
395
+ # Although this command ignores most +options+ and the block if one is given,
396
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
365
397
  # In that case, +options+ and the block will be used by create_table.
366
398
  def drop_table(table_name, options = {})
367
399
  execute "DROP TABLE #{quote_table_name(table_name)}"
@@ -512,6 +544,8 @@ module ActiveRecord
512
544
  #
513
545
  # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
514
546
  #
547
+ # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
548
+ #
515
549
  # ====== Creating an index with a specific method
516
550
  #
517
551
  # add_index(:developers, :name, using: 'btree')
@@ -570,6 +604,8 @@ module ActiveRecord
570
604
  # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
571
605
  #
572
606
  def rename_index(table_name, old_name, new_name)
607
+ validate_index_length!(table_name, new_name)
608
+
573
609
  # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
574
610
  old_index_def = indexes(table_name).detect { |i| i.name == old_name }
575
611
  return unless old_index_def
@@ -601,27 +637,50 @@ module ActiveRecord
601
637
  indexes(table_name).detect { |i| i.name == index_name }
602
638
  end
603
639
 
604
- # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
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.
605
643
  # <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
606
644
  #
607
- # ====== Create a user_id column
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
+ #
655
+ # ====== Create a user_id integer column
608
656
  #
609
657
  # add_reference(:products, :user)
610
658
  #
611
- # ====== Create a supplier_id and supplier_type columns
659
+ # ====== Create a user_id string column
612
660
  #
613
- # add_belongs_to(:products, :supplier, polymorphic: true)
661
+ # add_reference(:products, :user, type: :string)
614
662
  #
615
- # ====== Create a supplier_id, supplier_type columns and appropriate index
663
+ # ====== Create supplier_id, supplier_type columns and appropriate index
616
664
  #
617
665
  # add_reference(:products, :supplier, polymorphic: true, index: true)
618
666
  #
619
667
  def add_reference(table_name, ref_name, options = {})
620
668
  polymorphic = options.delete(:polymorphic)
621
669
  index_options = options.delete(:index)
622
- add_column(table_name, "#{ref_name}_id", :integer, options)
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
+
677
+ add_column(table_name, "#{ref_name}_id", type, options)
623
678
  add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
624
- add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
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
625
684
  end
626
685
  alias :add_belongs_to :add_reference
627
686
 
@@ -636,12 +695,133 @@ module ActiveRecord
636
695
  #
637
696
  # remove_reference(:products, :supplier, polymorphic: true)
638
697
  #
698
+ # ====== Remove the reference with a foreign key
699
+ #
700
+ # remove_reference(:products, :user, index: true, foreign_key: true)
701
+ #
639
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
+
640
708
  remove_column(table_name, "#{ref_name}_id")
641
709
  remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
642
710
  end
643
711
  alias :remove_belongs_to :remove_reference
644
712
 
713
+ # Returns an array of foreign keys for the given table.
714
+ # The foreign keys are represented as +ForeignKeyDefinition+ objects.
715
+ def foreign_keys(table_name)
716
+ raise NotImplementedError, "foreign_keys is not implemented"
717
+ end
718
+
719
+ # Adds a new foreign key. +from_table+ is the table with the key column,
720
+ # +to_table+ contains the referenced primary key.
721
+ #
722
+ # The foreign key will be named after the following pattern: <tt>fk_rails_<identifier></tt>.
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.
725
+ #
726
+ # ====== Creating a simple foreign key
727
+ #
728
+ # add_foreign_key :articles, :authors
729
+ #
730
+ # generates:
731
+ #
732
+ # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
733
+ #
734
+ # ====== Creating a foreign key on a specific column
735
+ #
736
+ # add_foreign_key :articles, :users, column: :author_id, primary_key: :lng_id
737
+ #
738
+ # generates:
739
+ #
740
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
741
+ #
742
+ # ====== Creating a cascading foreign key
743
+ #
744
+ # add_foreign_key :articles, :authors, on_delete: :cascade
745
+ #
746
+ # generates:
747
+ #
748
+ # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
749
+ #
750
+ # The +options+ hash can include the following keys:
751
+ # [<tt>:column</tt>]
752
+ # The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>
753
+ # [<tt>:primary_key</tt>]
754
+ # The primary key column name on +to_table+. Defaults to +id+.
755
+ # [<tt>:name</tt>]
756
+ # The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
757
+ # [<tt>:on_delete</tt>]
758
+ # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
759
+ # [<tt>:on_update</tt>]
760
+ # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
761
+ def add_foreign_key(from_table, to_table, options = {})
762
+ return unless supports_foreign_keys?
763
+
764
+ options[:column] ||= foreign_key_column_for(to_table)
765
+
766
+ options = {
767
+ column: options[:column],
768
+ primary_key: options[:primary_key],
769
+ name: foreign_key_name(from_table, options),
770
+ on_delete: options[:on_delete],
771
+ on_update: options[:on_update]
772
+ }
773
+ at = create_alter_table from_table
774
+ at.add_foreign_key to_table, options
775
+
776
+ execute schema_creation.accept(at)
777
+ end
778
+
779
+ # Removes the given foreign key from the table.
780
+ #
781
+ # Removes the foreign key on +accounts.branch_id+.
782
+ #
783
+ # remove_foreign_key :accounts, :branches
784
+ #
785
+ # Removes the foreign key on +accounts.owner_id+.
786
+ #
787
+ # remove_foreign_key :accounts, column: :owner_id
788
+ #
789
+ # Removes the foreign key named +special_fk_name+ on the +accounts+ table.
790
+ #
791
+ # remove_foreign_key :accounts, name: :special_fk_name
792
+ #
793
+ def remove_foreign_key(from_table, options_or_to_table = {})
794
+ return unless supports_foreign_keys?
795
+
796
+ if options_or_to_table.is_a?(Hash)
797
+ options = options_or_to_table
798
+ else
799
+ options = { column: foreign_key_column_for(options_or_to_table) }
800
+ end
801
+
802
+ fk_name_to_delete = options.fetch(:name) do
803
+ fk_to_delete = foreign_keys(from_table).detect {|fk| fk.column == options[:column].to_s }
804
+
805
+ if fk_to_delete
806
+ fk_to_delete.name
807
+ else
808
+ raise ArgumentError, "Table '#{from_table}' has no foreign key on column '#{options[:column]}'"
809
+ end
810
+ end
811
+
812
+ at = create_alter_table from_table
813
+ at.drop_foreign_key fk_name_to_delete
814
+
815
+ execute schema_creation.accept(at)
816
+ end
817
+
818
+ def foreign_key_column_for(table_name) # :nodoc:
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"
823
+ end
824
+
645
825
  def dump_schema_information #:nodoc:
646
826
  sm_table = ActiveRecord::Migrator.schema_migrations_table_name
647
827
 
@@ -661,10 +841,9 @@ module ActiveRecord
661
841
  version = version.to_i
662
842
  sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
663
843
 
664
- migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i }
665
- paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" }
666
- versions = Dir[*paths].map do |filename|
667
- 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
668
847
  end
669
848
 
670
849
  unless migrated.include?(version)
@@ -710,19 +889,23 @@ module ActiveRecord
710
889
  end
711
890
 
712
891
  # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
713
- # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax - they
892
+ # PostgreSQL, MySQL, and Oracle overrides this for custom DISTINCT syntax - they
714
893
  # require the order columns appear in the SELECT.
715
894
  #
716
895
  # columns_for_distinct("posts.id", ["posts.created_at desc"])
717
- def columns_for_distinct(columns, orders) #:nodoc:
896
+ #
897
+ def columns_for_distinct(columns, orders) # :nodoc:
718
898
  columns
719
899
  end
720
900
 
721
- # Adds timestamps (+created_at+ and +updated_at+) columns to the named table.
901
+ include TimestampDefaultDeprecation
902
+ # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
903
+ # Additional options (like <tt>null: false</tt>) are forwarded to #add_column.
722
904
  #
723
- # add_timestamps(:suppliers)
905
+ # add_timestamps(:suppliers, null: false)
724
906
  #
725
907
  def add_timestamps(table_name, options = {})
908
+ emit_warning_if_null_unspecified(:add_timestamps, options)
726
909
  add_column table_name, :created_at, :datetime, options
727
910
  add_column table_name, :updated_at, :datetime, options
728
911
  end
@@ -731,7 +914,7 @@ module ActiveRecord
731
914
  #
732
915
  # remove_timestamps(:suppliers)
733
916
  #
734
- def remove_timestamps(table_name)
917
+ def remove_timestamps(table_name, options = {})
735
918
  remove_column table_name, :updated_at
736
919
  remove_column table_name, :created_at
737
920
  end
@@ -740,6 +923,40 @@ module ActiveRecord
740
923
  Table.new(table_name, base)
741
924
  end
742
925
 
926
+ def add_index_options(table_name, column_name, options = {}) #:nodoc:
927
+ column_names = Array(column_name)
928
+ index_name = index_name(table_name, column: column_names)
929
+
930
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
931
+
932
+ index_type = options[:unique] ? "UNIQUE" : ""
933
+ index_type = options[:type].to_s if options.key?(:type)
934
+ index_name = options[:name].to_s if options.key?(:name)
935
+ max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
936
+
937
+ if options.key?(:algorithm)
938
+ algorithm = index_algorithms.fetch(options[:algorithm]) {
939
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
940
+ }
941
+ end
942
+
943
+ using = "USING #{options[:using]}" if options[:using].present?
944
+
945
+ if supports_partial_index?
946
+ index_options = options[:where] ? " WHERE #{options[:where]}" : ""
947
+ end
948
+
949
+ if index_name.length > max_index_length
950
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
951
+ end
952
+ if table_exists?(table_name) && index_name_exists?(table_name, index_name, false)
953
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
954
+ end
955
+ index_columns = quoted_columns_for_index(column_names, options).join(", ")
956
+
957
+ [index_name, index_type, index_columns, index_options, algorithm, using]
958
+ end
959
+
743
960
  protected
744
961
  def add_index_sort_order(option_strings, column_names, options = {})
745
962
  if options.is_a?(Hash) && order = options[:order]
@@ -754,7 +971,7 @@ module ActiveRecord
754
971
  return option_strings
755
972
  end
756
973
 
757
- # Overridden by the mysql adapter for supporting index lengths
974
+ # Overridden by the MySQL adapter for supporting index lengths
758
975
  def quoted_columns_for_index(column_names, options = {})
759
976
  option_strings = Hash[column_names.map {|name| [name, '']}]
760
977
 
@@ -770,40 +987,6 @@ module ActiveRecord
770
987
  options.include?(:default) && !(options[:null] == false && options[:default].nil?)
771
988
  end
772
989
 
773
- def add_index_options(table_name, column_name, options = {})
774
- column_names = Array(column_name)
775
- index_name = index_name(table_name, column: column_names)
776
-
777
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
778
-
779
- index_type = options[:unique] ? "UNIQUE" : ""
780
- index_type = options[:type].to_s if options.key?(:type)
781
- index_name = options[:name].to_s if options.key?(:name)
782
- max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
783
-
784
- if options.key?(:algorithm)
785
- algorithm = index_algorithms.fetch(options[:algorithm]) {
786
- raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
787
- }
788
- end
789
-
790
- using = "USING #{options[:using]}" if options[:using].present?
791
-
792
- if supports_partial_index?
793
- index_options = options[:where] ? " WHERE #{options[:where]}" : ""
794
- end
795
-
796
- if index_name.length > max_index_length
797
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
798
- end
799
- if index_name_exists?(table_name, index_name, false)
800
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
801
- end
802
- index_columns = quoted_columns_for_index(column_names, options).join(", ")
803
-
804
- [index_name, index_type, index_columns, index_options, algorithm, using]
805
- end
806
-
807
990
  def index_name_for_remove(table_name, options = {})
808
991
  index_name = index_name(table_name, options)
809
992
 
@@ -852,6 +1035,20 @@ module ActiveRecord
852
1035
  def create_alter_table(name)
853
1036
  AlterTable.new create_table_definition(name, false, {})
854
1037
  end
1038
+
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)
1042
+ options.fetch(:name) do
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"
1050
+ end
1051
+ end
855
1052
  end
856
1053
  end
857
1054
  end