activerecord 3.2.22.5 → 5.2.8

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 (275) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -621
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +41 -46
  5. data/examples/performance.rb +55 -42
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +264 -236
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +127 -75
  11. data/lib/active_record/associations/association_scope.rb +126 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +78 -27
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
  14. data/lib/active_record/associations/builder/association.rb +117 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +135 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -54
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
  18. data/lib/active_record/associations/builder/has_many.rb +10 -64
  19. data/lib/active_record/associations/builder/has_one.rb +19 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +28 -18
  21. data/lib/active_record/associations/collection_association.rb +226 -293
  22. data/lib/active_record/associations/collection_proxy.rb +1067 -69
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +83 -47
  25. data/lib/active_record/associations/has_many_through_association.rb +98 -65
  26. data/lib/active_record/associations/has_one_association.rb +57 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +212 -164
  32. data/lib/active_record/associations/preloader/association.rb +95 -89
  33. data/lib/active_record/associations/preloader/through_association.rb +84 -44
  34. data/lib/active_record/associations/preloader.rb +123 -111
  35. data/lib/active_record/associations/singular_association.rb +33 -24
  36. data/lib/active_record/associations/through_association.rb +60 -26
  37. data/lib/active_record/associations.rb +1759 -1506
  38. data/lib/active_record/attribute_assignment.rb +60 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +113 -74
  42. data/lib/active_record/attribute_methods/primary_key.rb +106 -77
  43. data/lib/active_record/attribute_methods/query.rb +8 -5
  44. data/lib/active_record/attribute_methods/read.rb +63 -114
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -90
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
  47. data/lib/active_record/attribute_methods/write.rb +43 -45
  48. data/lib/active_record/attribute_methods.rb +366 -149
  49. data/lib/active_record/attributes.rb +266 -0
  50. data/lib/active_record/autosave_association.rb +312 -225
  51. data/lib/active_record/base.rb +114 -505
  52. data/lib/active_record/callbacks.rb +145 -67
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
  69. data/lib/active_record/connection_adapters/column.rb +50 -255
  70. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +59 -210
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
  117. data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +545 -27
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +145 -0
  128. data/lib/active_record/core.rb +559 -0
  129. data/lib/active_record/counter_cache.rb +200 -105
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +107 -69
  132. data/lib/active_record/enum.rb +244 -0
  133. data/lib/active_record/errors.rb +245 -60
  134. data/lib/active_record/explain.rb +35 -71
  135. data/lib/active_record/explain_registry.rb +32 -0
  136. data/lib/active_record/explain_subscriber.rb +18 -9
  137. data/lib/active_record/fixture_set/file.rb +82 -0
  138. data/lib/active_record/fixtures.rb +418 -275
  139. data/lib/active_record/gem_version.rb +17 -0
  140. data/lib/active_record/inheritance.rb +209 -100
  141. data/lib/active_record/integration.rb +116 -21
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  144. data/lib/active_record/locale/en.yml +9 -1
  145. data/lib/active_record/locking/optimistic.rb +107 -94
  146. data/lib/active_record/locking/pessimistic.rb +20 -8
  147. data/lib/active_record/log_subscriber.rb +99 -34
  148. data/lib/active_record/migration/command_recorder.rb +199 -64
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +17 -0
  151. data/lib/active_record/migration.rb +893 -296
  152. data/lib/active_record/model_schema.rb +328 -175
  153. data/lib/active_record/nested_attributes.rb +338 -242
  154. data/lib/active_record/no_touching.rb +58 -0
  155. data/lib/active_record/null_relation.rb +68 -0
  156. data/lib/active_record/persistence.rb +557 -170
  157. data/lib/active_record/query_cache.rb +14 -43
  158. data/lib/active_record/querying.rb +36 -24
  159. data/lib/active_record/railtie.rb +147 -52
  160. data/lib/active_record/railties/console_sandbox.rb +5 -4
  161. data/lib/active_record/railties/controller_runtime.rb +13 -6
  162. data/lib/active_record/railties/databases.rake +206 -488
  163. data/lib/active_record/readonly_attributes.rb +4 -6
  164. data/lib/active_record/reflection.rb +734 -228
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +249 -52
  167. data/lib/active_record/relation/calculations.rb +330 -284
  168. data/lib/active_record/relation/delegation.rb +135 -37
  169. data/lib/active_record/relation/finder_methods.rb +450 -287
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +193 -0
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  179. data/lib/active_record/relation/predicate_builder.rb +132 -43
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +1037 -221
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +48 -151
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +451 -359
  187. data/lib/active_record/result.rb +129 -20
  188. data/lib/active_record/runtime_registry.rb +24 -0
  189. data/lib/active_record/sanitization.rb +164 -136
  190. data/lib/active_record/schema.rb +31 -19
  191. data/lib/active_record/schema_dumper.rb +154 -107
  192. data/lib/active_record/schema_migration.rb +56 -0
  193. data/lib/active_record/scoping/default.rb +108 -98
  194. data/lib/active_record/scoping/named.rb +125 -112
  195. data/lib/active_record/scoping.rb +77 -123
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +10 -6
  198. data/lib/active_record/statement_cache.rb +121 -0
  199. data/lib/active_record/store.rb +175 -16
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +337 -0
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  206. data/lib/active_record/timestamp.rb +80 -41
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +240 -119
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +9 -0
  212. data/lib/active_record/type/date_time.rb +9 -0
  213. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  214. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +71 -0
  218. data/lib/active_record/type/text.rb +11 -0
  219. data/lib/active_record/type/time.rb +21 -0
  220. data/lib/active_record/type/type_map.rb +62 -0
  221. data/lib/active_record/type/unsigned_integer.rb +17 -0
  222. data/lib/active_record/type.rb +79 -0
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +35 -18
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +68 -0
  230. data/lib/active_record/validations/uniqueness.rb +133 -75
  231. data/lib/active_record/validations.rb +53 -43
  232. data/lib/active_record/version.rb +7 -7
  233. data/lib/active_record.rb +89 -57
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +61 -8
  237. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  238. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  239. data/lib/rails/generators/active_record/migration.rb +28 -8
  240. data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
  241. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  243. data/lib/rails/generators/active_record.rb +10 -16
  244. metadata +141 -62
  245. data/examples/associations.png +0 -0
  246. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  247. data/lib/active_record/associations/join_helper.rb +0 -55
  248. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  249. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  250. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  251. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  252. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  253. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  254. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  255. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  256. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  257. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  258. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  259. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  260. data/lib/active_record/dynamic_finder_match.rb +0 -68
  261. data/lib/active_record/dynamic_scope_match.rb +0 -23
  262. data/lib/active_record/fixtures/file.rb +0 -65
  263. data/lib/active_record/identity_map.rb +0 -162
  264. data/lib/active_record/observer.rb +0 -121
  265. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  266. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  267. data/lib/active_record/session_store.rb +0 -360
  268. data/lib/active_record/test_case.rb +0 -73
  269. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  270. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  271. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  272. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  273. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  274. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  275. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,119 +1,201 @@
1
- require 'active_support/core_ext/array/wrap'
2
- require 'active_support/deprecation/reporting'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/migration/join_table"
4
+ require "active_support/core_ext/string/access"
5
+ require "digest/sha2"
3
6
 
4
7
  module ActiveRecord
5
8
  module ConnectionAdapters # :nodoc:
6
9
  module SchemaStatements
7
- # Returns a Hash of mappings from the abstract data types to the native
10
+ include ActiveRecord::Migration::JoinTable
11
+
12
+ # Returns a hash of mappings from the abstract data types to the native
8
13
  # database types. See TableDefinition#column for details on the recognized
9
14
  # abstract data types.
10
15
  def native_database_types
11
16
  {}
12
17
  end
13
18
 
19
+ def table_options(table_name)
20
+ nil
21
+ end
22
+
23
+ # Returns the table comment that's stored in database metadata.
24
+ def table_comment(table_name)
25
+ nil
26
+ end
27
+
14
28
  # Truncates a table alias according to the limits of the current adapter.
15
29
  def table_alias_for(table_name)
16
- table_name[0...table_alias_length].gsub(/\./, '_')
30
+ table_name[0...table_alias_length].tr(".", "_")
31
+ end
32
+
33
+ # Returns the relation names useable to back Active Record models.
34
+ # For most adapters this means all #tables and #views.
35
+ def data_sources
36
+ query_values(data_source_sql, "SCHEMA")
37
+ rescue NotImplementedError
38
+ tables | views
39
+ end
40
+
41
+ # Checks to see if the data source +name+ exists on the database.
42
+ #
43
+ # data_source_exists?(:ebooks)
44
+ #
45
+ def data_source_exists?(name)
46
+ query_values(data_source_sql(name), "SCHEMA").any? if name.present?
47
+ rescue NotImplementedError
48
+ data_sources.include?(name.to_s)
49
+ end
50
+
51
+ # Returns an array of table names defined in the database.
52
+ def tables
53
+ query_values(data_source_sql(type: "BASE TABLE"), "SCHEMA")
17
54
  end
18
55
 
19
56
  # Checks to see if the table +table_name+ exists on the database.
20
57
  #
21
- # === Example
22
58
  # table_exists?(:developers)
59
+ #
23
60
  def table_exists?(table_name)
61
+ query_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present?
62
+ rescue NotImplementedError
24
63
  tables.include?(table_name.to_s)
25
64
  end
26
65
 
66
+ # Returns an array of view names defined in the database.
67
+ def views
68
+ query_values(data_source_sql(type: "VIEW"), "SCHEMA")
69
+ end
70
+
71
+ # Checks to see if the view +view_name+ exists on the database.
72
+ #
73
+ # view_exists?(:ebooks)
74
+ #
75
+ def view_exists?(view_name)
76
+ query_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present?
77
+ rescue NotImplementedError
78
+ views.include?(view_name.to_s)
79
+ end
80
+
27
81
  # Returns an array of indexes for the given table.
28
- # def indexes(table_name, name = nil) end
82
+ def indexes(table_name)
83
+ raise NotImplementedError, "#indexes is not implemented"
84
+ end
29
85
 
30
86
  # Checks to see if an index exists on a table for a given index definition.
31
87
  #
32
- # === Examples
33
- # # Check an index exists
34
- # index_exists?(:suppliers, :company_id)
88
+ # # Check an index exists
89
+ # index_exists?(:suppliers, :company_id)
90
+ #
91
+ # # Check an index on multiple columns exists
92
+ # index_exists?(:suppliers, [:company_id, :company_type])
35
93
  #
36
- # # Check an index on multiple columns exists
37
- # index_exists?(:suppliers, [:company_id, :company_type])
94
+ # # Check a unique index exists
95
+ # index_exists?(:suppliers, :company_id, unique: true)
38
96
  #
39
- # # Check a unique index exists
40
- # index_exists?(:suppliers, :company_id, :unique => true)
97
+ # # Check an index with a custom name exists
98
+ # index_exists?(:suppliers, :company_id, name: "idx_company_id")
41
99
  #
42
- # # Check an index with a custom name exists
43
- # index_exists?(:suppliers, :company_id, :name => "idx_company_id"
44
100
  def index_exists?(table_name, column_name, options = {})
45
- column_names = Array.wrap(column_name)
46
- index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
47
- if options[:unique]
48
- indexes(table_name).any?{ |i| i.unique && i.name == index_name }
49
- else
50
- indexes(table_name).any?{ |i| i.name == index_name }
51
- end
101
+ column_names = Array(column_name).map(&:to_s)
102
+ checks = []
103
+ checks << lambda { |i| Array(i.columns) == column_names }
104
+ checks << lambda { |i| i.unique } if options[:unique]
105
+ checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
106
+
107
+ indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
52
108
  end
53
109
 
54
- # Returns an array of Column objects for the table specified by +table_name+.
55
- # See the concrete implementation for details on the expected parameter values.
56
- def columns(table_name, name = nil) end
110
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
111
+ def columns(table_name)
112
+ table_name = table_name.to_s
113
+ column_definitions(table_name).map do |field|
114
+ new_column_from_field(table_name, field)
115
+ end
116
+ end
57
117
 
58
118
  # Checks to see if a column exists in a given table.
59
119
  #
60
- # === Examples
61
- # # Check a column exists
62
- # column_exists?(:suppliers, :name)
120
+ # # Check a column exists
121
+ # column_exists?(:suppliers, :name)
63
122
  #
64
- # # Check a column exists of a particular type
65
- # column_exists?(:suppliers, :name, :string)
123
+ # # Check a column exists of a particular type
124
+ # column_exists?(:suppliers, :name, :string)
125
+ #
126
+ # # Check a column exists with a specific definition
127
+ # column_exists?(:suppliers, :name, :string, limit: 100)
128
+ # column_exists?(:suppliers, :name, :string, default: 'default')
129
+ # column_exists?(:suppliers, :name, :string, null: false)
130
+ # column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
66
131
  #
67
- # # Check a column exists with a specific definition
68
- # column_exists?(:suppliers, :name, :string, :limit => 100)
69
132
  def column_exists?(table_name, column_name, type = nil, options = {})
70
- columns(table_name).any?{ |c| c.name == column_name.to_s &&
71
- (!type || c.type == type) &&
72
- (!options[:limit] || c.limit == options[:limit]) &&
73
- (!options[:precision] || c.precision == options[:precision]) &&
74
- (!options[:scale] || c.scale == options[:scale]) }
133
+ column_name = column_name.to_s
134
+ checks = []
135
+ checks << lambda { |c| c.name == column_name }
136
+ checks << lambda { |c| c.type == type } if type
137
+ column_options_keys.each do |attr|
138
+ checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)
139
+ end
140
+
141
+ columns(table_name).any? { |c| checks.all? { |check| check[c] } }
142
+ end
143
+
144
+ # Returns just a table's primary key
145
+ def primary_key(table_name)
146
+ pk = primary_keys(table_name)
147
+ pk = pk.first unless pk.size > 1
148
+ pk
75
149
  end
76
150
 
77
151
  # Creates a new table with the name +table_name+. +table_name+ may either
78
152
  # be a String or a Symbol.
79
153
  #
80
- # There are two ways to work with +create_table+. You can use the block
154
+ # There are two ways to work with #create_table. You can use the block
81
155
  # form or the regular form, like this:
82
156
  #
83
157
  # === Block form
84
- # # create_table() passes a TableDefinition object to the block.
85
- # # This form will not only create the table, but also columns for the
86
- # # table.
87
158
  #
88
- # create_table(:suppliers) do |t|
89
- # t.column :name, :string, :limit => 60
90
- # # Other fields here
91
- # end
159
+ # # create_table() passes a TableDefinition object to the block.
160
+ # # This form will not only create the table, but also columns for the
161
+ # # table.
162
+ #
163
+ # create_table(:suppliers) do |t|
164
+ # t.column :name, :string, limit: 60
165
+ # # Other fields here
166
+ # end
92
167
  #
93
168
  # === Block form, with shorthand
94
- # # You can also use the column types as method calls, rather than calling the column method.
95
- # create_table(:suppliers) do |t|
96
- # t.string :name, :limit => 60
97
- # # Other fields here
98
- # end
169
+ #
170
+ # # You can also use the column types as method calls, rather than calling the column method.
171
+ # create_table(:suppliers) do |t|
172
+ # t.string :name, limit: 60
173
+ # # Other fields here
174
+ # end
99
175
  #
100
176
  # === Regular form
101
- # # Creates a table called 'suppliers' with no columns.
102
- # create_table(:suppliers)
103
- # # Add a column to 'suppliers'.
104
- # add_column(:suppliers, :name, :string, {:limit => 60})
177
+ #
178
+ # # Creates a table called 'suppliers' with no columns.
179
+ # create_table(:suppliers)
180
+ # # Add a column to 'suppliers'.
181
+ # add_column(:suppliers, :name, :string, {limit: 60})
105
182
  #
106
183
  # The +options+ hash can include the following keys:
107
184
  # [<tt>:id</tt>]
108
185
  # Whether to automatically add a primary key column. Defaults to true.
109
- # Join tables for +has_and_belongs_to_many+ should set it to false.
186
+ # Join tables for {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many] should set it to false.
187
+ #
188
+ # A Symbol can be used to specify the type of the generated primary key column.
110
189
  # [<tt>:primary_key</tt>]
111
190
  # The name of the primary key, if one is to be added automatically.
112
- # Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
191
+ # Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored.
113
192
  #
114
- # Also note that this just sets the primary key in the table. You additionally
115
- # need to configure the primary key in the model via +self.primary_key=+.
116
- # Models do NOT auto-detect the primary key from their table definition.
193
+ # If an array is passed, a composite primary key will be created.
194
+ #
195
+ # Note that Active Record models will automatically detect their
196
+ # primary key. This can be avoided by using
197
+ # {self.primary_key=}[rdoc-ref:AttributeMethods::PrimaryKey::ClassMethods#primary_key=] on the model
198
+ # to define the key explicitly.
117
199
  #
118
200
  # [<tt>:options</tt>]
119
201
  # Any extra options you want appended to the table definition.
@@ -121,187 +203,458 @@ module ActiveRecord
121
203
  # Make a temporary table.
122
204
  # [<tt>:force</tt>]
123
205
  # Set to true to drop the table before creating it.
206
+ # Set to +:cascade+ to drop dependent objects as well.
124
207
  # Defaults to false.
208
+ # [<tt>:as</tt>]
209
+ # SQL to use to generate the table. When this option is used, the block is
210
+ # ignored, as are the <tt>:id</tt> and <tt>:primary_key</tt> options.
125
211
  #
126
- # ===== Examples
127
212
  # ====== Add a backend specific option to the generated SQL (MySQL)
128
- # create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
213
+ #
214
+ # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
215
+ #
129
216
  # generates:
130
- # CREATE TABLE suppliers (
131
- # id int(11) DEFAULT NULL auto_increment PRIMARY KEY
132
- # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
217
+ #
218
+ # CREATE TABLE suppliers (
219
+ # id bigint auto_increment PRIMARY KEY
220
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
133
221
  #
134
222
  # ====== Rename the primary key column
135
- # create_table(:objects, :primary_key => 'guid') do |t|
136
- # t.column :name, :string, :limit => 80
137
- # end
223
+ #
224
+ # create_table(:objects, primary_key: 'guid') do |t|
225
+ # t.column :name, :string, limit: 80
226
+ # end
227
+ #
228
+ # generates:
229
+ #
230
+ # CREATE TABLE objects (
231
+ # guid bigint auto_increment PRIMARY KEY,
232
+ # name varchar(80)
233
+ # )
234
+ #
235
+ # ====== Change the primary key column type
236
+ #
237
+ # create_table(:tags, id: :string) do |t|
238
+ # t.column :label, :string
239
+ # end
240
+ #
241
+ # generates:
242
+ #
243
+ # CREATE TABLE tags (
244
+ # id varchar PRIMARY KEY,
245
+ # label varchar
246
+ # )
247
+ #
248
+ # ====== Create a composite primary key
249
+ #
250
+ # create_table(:orders, primary_key: [:product_id, :client_id]) do |t|
251
+ # t.belongs_to :product
252
+ # t.belongs_to :client
253
+ # end
254
+ #
138
255
  # generates:
139
- # CREATE TABLE objects (
140
- # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
141
- # name varchar(80)
142
- # )
256
+ #
257
+ # CREATE TABLE order (
258
+ # product_id bigint NOT NULL,
259
+ # client_id bigint NOT NULL
260
+ # );
261
+ #
262
+ # ALTER TABLE ONLY "orders"
263
+ # ADD CONSTRAINT orders_pkey PRIMARY KEY (product_id, client_id);
143
264
  #
144
265
  # ====== Do not add a primary key column
145
- # create_table(:categories_suppliers, :id => false) do |t|
146
- # t.column :category_id, :integer
147
- # t.column :supplier_id, :integer
148
- # end
266
+ #
267
+ # create_table(:categories_suppliers, id: false) do |t|
268
+ # t.column :category_id, :bigint
269
+ # t.column :supplier_id, :bigint
270
+ # end
271
+ #
272
+ # generates:
273
+ #
274
+ # CREATE TABLE categories_suppliers (
275
+ # category_id bigint,
276
+ # supplier_id bigint
277
+ # )
278
+ #
279
+ # ====== Create a temporary table based on a query
280
+ #
281
+ # create_table(:long_query, temporary: true,
282
+ # as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id")
283
+ #
149
284
  # generates:
150
- # CREATE TABLE categories_suppliers (
151
- # category_id int,
152
- # supplier_id int
153
- # )
285
+ #
286
+ # CREATE TEMPORARY TABLE long_query AS
287
+ # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
154
288
  #
155
289
  # See also TableDefinition#column for details on how to create columns.
156
- def create_table(table_name, options = {})
157
- td = table_definition
158
- td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
290
+ def create_table(table_name, comment: nil, **options)
291
+ td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment
292
+
293
+ if options[:id] != false && !options[:as]
294
+ pk = options.fetch(:primary_key) do
295
+ Base.get_primary_key table_name.to_s.singularize
296
+ end
297
+
298
+ if pk.is_a?(Array)
299
+ td.primary_keys pk
300
+ else
301
+ td.primary_key pk, options.fetch(:id, :primary_key), options
302
+ end
303
+ end
159
304
 
160
305
  yield td if block_given?
161
306
 
162
- if options[:force] && table_exists?(table_name)
163
- drop_table(table_name, options)
307
+ if options[:force]
308
+ drop_table(table_name, options.merge(if_exists: true))
309
+ end
310
+
311
+ result = execute schema_creation.accept td
312
+
313
+ unless supports_indexes_in_create?
314
+ td.indexes.each do |column_name, index_options|
315
+ add_index(table_name, column_name, index_options)
316
+ end
317
+ end
318
+
319
+ if supports_comments? && !supports_comments_in_create?
320
+ change_table_comment(table_name, comment) if comment.present?
321
+
322
+ td.columns.each do |column|
323
+ change_column_comment(table_name, column.name, column.comment) if column.comment.present?
324
+ end
164
325
  end
165
326
 
166
- create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
167
- create_sql << "#{quote_table_name(table_name)} ("
168
- create_sql << td.to_sql
169
- create_sql << ") #{options[:options]}"
170
- execute create_sql
327
+ result
328
+ end
329
+
330
+ # Creates a new join table with the name created using the lexical order of the first two
331
+ # arguments. These arguments can be a String or a Symbol.
332
+ #
333
+ # # Creates a table called 'assemblies_parts' with no id.
334
+ # create_join_table(:assemblies, :parts)
335
+ #
336
+ # You can pass an +options+ hash which can include the following keys:
337
+ # [<tt>:table_name</tt>]
338
+ # Sets the table name, overriding the default.
339
+ # [<tt>:column_options</tt>]
340
+ # Any extra options you want appended to the columns definition.
341
+ # [<tt>:options</tt>]
342
+ # Any extra options you want appended to the table definition.
343
+ # [<tt>:temporary</tt>]
344
+ # Make a temporary table.
345
+ # [<tt>:force</tt>]
346
+ # Set to true to drop the table before creating it.
347
+ # Defaults to false.
348
+ #
349
+ # Note that #create_join_table does not create any indices by default; you can use
350
+ # its block form to do so yourself:
351
+ #
352
+ # create_join_table :products, :categories do |t|
353
+ # t.index :product_id
354
+ # t.index :category_id
355
+ # end
356
+ #
357
+ # ====== Add a backend specific option to the generated SQL (MySQL)
358
+ #
359
+ # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
360
+ #
361
+ # generates:
362
+ #
363
+ # CREATE TABLE assemblies_parts (
364
+ # assembly_id bigint NOT NULL,
365
+ # part_id bigint NOT NULL,
366
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
367
+ #
368
+ def create_join_table(table_1, table_2, column_options: {}, **options)
369
+ join_table_name = find_join_table_name(table_1, table_2, options)
370
+
371
+ column_options.reverse_merge!(null: false, index: false)
372
+
373
+ t1_ref, t2_ref = [table_1, table_2].map { |t| t.to_s.singularize }
374
+
375
+ create_table(join_table_name, options.merge!(id: false)) do |td|
376
+ td.references t1_ref, column_options
377
+ td.references t2_ref, column_options
378
+ yield td if block_given?
379
+ end
380
+ end
381
+
382
+ # Drops the join table specified by the given arguments.
383
+ # See #create_join_table for details.
384
+ #
385
+ # Although this command ignores the block if one is given, it can be helpful
386
+ # to provide one in a migration's +change+ method so it can be reverted.
387
+ # In that case, the block will be used by #create_join_table.
388
+ def drop_join_table(table_1, table_2, options = {})
389
+ join_table_name = find_join_table_name(table_1, table_2, options)
390
+ drop_table(join_table_name)
171
391
  end
172
392
 
173
393
  # A block for changing columns in +table+.
174
394
  #
175
- # === Example
176
- # # change_table() yields a Table instance
177
- # change_table(:suppliers) do |t|
178
- # t.column :name, :string, :limit => 60
179
- # # Other column alterations here
180
- # end
395
+ # # change_table() yields a Table instance
396
+ # change_table(:suppliers) do |t|
397
+ # t.column :name, :string, limit: 60
398
+ # # Other column alterations here
399
+ # end
181
400
  #
182
401
  # The +options+ hash can include the following keys:
183
402
  # [<tt>:bulk</tt>]
184
403
  # Set this to true to make this a bulk alter query, such as
185
- # ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ...
404
+ #
405
+ # ALTER TABLE `users` ADD COLUMN age INT, ADD COLUMN birthdate DATETIME ...
186
406
  #
187
407
  # Defaults to false.
188
408
  #
189
- # ===== Examples
409
+ # Only supported on the MySQL and PostgreSQL adapter, ignored elsewhere.
410
+ #
190
411
  # ====== Add a column
191
- # change_table(:suppliers) do |t|
192
- # t.column :name, :string, :limit => 60
193
- # end
412
+ #
413
+ # change_table(:suppliers) do |t|
414
+ # t.column :name, :string, limit: 60
415
+ # end
194
416
  #
195
417
  # ====== Add 2 integer columns
196
- # change_table(:suppliers) do |t|
197
- # t.integer :width, :height, :null => false, :default => 0
198
- # end
418
+ #
419
+ # change_table(:suppliers) do |t|
420
+ # t.integer :width, :height, null: false, default: 0
421
+ # end
199
422
  #
200
423
  # ====== Add created_at/updated_at columns
201
- # change_table(:suppliers) do |t|
202
- # t.timestamps
203
- # end
424
+ #
425
+ # change_table(:suppliers) do |t|
426
+ # t.timestamps
427
+ # end
204
428
  #
205
429
  # ====== Add a foreign key column
206
- # change_table(:suppliers) do |t|
207
- # t.references :company
208
- # end
209
430
  #
210
- # Creates a <tt>company_id(integer)</tt> column
431
+ # change_table(:suppliers) do |t|
432
+ # t.references :company
433
+ # end
434
+ #
435
+ # Creates a <tt>company_id(bigint)</tt> column.
211
436
  #
212
437
  # ====== Add a polymorphic foreign key column
438
+ #
213
439
  # change_table(:suppliers) do |t|
214
- # t.belongs_to :company, :polymorphic => true
440
+ # t.belongs_to :company, polymorphic: true
215
441
  # end
216
442
  #
217
- # Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns
443
+ # Creates <tt>company_type(varchar)</tt> and <tt>company_id(bigint)</tt> columns.
218
444
  #
219
445
  # ====== Remove a column
446
+ #
220
447
  # change_table(:suppliers) do |t|
221
448
  # t.remove :company
222
449
  # end
223
450
  #
224
451
  # ====== Remove several columns
452
+ #
225
453
  # change_table(:suppliers) do |t|
226
454
  # t.remove :company_id
227
455
  # t.remove :width, :height
228
456
  # end
229
457
  #
230
458
  # ====== Remove an index
459
+ #
231
460
  # change_table(:suppliers) do |t|
232
461
  # t.remove_index :company_id
233
462
  # end
234
463
  #
235
- # See also Table for details on
236
- # all of the various column transformation
464
+ # See also Table for details on all of the various column transformations.
237
465
  def change_table(table_name, options = {})
238
466
  if supports_bulk_alter? && options[:bulk]
239
467
  recorder = ActiveRecord::Migration::CommandRecorder.new(self)
240
- yield Table.new(table_name, recorder)
468
+ yield update_table_definition(table_name, recorder)
241
469
  bulk_change_table(table_name, recorder.commands)
242
470
  else
243
- yield Table.new(table_name, self)
471
+ yield update_table_definition(table_name, self)
244
472
  end
245
473
  end
246
474
 
247
475
  # Renames a table.
248
- # ===== Example
249
- # rename_table('octopuses', 'octopi')
476
+ #
477
+ # rename_table('octopuses', 'octopi')
478
+ #
250
479
  def rename_table(table_name, new_name)
251
480
  raise NotImplementedError, "rename_table is not implemented"
252
481
  end
253
482
 
254
483
  # Drops a table from the database.
484
+ #
485
+ # [<tt>:force</tt>]
486
+ # Set to +:cascade+ to drop dependent objects as well.
487
+ # Defaults to false.
488
+ # [<tt>:if_exists</tt>]
489
+ # Set to +true+ to only drop the table if it exists.
490
+ # Defaults to false.
491
+ #
492
+ # Although this command ignores most +options+ and the block if one is given,
493
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
494
+ # In that case, +options+ and the block will be used by #create_table.
255
495
  def drop_table(table_name, options = {})
256
- execute "DROP TABLE #{quote_table_name(table_name)}"
496
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
257
497
  end
258
498
 
259
- # Adds a new column to the named table.
260
- # See TableDefinition#column for details of the options you can use.
499
+ # Add a new +type+ column named +column_name+ to +table_name+.
500
+ #
501
+ # The +type+ parameter is normally one of the migrations native types,
502
+ # which is one of the following:
503
+ # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
504
+ # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
505
+ # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
506
+ # <tt>:binary</tt>, <tt>:boolean</tt>.
507
+ #
508
+ # You may use a type not in this list as long as it is supported by your
509
+ # database (for example, "polygon" in MySQL), but this will not be database
510
+ # agnostic and should usually be avoided.
511
+ #
512
+ # Available options are (none of these exists by default):
513
+ # * <tt>:limit</tt> -
514
+ # Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column
515
+ # and number of bytes for <tt>:text</tt>, <tt>:binary</tt> and <tt>:integer</tt> columns.
516
+ # This option is ignored by some backends.
517
+ # * <tt>:default</tt> -
518
+ # The column's default value. Use +nil+ for +NULL+.
519
+ # * <tt>:null</tt> -
520
+ # Allows or disallows +NULL+ values in the column.
521
+ # * <tt>:precision</tt> -
522
+ # Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
523
+ # * <tt>:scale</tt> -
524
+ # Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
525
+ # * <tt>:comment</tt> -
526
+ # Specifies the comment for the column. This option is ignored by some backends.
527
+ #
528
+ # Note: The precision is the total number of significant digits,
529
+ # and the scale is the number of digits that can be stored following
530
+ # the decimal point. For example, the number 123.45 has a precision of 5
531
+ # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
532
+ # range from -999.99 to 999.99.
533
+ #
534
+ # Please be aware of different RDBMS implementations behavior with
535
+ # <tt>:decimal</tt> columns:
536
+ # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
537
+ # <tt>:precision</tt>, and makes no comments about the requirements of
538
+ # <tt>:precision</tt>.
539
+ # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
540
+ # Default is (10,0).
541
+ # * PostgreSQL: <tt>:precision</tt> [1..infinity],
542
+ # <tt>:scale</tt> [0..infinity]. No default.
543
+ # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
544
+ # but the maximum supported <tt>:precision</tt> is 16. No default.
545
+ # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
546
+ # Default is (38,0).
547
+ # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
548
+ # Default unknown.
549
+ # * SqlServer: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
550
+ # Default (38,0).
551
+ #
552
+ # == Examples
553
+ #
554
+ # add_column(:users, :picture, :binary, limit: 2.megabytes)
555
+ # # ALTER TABLE "users" ADD "picture" blob(2097152)
556
+ #
557
+ # add_column(:articles, :status, :string, limit: 20, default: 'draft', null: false)
558
+ # # ALTER TABLE "articles" ADD "status" varchar(20) DEFAULT 'draft' NOT NULL
559
+ #
560
+ # add_column(:answers, :bill_gates_money, :decimal, precision: 15, scale: 2)
561
+ # # ALTER TABLE "answers" ADD "bill_gates_money" decimal(15,2)
562
+ #
563
+ # add_column(:measurements, :sensor_reading, :decimal, precision: 30, scale: 20)
564
+ # # ALTER TABLE "measurements" ADD "sensor_reading" decimal(30,20)
565
+ #
566
+ # # While :scale defaults to zero on most databases, it
567
+ # # probably wouldn't hurt to include it.
568
+ # add_column(:measurements, :huge_integer, :decimal, precision: 30)
569
+ # # ALTER TABLE "measurements" ADD "huge_integer" decimal(30)
570
+ #
571
+ # # Defines a column that stores an array of a type.
572
+ # add_column(:users, :skills, :text, array: true)
573
+ # # ALTER TABLE "users" ADD "skills" text[]
574
+ #
575
+ # # Defines a column with a database-specific type.
576
+ # add_column(:shapes, :triangle, 'polygon')
577
+ # # ALTER TABLE "shapes" ADD "triangle" polygon
261
578
  def add_column(table_name, column_name, type, options = {})
262
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
263
- add_column_options!(add_column_sql, options)
264
- execute(add_column_sql)
579
+ at = create_alter_table table_name
580
+ at.add_column(column_name, type, options)
581
+ execute schema_creation.accept at
265
582
  end
266
583
 
267
- # Removes the column(s) from the table definition.
268
- # ===== Examples
269
- # remove_column(:suppliers, :qualification)
270
- # remove_columns(:suppliers, :qualification, :experience)
271
- def remove_column(table_name, *column_names)
272
- if column_names.flatten!
273
- message = 'Passing array to remove_columns is deprecated, please use ' +
274
- 'multiple arguments, like: `remove_columns(:posts, :foo, :bar)`'
275
- ActiveSupport::Deprecation.warn message, caller
584
+ # Removes the given columns from the table definition.
585
+ #
586
+ # remove_columns(:suppliers, :qualification, :experience)
587
+ #
588
+ def remove_columns(table_name, *column_names)
589
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.empty?
590
+ column_names.each do |column_name|
591
+ remove_column(table_name, column_name)
276
592
  end
593
+ end
277
594
 
278
- columns_for_remove(table_name, *column_names).each do |column_name|
279
- execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{column_name}"
280
- end
595
+ # Removes the column from the table definition.
596
+ #
597
+ # remove_column(:suppliers, :qualification)
598
+ #
599
+ # The +type+ and +options+ parameters will be ignored if present. It can be helpful
600
+ # to provide these in a migration's +change+ method so it can be reverted.
601
+ # In that case, +type+ and +options+ will be used by #add_column.
602
+ def remove_column(table_name, column_name, type = nil, options = {})
603
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, options)}"
281
604
  end
282
- alias :remove_columns :remove_column
283
605
 
284
606
  # Changes the column's definition according to the new options.
285
607
  # See TableDefinition#column for details of the options you can use.
286
- # ===== Examples
287
- # change_column(:suppliers, :name, :string, :limit => 80)
288
- # change_column(:accounts, :description, :text)
608
+ #
609
+ # change_column(:suppliers, :name, :string, limit: 80)
610
+ # change_column(:accounts, :description, :text)
611
+ #
289
612
  def change_column(table_name, column_name, type, options = {})
290
613
  raise NotImplementedError, "change_column is not implemented"
291
614
  end
292
615
 
293
- # Sets a new default value for a column.
294
- # ===== Examples
295
- # change_column_default(:suppliers, :qualification, 'new')
296
- # change_column_default(:accounts, :authorized, 1)
297
- # change_column_default(:users, :email, nil)
298
- def change_column_default(table_name, column_name, default)
616
+ # Sets a new default value for a column:
617
+ #
618
+ # change_column_default(:suppliers, :qualification, 'new')
619
+ # change_column_default(:accounts, :authorized, 1)
620
+ #
621
+ # Setting the default to +nil+ effectively drops the default:
622
+ #
623
+ # change_column_default(:users, :email, nil)
624
+ #
625
+ # Passing a hash containing +:from+ and +:to+ will make this change
626
+ # reversible in migration:
627
+ #
628
+ # change_column_default(:posts, :state, from: nil, to: "draft")
629
+ #
630
+ def change_column_default(table_name, column_name, default_or_changes)
299
631
  raise NotImplementedError, "change_column_default is not implemented"
300
632
  end
301
633
 
634
+ # Sets or removes a <tt>NOT NULL</tt> constraint on a column. The +null+ flag
635
+ # indicates whether the value can be +NULL+. For example
636
+ #
637
+ # change_column_null(:users, :nickname, false)
638
+ #
639
+ # says nicknames cannot be +NULL+ (adds the constraint), whereas
640
+ #
641
+ # change_column_null(:users, :nickname, true)
642
+ #
643
+ # allows them to be +NULL+ (drops the constraint).
644
+ #
645
+ # The method accepts an optional fourth argument to replace existing
646
+ # <tt>NULL</tt>s with some other value. Use that one when enabling the
647
+ # constraint if needed, since otherwise those rows would not be valid.
648
+ #
649
+ # Please note the fourth argument does not set a column's default.
650
+ def change_column_null(table_name, column_name, null, default = nil)
651
+ raise NotImplementedError, "change_column_null is not implemented"
652
+ end
653
+
302
654
  # Renames a column.
303
- # ===== Example
304
- # rename_column(:suppliers, :description, :name)
655
+ #
656
+ # rename_column(:suppliers, :description, :name)
657
+ #
305
658
  def rename_column(table_name, column_name, new_column_name)
306
659
  raise NotImplementedError, "rename_column is not implemented"
307
660
  end
@@ -312,165 +665,407 @@ module ActiveRecord
312
665
  # The index will be named after the table and the column name(s), unless
313
666
  # you pass <tt>:name</tt> as an option.
314
667
  #
315
- # ===== Examples
316
- #
317
668
  # ====== Creating a simple index
318
- # add_index(:suppliers, :name)
319
- # generates
320
- # CREATE INDEX suppliers_name_index ON suppliers(name)
669
+ #
670
+ # add_index(:suppliers, :name)
671
+ #
672
+ # generates:
673
+ #
674
+ # CREATE INDEX suppliers_name_index ON suppliers(name)
321
675
  #
322
676
  # ====== Creating a unique index
323
- # add_index(:accounts, [:branch_id, :party_id], :unique => true)
324
- # generates
325
- # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
677
+ #
678
+ # add_index(:accounts, [:branch_id, :party_id], unique: true)
679
+ #
680
+ # generates:
681
+ #
682
+ # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
326
683
  #
327
684
  # ====== Creating a named index
328
- # add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
329
- # generates
685
+ #
686
+ # add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
687
+ #
688
+ # generates:
689
+ #
330
690
  # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
331
691
  #
332
692
  # ====== Creating an index with specific key length
333
- # add_index(:accounts, :name, :name => 'by_name', :length => 10)
334
- # generates
335
- # CREATE INDEX by_name ON accounts(name(10))
336
693
  #
337
- # add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15})
338
- # generates
339
- # CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
694
+ # add_index(:accounts, :name, name: 'by_name', length: 10)
695
+ #
696
+ # generates:
697
+ #
698
+ # CREATE INDEX by_name ON accounts(name(10))
340
699
  #
341
- # Note: SQLite doesn't support index length
700
+ # ====== Creating an index with specific key lengths for multiple keys
701
+ #
702
+ # add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
703
+ #
704
+ # generates:
705
+ #
706
+ # CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
707
+ #
708
+ # Note: SQLite doesn't support index length.
342
709
  #
343
710
  # ====== Creating an index with a sort order (desc or asc, asc is the default)
344
- # add_index(:accounts, [:branch_id, :party_id, :surname], :order => {:branch_id => :desc, :part_id => :asc})
345
- # generates
346
- # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
347
711
  #
348
- # Note: mysql doesn't yet support index order (it accepts the syntax but ignores it)
712
+ # add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
713
+ #
714
+ # generates:
715
+ #
716
+ # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
717
+ #
718
+ # Note: MySQL only supports index order from 8.0.1 onwards (earlier versions accepted the syntax but ignored it).
719
+ #
720
+ # ====== Creating a partial index
721
+ #
722
+ # add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
723
+ #
724
+ # generates:
725
+ #
726
+ # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
727
+ #
728
+ # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
729
+ #
730
+ # ====== Creating an index with a specific method
731
+ #
732
+ # add_index(:developers, :name, using: 'btree')
733
+ #
734
+ # generates:
735
+ #
736
+ # CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL
737
+ # CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL
738
+ #
739
+ # Note: only supported by PostgreSQL and MySQL
740
+ #
741
+ # ====== Creating an index with a specific operator class
742
+ #
743
+ # add_index(:developers, :name, using: 'gist', opclass: :gist_trgm_ops)
744
+ # # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL
745
+ #
746
+ # add_index(:developers, [:name, :city], using: 'gist', opclass: { city: :gist_trgm_ops })
747
+ # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
748
+ #
749
+ # add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops)
750
+ # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
751
+ #
752
+ # Note: only supported by PostgreSQL
753
+ #
754
+ # ====== Creating an index with a specific type
755
+ #
756
+ # add_index(:developers, :name, type: :fulltext)
757
+ #
758
+ # generates:
759
+ #
760
+ # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
349
761
  #
762
+ # Note: only supported by MySQL.
350
763
  def add_index(table_name, column_name, options = {})
351
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
352
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})"
764
+ index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
765
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
353
766
  end
354
767
 
355
- # Remove the given index from the table.
768
+ # Removes the given index from the table.
769
+ #
770
+ # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
771
+ #
772
+ # remove_index :accounts, :branch_id
773
+ #
774
+ # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
775
+ #
776
+ # remove_index :accounts, column: :branch_id
777
+ #
778
+ # Removes the index on +branch_id+ and +party_id+ in the +accounts+ table if exactly one such index exists.
779
+ #
780
+ # remove_index :accounts, column: [:branch_id, :party_id]
781
+ #
782
+ # Removes the index named +by_branch_party+ in the +accounts+ table.
783
+ #
784
+ # remove_index :accounts, name: :by_branch_party
356
785
  #
357
- # Remove the index_accounts_on_column in the accounts table.
358
- # remove_index :accounts, :column
359
- # Remove the index named index_accounts_on_branch_id in the accounts table.
360
- # remove_index :accounts, :column => :branch_id
361
- # Remove the index named index_accounts_on_branch_id_and_party_id in the accounts table.
362
- # remove_index :accounts, :column => [:branch_id, :party_id]
363
- # Remove the index named by_branch_party in the accounts table.
364
- # remove_index :accounts, :name => :by_branch_party
365
786
  def remove_index(table_name, options = {})
366
- remove_index!(table_name, index_name_for_remove(table_name, options))
367
- end
368
-
369
- def remove_index!(table_name, index_name) #:nodoc:
787
+ index_name = index_name_for_remove(table_name, options)
370
788
  execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
371
789
  end
372
790
 
373
- # Rename an index.
791
+ # Renames an index.
792
+ #
793
+ # Rename the +index_people_on_last_name+ index to +index_users_on_last_name+:
374
794
  #
375
- # Rename the index_people_on_last_name index to index_users_on_last_name
376
795
  # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
796
+ #
377
797
  def rename_index(table_name, old_name, new_name)
378
- # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
798
+ validate_index_length!(table_name, new_name)
799
+
800
+ # this is a naive implementation; some DBs may support this more efficiently (PostgreSQL, for instance)
379
801
  old_index_def = indexes(table_name).detect { |i| i.name == old_name }
380
802
  return unless old_index_def
381
- remove_index(table_name, :name => old_name)
382
- add_index(table_name, old_index_def.columns, :name => new_name, :unique => old_index_def.unique)
803
+ add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique)
804
+ remove_index(table_name, name: old_name)
383
805
  end
384
806
 
385
807
  def index_name(table_name, options) #:nodoc:
386
- if Hash === options # legacy support
808
+ if Hash === options
387
809
  if options[:column]
388
- "index_#{table_name}_on_#{Array.wrap(options[:column]) * '_and_'}"
810
+ "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
389
811
  elsif options[:name]
390
812
  options[:name]
391
813
  else
392
814
  raise ArgumentError, "You must specify the index name"
393
815
  end
394
816
  else
395
- index_name(table_name, :column => options)
817
+ index_name(table_name, index_name_options(options))
396
818
  end
397
819
  end
398
820
 
399
- # Verify the existence of an index with a given name.
400
- #
401
- # The default argument is returned if the underlying implementation does not define the indexes method,
402
- # as there's no way to determine the correct answer in that case.
403
- def index_name_exists?(table_name, index_name, default)
404
- return default unless respond_to?(:indexes)
821
+ # Verifies the existence of an index with a given name.
822
+ def index_name_exists?(table_name, index_name)
405
823
  index_name = index_name.to_s
406
824
  indexes(table_name).detect { |i| i.name == index_name }
407
825
  end
408
826
 
409
- # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
410
- # entire structure of the database.
411
- def structure_dump
827
+ # Adds a reference. The reference column is a bigint by default,
828
+ # the <tt>:type</tt> option can be used to specify a different type.
829
+ # Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
830
+ # #add_reference and #add_belongs_to are acceptable.
831
+ #
832
+ # The +options+ hash can include the following keys:
833
+ # [<tt>:type</tt>]
834
+ # The reference column type. Defaults to +:bigint+.
835
+ # [<tt>:index</tt>]
836
+ # Add an appropriate index. Defaults to true.
837
+ # See #add_index for usage of this option.
838
+ # [<tt>:foreign_key</tt>]
839
+ # Add an appropriate foreign key constraint. Defaults to false.
840
+ # [<tt>:polymorphic</tt>]
841
+ # Whether an additional +_type+ column should be added. Defaults to false.
842
+ # [<tt>:null</tt>]
843
+ # Whether the column allows nulls. Defaults to true.
844
+ #
845
+ # ====== Create a user_id bigint column
846
+ #
847
+ # add_reference(:products, :user)
848
+ #
849
+ # ====== Create a user_id string column
850
+ #
851
+ # add_reference(:products, :user, type: :string)
852
+ #
853
+ # ====== Create supplier_id, supplier_type columns and appropriate index
854
+ #
855
+ # add_reference(:products, :supplier, polymorphic: true, index: true)
856
+ #
857
+ # ====== Create a supplier_id column with a unique index
858
+ #
859
+ # add_reference(:products, :supplier, index: { unique: true })
860
+ #
861
+ # ====== Create a supplier_id column with a named index
862
+ #
863
+ # add_reference(:products, :supplier, index: { name: "my_supplier_index" })
864
+ #
865
+ # ====== Create a supplier_id column and appropriate foreign key
866
+ #
867
+ # add_reference(:products, :supplier, foreign_key: true)
868
+ #
869
+ # ====== Create a supplier_id column and a foreign key to the firms table
870
+ #
871
+ # add_reference(:products, :supplier, foreign_key: {to_table: :firms})
872
+ #
873
+ def add_reference(table_name, ref_name, **options)
874
+ ReferenceDefinition.new(ref_name, options).add_to(update_table_definition(table_name, self))
412
875
  end
876
+ alias :add_belongs_to :add_reference
413
877
 
414
- def dump_schema_information #:nodoc:
415
- sm_table = ActiveRecord::Migrator.schema_migrations_table_name
416
- migrated = select_values("SELECT version FROM #{sm_table} ORDER BY version")
417
- migrated.map { |v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}');" }.join("\n\n")
878
+ # Removes the reference(s). Also removes a +type+ column if one exists.
879
+ # #remove_reference and #remove_belongs_to are acceptable.
880
+ #
881
+ # ====== Remove the reference
882
+ #
883
+ # remove_reference(:products, :user, index: true)
884
+ #
885
+ # ====== Remove polymorphic reference
886
+ #
887
+ # remove_reference(:products, :supplier, polymorphic: true)
888
+ #
889
+ # ====== Remove the reference with a foreign key
890
+ #
891
+ # remove_reference(:products, :user, index: true, foreign_key: true)
892
+ #
893
+ def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
894
+ if foreign_key
895
+ reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
896
+ if foreign_key.is_a?(Hash)
897
+ foreign_key_options = foreign_key
898
+ else
899
+ foreign_key_options = { to_table: reference_name }
900
+ end
901
+ foreign_key_options[:column] ||= "#{ref_name}_id"
902
+ remove_foreign_key(table_name, foreign_key_options)
903
+ end
904
+
905
+ remove_column(table_name, "#{ref_name}_id")
906
+ remove_column(table_name, "#{ref_name}_type") if polymorphic
418
907
  end
908
+ alias :remove_belongs_to :remove_reference
419
909
 
420
- # Should not be called normally, but this operation is non-destructive.
421
- # The migrations module handles this automatically.
422
- def initialize_schema_migrations_table
423
- sm_table = ActiveRecord::Migrator.schema_migrations_table_name
910
+ # Returns an array of foreign keys for the given table.
911
+ # The foreign keys are represented as ForeignKeyDefinition objects.
912
+ def foreign_keys(table_name)
913
+ raise NotImplementedError, "foreign_keys is not implemented"
914
+ end
424
915
 
425
- unless table_exists?(sm_table)
426
- create_table(sm_table, :id => false) do |schema_migrations_table|
427
- schema_migrations_table.column :version, :string, :null => false
428
- end
429
- add_index sm_table, :version, :unique => true,
430
- :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
916
+ # Adds a new foreign key. +from_table+ is the table with the key column,
917
+ # +to_table+ contains the referenced primary key.
918
+ #
919
+ # The foreign key will be named after the following pattern: <tt>fk_rails_<identifier></tt>.
920
+ # +identifier+ is a 10 character long string which is deterministically generated from the
921
+ # +from_table+ and +column+. A custom name can be specified with the <tt>:name</tt> option.
922
+ #
923
+ # ====== Creating a simple foreign key
924
+ #
925
+ # add_foreign_key :articles, :authors
926
+ #
927
+ # generates:
928
+ #
929
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
930
+ #
931
+ # ====== Creating a foreign key on a specific column
932
+ #
933
+ # add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id"
934
+ #
935
+ # generates:
936
+ #
937
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
938
+ #
939
+ # ====== Creating a cascading foreign key
940
+ #
941
+ # add_foreign_key :articles, :authors, on_delete: :cascade
942
+ #
943
+ # generates:
944
+ #
945
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
946
+ #
947
+ # The +options+ hash can include the following keys:
948
+ # [<tt>:column</tt>]
949
+ # The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>
950
+ # [<tt>:primary_key</tt>]
951
+ # The primary key column name on +to_table+. Defaults to +id+.
952
+ # [<tt>:name</tt>]
953
+ # The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
954
+ # [<tt>:on_delete</tt>]
955
+ # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
956
+ # [<tt>:on_update</tt>]
957
+ # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
958
+ # [<tt>:validate</tt>]
959
+ # (Postgres only) Specify whether or not the constraint should be validated. Defaults to +true+.
960
+ def add_foreign_key(from_table, to_table, options = {})
961
+ return unless supports_foreign_keys?
962
+
963
+ options = foreign_key_options(from_table, to_table, options)
964
+ at = create_alter_table from_table
965
+ at.add_foreign_key to_table, options
431
966
 
432
- # Backwards-compatibility: if we find schema_info, assume we've
433
- # migrated up to that point:
434
- si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix
967
+ execute schema_creation.accept(at)
968
+ end
435
969
 
436
- if table_exists?(si_table)
437
- ActiveSupport::Deprecation.warn "Usage of the schema table `#{si_table}` is deprecated. Please switch to using `schema_migrations` table"
970
+ # Removes the given foreign key from the table. Any option parameters provided
971
+ # will be used to re-add the foreign key in case of a migration rollback.
972
+ # It is recommended that you provide any options used when creating the foreign
973
+ # key so that the migration can be reverted properly.
974
+ #
975
+ # Removes the foreign key on +accounts.branch_id+.
976
+ #
977
+ # remove_foreign_key :accounts, :branches
978
+ #
979
+ # Removes the foreign key on +accounts.owner_id+.
980
+ #
981
+ # remove_foreign_key :accounts, column: :owner_id
982
+ #
983
+ # Removes the foreign key named +special_fk_name+ on the +accounts+ table.
984
+ #
985
+ # remove_foreign_key :accounts, name: :special_fk_name
986
+ #
987
+ # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
988
+ def remove_foreign_key(from_table, options_or_to_table = {})
989
+ return unless supports_foreign_keys?
438
990
 
439
- old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i
440
- assume_migrated_upto_version(old_version)
441
- drop_table(si_table)
442
- end
443
- end
991
+ fk_name_to_delete = foreign_key_for!(from_table, options_or_to_table).name
992
+
993
+ at = create_alter_table from_table
994
+ at.drop_foreign_key fk_name_to_delete
995
+
996
+ execute schema_creation.accept(at)
997
+ end
998
+
999
+ # Checks to see if a foreign key exists on a table for a given foreign key definition.
1000
+ #
1001
+ # # Checks to see if a foreign key exists.
1002
+ # foreign_key_exists?(:accounts, :branches)
1003
+ #
1004
+ # # Checks to see if a foreign key on a specified column exists.
1005
+ # foreign_key_exists?(:accounts, column: :owner_id)
1006
+ #
1007
+ # # Checks to see if a foreign key with a custom name exists.
1008
+ # foreign_key_exists?(:accounts, name: "special_fk_name")
1009
+ #
1010
+ def foreign_key_exists?(from_table, options_or_to_table = {})
1011
+ foreign_key_for(from_table, options_or_to_table).present?
1012
+ end
1013
+
1014
+ def foreign_key_column_for(table_name) # :nodoc:
1015
+ prefix = Base.table_name_prefix
1016
+ suffix = Base.table_name_suffix
1017
+ name = table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s
1018
+ "#{name.singularize}_id"
444
1019
  end
445
1020
 
446
- def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
447
- migrations_paths = Array.wrap(migrations_paths)
1021
+ def foreign_key_options(from_table, to_table, options) # :nodoc:
1022
+ options = options.dup
1023
+ options[:column] ||= foreign_key_column_for(to_table)
1024
+ options[:name] ||= foreign_key_name(from_table, options)
1025
+ options
1026
+ end
1027
+
1028
+ def dump_schema_information #:nodoc:
1029
+ versions = ActiveRecord::SchemaMigration.all_versions
1030
+ insert_versions_sql(versions) if versions.any?
1031
+ end
1032
+
1033
+ def internal_string_options_for_primary_key # :nodoc:
1034
+ { primary_key: true }
1035
+ end
1036
+
1037
+ def assume_migrated_upto_version(version, migrations_paths)
1038
+ migrations_paths = Array(migrations_paths)
448
1039
  version = version.to_i
449
- sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
1040
+ sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
450
1041
 
451
- migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i }
452
- paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" }
453
- versions = Dir[*paths].map do |filename|
454
- filename.split('/').last.split('_').first.to_i
1042
+ migrated = ActiveRecord::SchemaMigration.all_versions.map(&:to_i)
1043
+ versions = migration_context.migration_files.map do |file|
1044
+ migration_context.parse_migration_filename(file).first.to_i
455
1045
  end
456
1046
 
457
1047
  unless migrated.include?(version)
458
- execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
1048
+ execute "INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})"
459
1049
  end
460
1050
 
461
- inserted = Set.new
462
- (versions - migrated).each do |v|
463
- if inserted.include?(v)
464
- raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict."
465
- elsif v < version
466
- execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
467
- inserted << v
1051
+ inserting = (versions - migrated).select { |v| v < version }
1052
+ if inserting.any?
1053
+ if (duplicate = inserting.detect { |v| inserting.count(v) > 1 })
1054
+ raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
1055
+ end
1056
+ if supports_multi_insert?
1057
+ execute insert_versions_sql(inserting)
1058
+ else
1059
+ inserting.each do |v|
1060
+ execute insert_versions_sql(v)
1061
+ end
468
1062
  end
469
1063
  end
470
1064
  end
471
1065
 
472
- def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
473
- if native = native_database_types[type.to_sym]
1066
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) # :nodoc:
1067
+ type = type.to_sym if type
1068
+ if native = native_database_types[type]
474
1069
  column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
475
1070
 
476
1071
  if type == :decimal # ignore limit, use precision and scale
@@ -483,124 +1078,319 @@ module ActiveRecord
483
1078
  column_type_sql << "(#{precision})"
484
1079
  end
485
1080
  elsif scale
486
- raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified"
1081
+ raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
487
1082
  end
488
1083
 
1084
+ elsif [:datetime, :timestamp, :time, :interval].include?(type) && precision ||= native[:precision]
1085
+ if (0..6) === precision
1086
+ column_type_sql << "(#{precision})"
1087
+ else
1088
+ raise(ActiveRecordError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6")
1089
+ end
489
1090
  elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
490
1091
  column_type_sql << "(#{limit})"
491
1092
  end
492
1093
 
493
1094
  column_type_sql
494
1095
  else
495
- type
1096
+ type.to_s
496
1097
  end
497
1098
  end
498
1099
 
499
- def add_column_options!(sql, options) #:nodoc:
500
- sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
501
- # must explicitly check for :null to allow change_column to work on migrations
502
- if options[:null] == false
503
- sql << " NOT NULL"
504
- end
1100
+ # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
1101
+ # PostgreSQL, MySQL, and Oracle override this for custom DISTINCT syntax - they
1102
+ # require the order columns appear in the SELECT.
1103
+ #
1104
+ # columns_for_distinct("posts.id", ["posts.created_at desc"])
1105
+ #
1106
+ def columns_for_distinct(columns, orders) # :nodoc:
1107
+ columns
505
1108
  end
506
1109
 
507
- # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
508
- # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax.
1110
+ # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
1111
+ # Additional options (like +:null+) are forwarded to #add_column.
509
1112
  #
510
- # distinct("posts.id", "posts.created_at desc")
511
- def distinct(columns, order_by)
512
- "DISTINCT #{columns}"
513
- end
1113
+ # add_timestamps(:suppliers, null: true)
1114
+ #
1115
+ def add_timestamps(table_name, options = {})
1116
+ options[:null] = false if options[:null].nil?
514
1117
 
515
- # Adds timestamps (created_at and updated_at) columns to the named table.
516
- # ===== Examples
517
- # add_timestamps(:suppliers)
518
- def add_timestamps(table_name)
519
- add_column table_name, :created_at, :datetime
520
- add_column table_name, :updated_at, :datetime
1118
+ add_column table_name, :created_at, :datetime, options
1119
+ add_column table_name, :updated_at, :datetime, options
521
1120
  end
522
1121
 
523
- # Removes the timestamp columns (created_at and updated_at) from the table definition.
524
- # ===== Examples
1122
+ # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
1123
+ #
525
1124
  # remove_timestamps(:suppliers)
526
- def remove_timestamps(table_name)
1125
+ #
1126
+ def remove_timestamps(table_name, options = {})
527
1127
  remove_column table_name, :updated_at
528
1128
  remove_column table_name, :created_at
529
1129
  end
530
1130
 
531
- protected
532
- def add_index_sort_order(option_strings, column_names, options = {})
533
- if options.is_a?(Hash) && order = options[:order]
534
- case order
535
- when Hash
536
- column_names.each {|name| option_strings[name] += " #{order[name].to_s.upcase}" if order.has_key?(name)}
537
- when String
538
- column_names.each {|name| option_strings[name] += " #{order.upcase}"}
539
- end
540
- end
1131
+ def update_table_definition(table_name, base) #:nodoc:
1132
+ Table.new(table_name, base)
1133
+ end
1134
+
1135
+ def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
1136
+ column_names = index_column_names(column_name)
1137
+
1138
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :opclass)
1139
+
1140
+ index_type = options[:type].to_s if options.key?(:type)
1141
+ index_type ||= options[:unique] ? "UNIQUE" : ""
1142
+ index_name = options[:name].to_s if options.key?(:name)
1143
+ index_name ||= index_name(table_name, column_names)
1144
+
1145
+ if options.key?(:algorithm)
1146
+ algorithm = index_algorithms.fetch(options[:algorithm]) {
1147
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
1148
+ }
1149
+ end
1150
+
1151
+ using = "USING #{options[:using]}" if options[:using].present?
1152
+
1153
+ if supports_partial_index?
1154
+ index_options = options[:where] ? " WHERE #{options[:where]}" : ""
1155
+ end
1156
+
1157
+ validate_index_length!(table_name, index_name, options.fetch(:internal, false))
1158
+
1159
+ if data_source_exists?(table_name) && index_name_exists?(table_name, index_name)
1160
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
1161
+ end
1162
+ index_columns = quoted_columns_for_index(column_names, options).join(", ")
541
1163
 
542
- return option_strings
1164
+ [index_name, index_type, index_columns, index_options, algorithm, using, comment]
1165
+ end
1166
+
1167
+ def options_include_default?(options)
1168
+ options.include?(:default) && !(options[:null] == false && options[:default].nil?)
1169
+ end
1170
+
1171
+ # Changes the comment for a table or removes it if +nil+.
1172
+ def change_table_comment(table_name, comment)
1173
+ raise NotImplementedError, "#{self.class} does not support changing table comments"
1174
+ end
1175
+
1176
+ # Changes the comment for a column or removes it if +nil+.
1177
+ def change_column_comment(table_name, column_name, comment)
1178
+ raise NotImplementedError, "#{self.class} does not support changing column comments"
1179
+ end
1180
+
1181
+ def create_schema_dumper(options) # :nodoc:
1182
+ SchemaDumper.create(self, options)
1183
+ end
1184
+
1185
+ private
1186
+ def column_options_keys
1187
+ [:limit, :precision, :scale, :default, :null, :collation, :comment]
1188
+ end
1189
+
1190
+ def add_index_sort_order(quoted_columns, **options)
1191
+ orders = options_for_index_columns(options[:order])
1192
+ quoted_columns.each do |name, column|
1193
+ column << " #{orders[name].upcase}" if orders[name].present?
1194
+ end
543
1195
  end
544
1196
 
545
- # Overridden by the mysql adapter for supporting index lengths
546
- def quoted_columns_for_index(column_names, options = {})
547
- option_strings = Hash[column_names.map {|name| [name, '']}]
1197
+ def options_for_index_columns(options)
1198
+ if options.is_a?(Hash)
1199
+ options.symbolize_keys
1200
+ else
1201
+ Hash.new { |hash, column| hash[column] = options }
1202
+ end
1203
+ end
548
1204
 
549
- # add index sort order if supported
1205
+ # Overridden by the MySQL adapter for supporting index lengths and by
1206
+ # the PostgreSQL adapter for supporting operator classes.
1207
+ def add_options_for_index_columns(quoted_columns, **options)
550
1208
  if supports_index_sort_order?
551
- option_strings = add_index_sort_order(option_strings, column_names, options)
1209
+ quoted_columns = add_index_sort_order(quoted_columns, options)
552
1210
  end
553
1211
 
554
- column_names.map {|name| quote_column_name(name) + option_strings[name]}
1212
+ quoted_columns
555
1213
  end
556
1214
 
557
- def options_include_default?(options)
558
- options.include?(:default) && !(options[:null] == false && options[:default].nil?)
1215
+ def quoted_columns_for_index(column_names, **options)
1216
+ return [column_names] if column_names.is_a?(String)
1217
+
1218
+ quoted_columns = Hash[column_names.map { |name| [name.to_sym, quote_column_name(name).dup] }]
1219
+ add_options_for_index_columns(quoted_columns, options).values
559
1220
  end
560
1221
 
561
- def add_index_options(table_name, column_name, options = {})
562
- column_names = Array.wrap(column_name)
563
- index_name = index_name(table_name, :column => column_names)
1222
+ def index_name_for_remove(table_name, options = {})
1223
+ return options[:name] if can_remove_index_by_name?(options)
1224
+
1225
+ checks = []
564
1226
 
565
- if Hash === options # legacy support, since this param was a string
566
- index_type = options[:unique] ? "UNIQUE" : ""
567
- index_name = options[:name].to_s if options.key?(:name)
1227
+ if options.is_a?(Hash)
1228
+ checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1229
+ column_names = index_column_names(options[:column])
568
1230
  else
569
- index_type = options
1231
+ column_names = index_column_names(options)
570
1232
  end
571
1233
 
572
- if index_name.length > index_name_length
573
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
1234
+ if column_names.present?
1235
+ checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
574
1236
  end
575
- if index_name_exists?(table_name, index_name, false)
576
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
1237
+
1238
+ raise ArgumentError, "No name or columns specified" if checks.none?
1239
+
1240
+ matching_indexes = indexes(table_name).select { |i| checks.all? { |check| check[i] } }
1241
+
1242
+ if matching_indexes.count > 1
1243
+ raise ArgumentError, "Multiple indexes found on #{table_name} columns #{column_names}. " \
1244
+ "Specify an index name from #{matching_indexes.map(&:name).join(', ')}"
1245
+ elsif matching_indexes.none?
1246
+ raise ArgumentError, "No indexes found on #{table_name} with the options provided."
1247
+ else
1248
+ matching_indexes.first.name
577
1249
  end
578
- index_columns = quoted_columns_for_index(column_names, options).join(", ")
1250
+ end
579
1251
 
580
- [index_name, index_type, index_columns]
1252
+ def rename_table_indexes(table_name, new_name)
1253
+ indexes(new_name).each do |index|
1254
+ generated_index_name = index_name(table_name, column: index.columns)
1255
+ if generated_index_name == index.name
1256
+ rename_index new_name, generated_index_name, index_name(new_name, column: index.columns)
1257
+ end
1258
+ end
581
1259
  end
582
1260
 
583
- def index_name_for_remove(table_name, options = {})
584
- index_name = index_name(table_name, options)
1261
+ def rename_column_indexes(table_name, column_name, new_column_name)
1262
+ column_name, new_column_name = column_name.to_s, new_column_name.to_s
1263
+ indexes(table_name).each do |index|
1264
+ next unless index.columns.include?(new_column_name)
1265
+ old_columns = index.columns.dup
1266
+ old_columns[old_columns.index(new_column_name)] = column_name
1267
+ generated_index_name = index_name(table_name, column: old_columns)
1268
+ if generated_index_name == index.name
1269
+ rename_index table_name, generated_index_name, index_name(table_name, column: index.columns)
1270
+ end
1271
+ end
1272
+ end
1273
+
1274
+ def schema_creation
1275
+ SchemaCreation.new(self)
1276
+ end
1277
+
1278
+ def create_table_definition(*args)
1279
+ TableDefinition.new(*args)
1280
+ end
1281
+
1282
+ def create_alter_table(name)
1283
+ AlterTable.new create_table_definition(name)
1284
+ end
1285
+
1286
+ def fetch_type_metadata(sql_type)
1287
+ cast_type = lookup_cast_type(sql_type)
1288
+ SqlTypeMetadata.new(
1289
+ sql_type: sql_type,
1290
+ type: cast_type.type,
1291
+ limit: cast_type.limit,
1292
+ precision: cast_type.precision,
1293
+ scale: cast_type.scale,
1294
+ )
1295
+ end
585
1296
 
586
- unless index_name_exists?(table_name, index_name, true)
587
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
1297
+ def index_column_names(column_names)
1298
+ if column_names.is_a?(String) && /\W/.match?(column_names)
1299
+ column_names
1300
+ else
1301
+ Array(column_names)
1302
+ end
1303
+ end
1304
+
1305
+ def index_name_options(column_names)
1306
+ if column_names.is_a?(String) && /\W/.match?(column_names)
1307
+ column_names = column_names.scan(/\w+/).join("_")
588
1308
  end
589
1309
 
590
- index_name
1310
+ { column: column_names }
591
1311
  end
592
1312
 
593
- def columns_for_remove(table_name, *column_names)
594
- column_names = column_names.flatten
1313
+ def foreign_key_name(table_name, options)
1314
+ options.fetch(:name) do
1315
+ identifier = "#{table_name}_#{options.fetch(:column)}_fk"
1316
+ hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
595
1317
 
596
- raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.blank?
597
- column_names.map {|column_name| quote_column_name(column_name) }
1318
+ "fk_rails_#{hashed_identifier}"
1319
+ end
598
1320
  end
599
1321
 
600
- private
601
- def table_definition
602
- TableDefinition.new(self)
603
- end
1322
+ def foreign_key_for(from_table, options_or_to_table = {})
1323
+ return unless supports_foreign_keys?
1324
+ foreign_keys(from_table).detect { |fk| fk.defined_for? options_or_to_table }
1325
+ end
1326
+
1327
+ def foreign_key_for!(from_table, options_or_to_table = {})
1328
+ foreign_key_for(from_table, options_or_to_table) || \
1329
+ raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}")
1330
+ end
1331
+
1332
+ def extract_foreign_key_action(specifier)
1333
+ case specifier
1334
+ when "CASCADE"; :cascade
1335
+ when "SET NULL"; :nullify
1336
+ when "RESTRICT"; :restrict
1337
+ end
1338
+ end
1339
+
1340
+ def validate_index_length!(table_name, new_name, internal = false)
1341
+ max_index_length = internal ? index_name_length : allowed_index_name_length
1342
+
1343
+ if new_name.length > max_index_length
1344
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
1345
+ end
1346
+ end
1347
+
1348
+ def extract_new_default_value(default_or_changes)
1349
+ if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to)
1350
+ default_or_changes[:to]
1351
+ else
1352
+ default_or_changes
1353
+ end
1354
+ end
1355
+
1356
+ def can_remove_index_by_name?(options)
1357
+ options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
1358
+ end
1359
+
1360
+ def add_column_for_alter(table_name, column_name, type, options = {})
1361
+ td = create_table_definition(table_name)
1362
+ cd = td.new_column_definition(column_name, type, options)
1363
+ schema_creation.accept(AddColumnDefinition.new(cd))
1364
+ end
1365
+
1366
+ def remove_column_for_alter(table_name, column_name, type = nil, options = {})
1367
+ "DROP COLUMN #{quote_column_name(column_name)}"
1368
+ end
1369
+
1370
+ def remove_columns_for_alter(table_name, *column_names)
1371
+ column_names.map { |column_name| remove_column_for_alter(table_name, column_name) }
1372
+ end
1373
+
1374
+ def insert_versions_sql(versions)
1375
+ sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
1376
+
1377
+ if versions.is_a?(Array)
1378
+ sql = "INSERT INTO #{sm_table} (version) VALUES\n".dup
1379
+ sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
1380
+ sql << ";\n\n"
1381
+ sql
1382
+ else
1383
+ "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
1384
+ end
1385
+ end
1386
+
1387
+ def data_source_sql(name = nil, type: nil)
1388
+ raise NotImplementedError
1389
+ end
1390
+
1391
+ def quoted_scope(name = nil, type: nil)
1392
+ raise NotImplementedError
1393
+ end
604
1394
  end
605
1395
  end
606
1396
  end