activerecord 3.2.22.5 → 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 (236) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1632 -609
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +37 -41
  5. data/examples/performance.rb +31 -19
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +56 -42
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -36
  10. data/lib/active_record/associations/association.rb +73 -55
  11. data/lib/active_record/associations/association_scope.rb +143 -82
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +125 -31
  15. data/lib/active_record/associations/builder/belongs_to.rb +89 -61
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +23 -17
  21. data/lib/active_record/associations/collection_association.rb +251 -177
  22. data/lib/active_record/associations/collection_proxy.rb +963 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +113 -22
  25. data/lib/active_record/associations/has_many_through_association.rb +99 -39
  26. data/lib/active_record/associations/has_one_association.rb +43 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +62 -33
  38. data/lib/active_record/associations/preloader.rb +101 -79
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +30 -16
  41. data/lib/active_record/associations.rb +463 -345
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +142 -151
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +137 -57
  47. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +73 -106
  50. data/lib/active_record/attribute_methods/serialization.rb +44 -94
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
  52. data/lib/active_record/attribute_methods/write.rb +57 -44
  53. data/lib/active_record/attribute_methods.rb +301 -141
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +246 -217
  58. data/lib/active_record/base.rb +70 -474
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
  75. data/lib/active_record/connection_adapters/column.rb +31 -245
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
  114. data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +157 -105
  119. data/lib/active_record/dynamic_matchers.rb +119 -63
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +94 -36
  122. data/lib/active_record/explain.rb +15 -63
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +9 -5
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +302 -215
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +143 -70
  129. data/lib/active_record/integration.rb +65 -12
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +73 -52
  133. data/lib/active_record/locking/pessimistic.rb +5 -5
  134. data/lib/active_record/log_subscriber.rb +24 -21
  135. data/lib/active_record/migration/command_recorder.rb +124 -32
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +511 -213
  138. data/lib/active_record/model_schema.rb +91 -117
  139. data/lib/active_record/nested_attributes.rb +184 -130
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +276 -117
  143. data/lib/active_record/query_cache.rb +19 -37
  144. data/lib/active_record/querying.rb +28 -18
  145. data/lib/active_record/railtie.rb +73 -40
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +4 -3
  148. data/lib/active_record/railties/databases.rake +141 -416
  149. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  150. data/lib/active_record/readonly_attributes.rb +1 -4
  151. data/lib/active_record/reflection.rb +513 -154
  152. data/lib/active_record/relation/batches.rb +91 -43
  153. data/lib/active_record/relation/calculations.rb +199 -161
  154. data/lib/active_record/relation/delegation.rb +116 -25
  155. data/lib/active_record/relation/finder_methods.rb +362 -248
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -43
  160. data/lib/active_record/relation/query_methods.rb +928 -167
  161. data/lib/active_record/relation/spawn_methods.rb +48 -149
  162. data/lib/active_record/relation.rb +352 -207
  163. data/lib/active_record/result.rb +101 -10
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +56 -59
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +106 -63
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +50 -57
  170. data/lib/active_record/scoping/named.rb +73 -109
  171. data/lib/active_record/scoping.rb +58 -123
  172. data/lib/active_record/serialization.rb +6 -2
  173. data/lib/active_record/serializers/xml_serializer.rb +12 -22
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +168 -15
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +23 -16
  181. data/lib/active_record/transactions.rb +125 -79
  182. data/lib/active_record/type/big_integer.rb +13 -0
  183. data/lib/active_record/type/binary.rb +50 -0
  184. data/lib/active_record/type/boolean.rb +31 -0
  185. data/lib/active_record/type/date.rb +50 -0
  186. data/lib/active_record/type/date_time.rb +54 -0
  187. data/lib/active_record/type/decimal.rb +64 -0
  188. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  189. data/lib/active_record/type/decorator.rb +14 -0
  190. data/lib/active_record/type/float.rb +19 -0
  191. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  192. data/lib/active_record/type/integer.rb +59 -0
  193. data/lib/active_record/type/mutable.rb +16 -0
  194. data/lib/active_record/type/numeric.rb +36 -0
  195. data/lib/active_record/type/serialized.rb +62 -0
  196. data/lib/active_record/type/string.rb +40 -0
  197. data/lib/active_record/type/text.rb +11 -0
  198. data/lib/active_record/type/time.rb +26 -0
  199. data/lib/active_record/type/time_value.rb +38 -0
  200. data/lib/active_record/type/type_map.rb +64 -0
  201. data/lib/active_record/type/unsigned_integer.rb +15 -0
  202. data/lib/active_record/type/value.rb +110 -0
  203. data/lib/active_record/type.rb +23 -0
  204. data/lib/active_record/validations/associated.rb +24 -16
  205. data/lib/active_record/validations/presence.rb +67 -0
  206. data/lib/active_record/validations/uniqueness.rb +123 -64
  207. data/lib/active_record/validations.rb +36 -29
  208. data/lib/active_record/version.rb +5 -7
  209. data/lib/active_record.rb +66 -46
  210. data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
  211. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
  212. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  213. data/lib/rails/generators/active_record/migration.rb +11 -8
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
  215. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  216. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  217. data/lib/rails/generators/active_record.rb +3 -11
  218. metadata +101 -45
  219. data/examples/associations.png +0 -0
  220. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  221. data/lib/active_record/associations/join_helper.rb +0 -55
  222. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  223. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  226. data/lib/active_record/dynamic_finder_match.rb +0 -68
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/fixtures/file.rb +0 -65
  229. data/lib/active_record/identity_map.rb +0 -162
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -360
  232. data/lib/active_record/test_case.rb +0 -73
  233. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  234. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  235. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  236. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,10 +1,13 @@
1
- require 'active_support/core_ext/array/wrap'
2
- require 'active_support/deprecation/reporting'
1
+ require 'active_record/migration/join_table'
2
+ require 'active_support/core_ext/string/access'
3
+ require 'digest'
3
4
 
4
5
  module ActiveRecord
5
6
  module ConnectionAdapters # :nodoc:
6
7
  module SchemaStatements
7
- # Returns a Hash of mappings from the abstract data types to the native
8
+ include ActiveRecord::Migration::JoinTable
9
+
10
+ # Returns a hash of mappings from the abstract data types to the native
8
11
  # database types. See TableDefinition#column for details on the recognized
9
12
  # abstract data types.
10
13
  def native_database_types
@@ -13,13 +16,27 @@ module ActiveRecord
13
16
 
14
17
  # Truncates a table alias according to the limits of the current adapter.
15
18
  def table_alias_for(table_name)
16
- table_name[0...table_alias_length].gsub(/\./, '_')
19
+ table_name[0...table_alias_length].tr('.', '_')
20
+ end
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)
17
34
  end
18
35
 
19
36
  # Checks to see if the table +table_name+ exists on the database.
20
37
  #
21
- # === Example
22
38
  # table_exists?(:developers)
39
+ #
23
40
  def table_exists?(table_name)
24
41
  tables.include?(table_name.to_s)
25
42
  end
@@ -29,49 +46,56 @@ module ActiveRecord
29
46
 
30
47
  # Checks to see if an index exists on a table for a given index definition.
31
48
  #
32
- # === Examples
33
- # # Check an index exists
34
- # index_exists?(:suppliers, :company_id)
49
+ # # Check an index exists
50
+ # index_exists?(:suppliers, :company_id)
35
51
  #
36
- # # Check an index on multiple columns exists
37
- # index_exists?(:suppliers, [:company_id, :company_type])
52
+ # # Check an index on multiple columns exists
53
+ # index_exists?(:suppliers, [:company_id, :company_type])
38
54
  #
39
- # # Check a unique index exists
40
- # index_exists?(:suppliers, :company_id, :unique => true)
55
+ # # Check a unique index exists
56
+ # index_exists?(:suppliers, :company_id, unique: true)
57
+ #
58
+ # # Check an index with a custom name exists
59
+ # index_exists?(:suppliers, :company_id, name: "idx_company_id")
41
60
  #
42
- # # Check an index with a custom name exists
43
- # index_exists?(:suppliers, :company_id, :name => "idx_company_id"
44
61
  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
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] } }
52
70
  end
53
71
 
54
72
  # Returns an array of Column objects for the table specified by +table_name+.
55
73
  # See the concrete implementation for details on the expected parameter values.
56
- def columns(table_name, name = nil) end
74
+ def columns(table_name) end
57
75
 
58
76
  # Checks to see if a column exists in a given table.
59
77
  #
60
- # === Examples
61
- # # Check a column exists
62
- # column_exists?(:suppliers, :name)
78
+ # # Check a column exists
79
+ # column_exists?(:suppliers, :name)
80
+ #
81
+ # # Check a column exists of a particular type
82
+ # column_exists?(:suppliers, :name, :string)
63
83
  #
64
- # # Check a column exists of a particular type
65
- # column_exists?(:suppliers, :name, :string)
84
+ # # Check a column exists with a specific definition
85
+ # column_exists?(:suppliers, :name, :string, limit: 100)
86
+ # column_exists?(:suppliers, :name, :string, default: 'default')
87
+ # column_exists?(:suppliers, :name, :string, null: false)
88
+ # column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
66
89
  #
67
- # # Check a column exists with a specific definition
68
- # column_exists?(:suppliers, :name, :string, :limit => 100)
69
90
  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]) }
91
+ column_name = column_name.to_s
92
+ columns(table_name).any?{ |c| c.name == column_name &&
93
+ (!type || c.type == type) &&
94
+ (!options.key?(:limit) || c.limit == options[:limit]) &&
95
+ (!options.key?(:precision) || c.precision == options[:precision]) &&
96
+ (!options.key?(:scale) || c.scale == options[:scale]) &&
97
+ (!options.key?(:default) || c.default == options[:default]) &&
98
+ (!options.key?(:null) || c.null == options[:null]) }
75
99
  end
76
100
 
77
101
  # Creates a new table with the name +table_name+. +table_name+ may either
@@ -81,27 +105,30 @@ module ActiveRecord
81
105
  # form or the regular form, like this:
82
106
  #
83
107
  # === 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
108
  #
88
- # create_table(:suppliers) do |t|
89
- # t.column :name, :string, :limit => 60
90
- # # Other fields here
91
- # end
109
+ # # create_table() passes a TableDefinition object to the block.
110
+ # # This form will not only create the table, but also columns for the
111
+ # # table.
112
+ #
113
+ # create_table(:suppliers) do |t|
114
+ # t.column :name, :string, limit: 60
115
+ # # Other fields here
116
+ # end
92
117
  #
93
118
  # === 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
119
+ #
120
+ # # You can also use the column types as method calls, rather than calling the column method.
121
+ # create_table(:suppliers) do |t|
122
+ # t.string :name, limit: 60
123
+ # # Other fields here
124
+ # end
99
125
  #
100
126
  # === 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})
127
+ #
128
+ # # Creates a table called 'suppliers' with no columns.
129
+ # create_table(:suppliers)
130
+ # # Add a column to 'suppliers'.
131
+ # add_column(:suppliers, :name, :string, {limit: 60})
105
132
  #
106
133
  # The +options+ hash can include the following keys:
107
134
  # [<tt>:id</tt>]
@@ -111,9 +138,9 @@ module ActiveRecord
111
138
  # The name of the primary key, if one is to be added automatically.
112
139
  # Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
113
140
  #
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.
141
+ # Note that Active Record models will automatically detect their
142
+ # primary key. This can be avoided by using +self.primary_key=+ on the model
143
+ # to define the key explicitly.
117
144
  #
118
145
  # [<tt>:options</tt>]
119
146
  # Any extra options you want appended to the table definition.
@@ -121,41 +148,70 @@ module ActiveRecord
121
148
  # Make a temporary table.
122
149
  # [<tt>:force</tt>]
123
150
  # Set to true to drop the table before creating it.
151
+ # Set to +:cascade+ to drop dependent objects as well.
124
152
  # Defaults to false.
153
+ # [<tt>:as</tt>]
154
+ # SQL to use to generate the table. When this option is used, the block is
155
+ # ignored, as are the <tt>:id</tt> and <tt>:primary_key</tt> options.
125
156
  #
126
- # ===== Examples
127
157
  # ====== Add a backend specific option to the generated SQL (MySQL)
128
- # create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
158
+ #
159
+ # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
160
+ #
129
161
  # generates:
130
- # CREATE TABLE suppliers (
131
- # id int(11) DEFAULT NULL auto_increment PRIMARY KEY
132
- # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
162
+ #
163
+ # CREATE TABLE suppliers (
164
+ # id int(11) DEFAULT NULL auto_increment PRIMARY KEY
165
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
133
166
  #
134
167
  # ====== Rename the primary key column
135
- # create_table(:objects, :primary_key => 'guid') do |t|
136
- # t.column :name, :string, :limit => 80
137
- # end
168
+ #
169
+ # create_table(:objects, primary_key: 'guid') do |t|
170
+ # t.column :name, :string, limit: 80
171
+ # end
172
+ #
138
173
  # generates:
139
- # CREATE TABLE objects (
140
- # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
141
- # name varchar(80)
142
- # )
174
+ #
175
+ # CREATE TABLE objects (
176
+ # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
177
+ # name varchar(80)
178
+ # )
143
179
  #
144
180
  # ====== 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
181
+ #
182
+ # create_table(:categories_suppliers, id: false) do |t|
183
+ # t.column :category_id, :integer
184
+ # t.column :supplier_id, :integer
185
+ # end
186
+ #
149
187
  # generates:
150
- # CREATE TABLE categories_suppliers (
151
- # category_id int,
152
- # supplier_id int
153
- # )
188
+ #
189
+ # CREATE TABLE categories_suppliers (
190
+ # category_id int,
191
+ # supplier_id int
192
+ # )
193
+ #
194
+ # ====== Create a temporary table based on a query
195
+ #
196
+ # create_table(:long_query, temporary: true,
197
+ # as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id")
198
+ #
199
+ # generates:
200
+ #
201
+ # CREATE TEMPORARY TABLE long_query AS
202
+ # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
154
203
  #
155
204
  # See also TableDefinition#column for details on how to create columns.
156
205
  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
206
+ td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
207
+
208
+ if options[:id] != false && !options[:as]
209
+ pk = options.fetch(:primary_key) do
210
+ Base.get_primary_key table_name.to_s.singularize
211
+ end
212
+
213
+ td.primary_key pk, options.fetch(:id, :primary_key), options
214
+ end
159
215
 
160
216
  yield td if block_given?
161
217
 
@@ -163,95 +219,182 @@ module ActiveRecord
163
219
  drop_table(table_name, options)
164
220
  end
165
221
 
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
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
235
+ end
236
+
237
+ # Creates a new join table with the name created using the lexical order of the first two
238
+ # arguments. These arguments can be a String or a Symbol.
239
+ #
240
+ # # Creates a table called 'assemblies_parts' with no id.
241
+ # create_join_table(:assemblies, :parts)
242
+ #
243
+ # You can pass a +options+ hash can include the following keys:
244
+ # [<tt>:table_name</tt>]
245
+ # Sets the table name overriding the default
246
+ # [<tt>:column_options</tt>]
247
+ # Any extra options you want appended to the columns definition.
248
+ # [<tt>:options</tt>]
249
+ # Any extra options you want appended to the table definition.
250
+ # [<tt>:temporary</tt>]
251
+ # Make a temporary table.
252
+ # [<tt>:force</tt>]
253
+ # Set to true to drop the table before creating it.
254
+ # Defaults to false.
255
+ #
256
+ # Note that +create_join_table+ does not create any indices by default; you can use
257
+ # its block form to do so yourself:
258
+ #
259
+ # create_join_table :products, :categories do |t|
260
+ # t.index :product_id
261
+ # t.index :category_id
262
+ # end
263
+ #
264
+ # ====== Add a backend specific option to the generated SQL (MySQL)
265
+ #
266
+ # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
267
+ #
268
+ # generates:
269
+ #
270
+ # CREATE TABLE assemblies_parts (
271
+ # assembly_id int NOT NULL,
272
+ # part_id int NOT NULL,
273
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
274
+ #
275
+ def create_join_table(table_1, table_2, options = {})
276
+ join_table_name = find_join_table_name(table_1, table_2, options)
277
+
278
+ column_options = options.delete(:column_options) || {}
279
+ column_options.reverse_merge!(null: false)
280
+
281
+ t1_column, t2_column = [table_1, table_2].map{ |t| t.to_s.singularize.foreign_key }
282
+
283
+ create_table(join_table_name, options.merge!(id: false)) do |td|
284
+ td.integer t1_column, column_options
285
+ td.integer t2_column, column_options
286
+ yield td if block_given?
287
+ end
288
+ end
289
+
290
+ # Drops the join table specified by the given arguments.
291
+ # See +create_join_table+ for details.
292
+ #
293
+ # Although this command ignores the block if one is given, it can be helpful
294
+ # to provide one in a migration's +change+ method so it can be reverted.
295
+ # In that case, the block will be used by create_join_table.
296
+ def drop_join_table(table_1, table_2, options = {})
297
+ join_table_name = find_join_table_name(table_1, table_2, options)
298
+ drop_table(join_table_name)
171
299
  end
172
300
 
173
301
  # A block for changing columns in +table+.
174
302
  #
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
303
+ # # change_table() yields a Table instance
304
+ # change_table(:suppliers) do |t|
305
+ # t.column :name, :string, limit: 60
306
+ # # Other column alterations here
307
+ # end
181
308
  #
182
309
  # The +options+ hash can include the following keys:
183
310
  # [<tt>:bulk</tt>]
184
311
  # 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 ...
312
+ #
313
+ # ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ...
186
314
  #
187
315
  # Defaults to false.
188
316
  #
189
- # ===== Examples
190
317
  # ====== Add a column
191
- # change_table(:suppliers) do |t|
192
- # t.column :name, :string, :limit => 60
193
- # end
318
+ #
319
+ # change_table(:suppliers) do |t|
320
+ # t.column :name, :string, limit: 60
321
+ # end
194
322
  #
195
323
  # ====== Add 2 integer columns
196
- # change_table(:suppliers) do |t|
197
- # t.integer :width, :height, :null => false, :default => 0
198
- # end
324
+ #
325
+ # change_table(:suppliers) do |t|
326
+ # t.integer :width, :height, null: false, default: 0
327
+ # end
199
328
  #
200
329
  # ====== Add created_at/updated_at columns
201
- # change_table(:suppliers) do |t|
202
- # t.timestamps
203
- # end
330
+ #
331
+ # change_table(:suppliers) do |t|
332
+ # t.timestamps
333
+ # end
204
334
  #
205
335
  # ====== Add a foreign key column
206
- # change_table(:suppliers) do |t|
207
- # t.references :company
208
- # end
209
336
  #
210
- # Creates a <tt>company_id(integer)</tt> column
337
+ # change_table(:suppliers) do |t|
338
+ # t.references :company
339
+ # end
340
+ #
341
+ # Creates a <tt>company_id(integer)</tt> column.
211
342
  #
212
343
  # ====== Add a polymorphic foreign key column
344
+ #
213
345
  # change_table(:suppliers) do |t|
214
- # t.belongs_to :company, :polymorphic => true
346
+ # t.belongs_to :company, polymorphic: true
215
347
  # end
216
348
  #
217
- # Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns
349
+ # Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns.
218
350
  #
219
351
  # ====== Remove a column
352
+ #
220
353
  # change_table(:suppliers) do |t|
221
354
  # t.remove :company
222
355
  # end
223
356
  #
224
357
  # ====== Remove several columns
358
+ #
225
359
  # change_table(:suppliers) do |t|
226
360
  # t.remove :company_id
227
361
  # t.remove :width, :height
228
362
  # end
229
363
  #
230
364
  # ====== Remove an index
365
+ #
231
366
  # change_table(:suppliers) do |t|
232
367
  # t.remove_index :company_id
233
368
  # end
234
369
  #
235
- # See also Table for details on
236
- # all of the various column transformation
370
+ # See also Table for details on all of the various column transformation.
237
371
  def change_table(table_name, options = {})
238
372
  if supports_bulk_alter? && options[:bulk]
239
373
  recorder = ActiveRecord::Migration::CommandRecorder.new(self)
240
- yield Table.new(table_name, recorder)
374
+ yield update_table_definition(table_name, recorder)
241
375
  bulk_change_table(table_name, recorder.commands)
242
376
  else
243
- yield Table.new(table_name, self)
377
+ yield update_table_definition(table_name, self)
244
378
  end
245
379
  end
246
380
 
247
381
  # Renames a table.
248
- # ===== Example
249
- # rename_table('octopuses', 'octopi')
382
+ #
383
+ # rename_table('octopuses', 'octopi')
384
+ #
250
385
  def rename_table(table_name, new_name)
251
386
  raise NotImplementedError, "rename_table is not implemented"
252
387
  end
253
388
 
254
389
  # Drops a table from the database.
390
+ #
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.
397
+ # In that case, +options+ and the block will be used by create_table.
255
398
  def drop_table(table_name, options = {})
256
399
  execute "DROP TABLE #{quote_table_name(table_name)}"
257
400
  end
@@ -259,49 +402,80 @@ module ActiveRecord
259
402
  # Adds a new column to the named table.
260
403
  # See TableDefinition#column for details of the options you can use.
261
404
  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)
265
- end
266
-
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
276
- end
405
+ at = create_alter_table table_name
406
+ at.add_column(column_name, type, options)
407
+ execute schema_creation.accept at
408
+ end
277
409
 
278
- columns_for_remove(table_name, *column_names).each do |column_name|
279
- execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{column_name}"
410
+ # Removes the given columns from the table definition.
411
+ #
412
+ # remove_columns(:suppliers, :qualification, :experience)
413
+ #
414
+ def remove_columns(table_name, *column_names)
415
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.empty?
416
+ column_names.each do |column_name|
417
+ remove_column(table_name, column_name)
280
418
  end
281
419
  end
282
- alias :remove_columns :remove_column
420
+
421
+ # Removes the column from the table definition.
422
+ #
423
+ # remove_column(:suppliers, :qualification)
424
+ #
425
+ # The +type+ and +options+ parameters will be ignored if present. It can be helpful
426
+ # to provide these in a migration's +change+ method so it can be reverted.
427
+ # In that case, +type+ and +options+ will be used by add_column.
428
+ def remove_column(table_name, column_name, type = nil, options = {})
429
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
430
+ end
283
431
 
284
432
  # Changes the column's definition according to the new options.
285
433
  # 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)
434
+ #
435
+ # change_column(:suppliers, :name, :string, limit: 80)
436
+ # change_column(:accounts, :description, :text)
437
+ #
289
438
  def change_column(table_name, column_name, type, options = {})
290
439
  raise NotImplementedError, "change_column is not implemented"
291
440
  end
292
441
 
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)
442
+ # Sets a new default value for a column:
443
+ #
444
+ # change_column_default(:suppliers, :qualification, 'new')
445
+ # change_column_default(:accounts, :authorized, 1)
446
+ #
447
+ # Setting the default to +nil+ effectively drops the default:
448
+ #
449
+ # change_column_default(:users, :email, nil)
450
+ #
298
451
  def change_column_default(table_name, column_name, default)
299
452
  raise NotImplementedError, "change_column_default is not implemented"
300
453
  end
301
454
 
455
+ # Sets or removes a +NOT NULL+ constraint on a column. The +null+ flag
456
+ # indicates whether the value can be +NULL+. For example
457
+ #
458
+ # change_column_null(:users, :nickname, false)
459
+ #
460
+ # says nicknames cannot be +NULL+ (adds the constraint), whereas
461
+ #
462
+ # change_column_null(:users, :nickname, true)
463
+ #
464
+ # allows them to be +NULL+ (drops the constraint).
465
+ #
466
+ # The method accepts an optional fourth argument to replace existing
467
+ # +NULL+s with some other value. Use that one when enabling the
468
+ # constraint if needed, since otherwise those rows would not be valid.
469
+ #
470
+ # Please note the fourth argument does not set a column's default.
471
+ def change_column_null(table_name, column_name, null, default = nil)
472
+ raise NotImplementedError, "change_column_null is not implemented"
473
+ end
474
+
302
475
  # Renames a column.
303
- # ===== Example
304
- # rename_column(:suppliers, :description, :name)
476
+ #
477
+ # rename_column(:suppliers, :description, :name)
478
+ #
305
479
  def rename_column(table_name, column_name, new_column_name)
306
480
  raise NotImplementedError, "rename_column is not implemented"
307
481
  end
@@ -312,56 +486,109 @@ module ActiveRecord
312
486
  # The index will be named after the table and the column name(s), unless
313
487
  # you pass <tt>:name</tt> as an option.
314
488
  #
315
- # ===== Examples
316
- #
317
489
  # ====== Creating a simple index
318
- # add_index(:suppliers, :name)
319
- # generates
320
- # CREATE INDEX suppliers_name_index ON suppliers(name)
490
+ #
491
+ # add_index(:suppliers, :name)
492
+ #
493
+ # generates:
494
+ #
495
+ # CREATE INDEX suppliers_name_index ON suppliers(name)
321
496
  #
322
497
  # ====== 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)
498
+ #
499
+ # add_index(:accounts, [:branch_id, :party_id], unique: true)
500
+ #
501
+ # generates:
502
+ #
503
+ # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
326
504
  #
327
505
  # ====== Creating a named index
328
- # add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
329
- # generates
506
+ #
507
+ # add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
508
+ #
509
+ # generates:
510
+ #
330
511
  # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
331
512
  #
332
513
  # ====== 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
514
  #
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))
515
+ # add_index(:accounts, :name, name: 'by_name', length: 10)
516
+ #
517
+ # generates:
518
+ #
519
+ # CREATE INDEX by_name ON accounts(name(10))
520
+ #
521
+ # add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
522
+ #
523
+ # generates:
524
+ #
525
+ # CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
340
526
  #
341
- # Note: SQLite doesn't support index length
527
+ # Note: SQLite doesn't support index length.
342
528
  #
343
529
  # ====== 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
530
  #
348
- # Note: mysql doesn't yet support index order (it accepts the syntax but ignores it)
531
+ # add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
532
+ #
533
+ # generates:
534
+ #
535
+ # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
536
+ #
537
+ # Note: MySQL doesn't yet support index order (it accepts the syntax but ignores it).
538
+ #
539
+ # ====== Creating a partial index
540
+ #
541
+ # add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
542
+ #
543
+ # generates:
544
+ #
545
+ # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
349
546
  #
547
+ # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
548
+ #
549
+ # ====== Creating an index with a specific method
550
+ #
551
+ # add_index(:developers, :name, using: 'btree')
552
+ #
553
+ # generates:
554
+ #
555
+ # CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL
556
+ # CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL
557
+ #
558
+ # Note: only supported by PostgreSQL and MySQL
559
+ #
560
+ # ====== Creating an index with a specific type
561
+ #
562
+ # add_index(:developers, :name, type: :fulltext)
563
+ #
564
+ # generates:
565
+ #
566
+ # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
567
+ #
568
+ # Note: only supported by MySQL. Supported: <tt>:fulltext</tt> and <tt>:spatial</tt> on MyISAM tables.
350
569
  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})"
570
+ index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
571
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
353
572
  end
354
573
 
355
- # Remove the given index from the table.
574
+ # Removes the given index from the table.
575
+ #
576
+ # Removes the +index_accounts_on_column+ in the +accounts+ table.
356
577
  #
357
- # Remove the index_accounts_on_column in the accounts table.
358
578
  # 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
579
+ #
580
+ # Removes the index named +index_accounts_on_branch_id+ in the +accounts+ table.
581
+ #
582
+ # remove_index :accounts, column: :branch_id
583
+ #
584
+ # Removes the index named +index_accounts_on_branch_id_and_party_id+ in the +accounts+ table.
585
+ #
586
+ # remove_index :accounts, column: [:branch_id, :party_id]
587
+ #
588
+ # Removes the index named +by_branch_party+ in the +accounts+ table.
589
+ #
590
+ # remove_index :accounts, name: :by_branch_party
591
+ #
365
592
  def remove_index(table_name, options = {})
366
593
  remove_index!(table_name, index_name_for_remove(table_name, options))
367
594
  end
@@ -370,22 +597,26 @@ module ActiveRecord
370
597
  execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
371
598
  end
372
599
 
373
- # Rename an index.
600
+ # Renames an index.
601
+ #
602
+ # Rename the +index_people_on_last_name+ index to +index_users_on_last_name+:
374
603
  #
375
- # Rename the index_people_on_last_name index to index_users_on_last_name
376
604
  # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
605
+ #
377
606
  def rename_index(table_name, old_name, new_name)
607
+ validate_index_length!(table_name, new_name)
608
+
378
609
  # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
379
610
  old_index_def = indexes(table_name).detect { |i| i.name == old_name }
380
611
  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)
612
+ add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique)
613
+ remove_index(table_name, name: old_name)
383
614
  end
384
615
 
385
616
  def index_name(table_name, options) #:nodoc:
386
- if Hash === options # legacy support
617
+ if Hash === options
387
618
  if options[:column]
388
- "index_#{table_name}_on_#{Array.wrap(options[:column]) * '_and_'}"
619
+ "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
389
620
  elsif options[:name]
390
621
  options[:name]
391
622
  else
@@ -396,7 +627,7 @@ module ActiveRecord
396
627
  end
397
628
  end
398
629
 
399
- # Verify the existence of an index with a given name.
630
+ # Verifies the existence of an index with a given name.
400
631
  #
401
632
  # The default argument is returned if the underlying implementation does not define the indexes method,
402
633
  # as there's no way to determine the correct answer in that case.
@@ -406,52 +637,213 @@ module ActiveRecord
406
637
  indexes(table_name).detect { |i| i.name == index_name }
407
638
  end
408
639
 
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
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.
643
+ # <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
644
+ #
645
+ # The +options+ hash can include the following keys:
646
+ # [<tt>:type</tt>]
647
+ # The reference column type. Defaults to +:integer+.
648
+ # [<tt>:index</tt>]
649
+ # Add an appropriate index. Defaults to false.
650
+ # [<tt>:foreign_key</tt>]
651
+ # Add an appropriate foreign key. Defaults to false.
652
+ # [<tt>:polymorphic</tt>]
653
+ # Wether an additional +_type+ column should be added. Defaults to false.
654
+ #
655
+ # ====== Create a user_id integer column
656
+ #
657
+ # add_reference(:products, :user)
658
+ #
659
+ # ====== Create a user_id string column
660
+ #
661
+ # add_reference(:products, :user, type: :string)
662
+ #
663
+ # ====== Create supplier_id, supplier_type columns and appropriate index
664
+ #
665
+ # add_reference(:products, :supplier, polymorphic: true, index: true)
666
+ #
667
+ def add_reference(table_name, ref_name, options = {})
668
+ polymorphic = options.delete(:polymorphic)
669
+ index_options = options.delete(:index)
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)
678
+ add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
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
412
684
  end
685
+ alias :add_belongs_to :add_reference
413
686
 
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")
687
+ # Removes the reference(s). Also removes a +type+ column if one exists.
688
+ # <tt>remove_reference</tt>, <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
689
+ #
690
+ # ====== Remove the reference
691
+ #
692
+ # remove_reference(:products, :user, index: true)
693
+ #
694
+ # ====== Remove polymorphic reference
695
+ #
696
+ # remove_reference(:products, :supplier, polymorphic: true)
697
+ #
698
+ # ====== Remove the reference with a foreign key
699
+ #
700
+ # remove_reference(:products, :user, index: true, foreign_key: true)
701
+ #
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
+
708
+ remove_column(table_name, "#{ref_name}_id")
709
+ remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
418
710
  end
711
+ alias :remove_belongs_to :remove_reference
419
712
 
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
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
424
718
 
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}"
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)
431
765
 
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
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
435
775
 
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"
776
+ execute schema_creation.accept(at)
777
+ end
438
778
 
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)
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]}'"
442
809
  end
443
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
+
825
+ def dump_schema_information #:nodoc:
826
+ sm_table = ActiveRecord::Migrator.schema_migrations_table_name
827
+
828
+ ActiveRecord::SchemaMigration.order('version').map { |sm|
829
+ "INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');"
830
+ }.join "\n\n"
831
+ end
832
+
833
+ # Should not be called normally, but this operation is non-destructive.
834
+ # The migrations module handles this automatically.
835
+ def initialize_schema_migrations_table
836
+ ActiveRecord::SchemaMigration.create_table
444
837
  end
445
838
 
446
839
  def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
447
- migrations_paths = Array.wrap(migrations_paths)
840
+ migrations_paths = Array(migrations_paths)
448
841
  version = version.to_i
449
842
  sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
450
843
 
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
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
455
847
  end
456
848
 
457
849
  unless migrated.include?(version)
@@ -483,7 +875,7 @@ module ActiveRecord
483
875
  column_type_sql << "(#{precision})"
484
876
  end
485
877
  elsif scale
486
- raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified"
878
+ raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
487
879
  end
488
880
 
489
881
  elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
@@ -492,48 +884,85 @@ module ActiveRecord
492
884
 
493
885
  column_type_sql
494
886
  else
495
- type
496
- end
497
- end
498
-
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"
887
+ type.to_s
504
888
  end
505
889
  end
506
890
 
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.
891
+ # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
892
+ # PostgreSQL, MySQL, and Oracle overrides this for custom DISTINCT syntax - they
893
+ # require the order columns appear in the SELECT.
894
+ #
895
+ # columns_for_distinct("posts.id", ["posts.created_at desc"])
509
896
  #
510
- # distinct("posts.id", "posts.created_at desc")
511
- def distinct(columns, order_by)
512
- "DISTINCT #{columns}"
897
+ def columns_for_distinct(columns, orders) # :nodoc:
898
+ columns
513
899
  end
514
900
 
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
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.
904
+ #
905
+ # add_timestamps(:suppliers, null: false)
906
+ #
907
+ def add_timestamps(table_name, options = {})
908
+ emit_warning_if_null_unspecified(:add_timestamps, options)
909
+ add_column table_name, :created_at, :datetime, options
910
+ add_column table_name, :updated_at, :datetime, options
521
911
  end
522
912
 
523
- # Removes the timestamp columns (created_at and updated_at) from the table definition.
524
- # ===== Examples
913
+ # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
914
+ #
525
915
  # remove_timestamps(:suppliers)
526
- def remove_timestamps(table_name)
916
+ #
917
+ def remove_timestamps(table_name, options = {})
527
918
  remove_column table_name, :updated_at
528
919
  remove_column table_name, :created_at
529
920
  end
530
921
 
922
+ def update_table_definition(table_name, base) #:nodoc:
923
+ Table.new(table_name, base)
924
+ end
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
+
531
960
  protected
532
961
  def add_index_sort_order(option_strings, column_names, options = {})
533
962
  if options.is_a?(Hash) && order = options[:order]
534
963
  case order
535
964
  when Hash
536
- column_names.each {|name| option_strings[name] += " #{order[name].to_s.upcase}" if order.has_key?(name)}
965
+ column_names.each {|name| option_strings[name] += " #{order[name].upcase}" if order.has_key?(name)}
537
966
  when String
538
967
  column_names.each {|name| option_strings[name] += " #{order.upcase}"}
539
968
  end
@@ -542,7 +971,7 @@ module ActiveRecord
542
971
  return option_strings
543
972
  end
544
973
 
545
- # Overridden by the mysql adapter for supporting index lengths
974
+ # Overridden by the MySQL adapter for supporting index lengths
546
975
  def quoted_columns_for_index(column_names, options = {})
547
976
  option_strings = Hash[column_names.map {|name| [name, '']}]
548
977
 
@@ -558,48 +987,67 @@ module ActiveRecord
558
987
  options.include?(:default) && !(options[:null] == false && options[:default].nil?)
559
988
  end
560
989
 
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)
564
-
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)
568
- else
569
- index_type = options
570
- end
571
-
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"
574
- end
575
- if index_name_exists?(table_name, index_name, false)
576
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
577
- end
578
- index_columns = quoted_columns_for_index(column_names, options).join(", ")
579
-
580
- [index_name, index_type, index_columns]
581
- end
582
-
583
990
  def index_name_for_remove(table_name, options = {})
584
991
  index_name = index_name(table_name, options)
585
992
 
586
993
  unless index_name_exists?(table_name, index_name, true)
994
+ if options.is_a?(Hash) && options.has_key?(:name)
995
+ options_without_column = options.dup
996
+ options_without_column.delete :column
997
+ index_name_without_column = index_name(table_name, options_without_column)
998
+
999
+ return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
1000
+ end
1001
+
587
1002
  raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
588
1003
  end
589
1004
 
590
1005
  index_name
591
1006
  end
592
1007
 
593
- def columns_for_remove(table_name, *column_names)
594
- column_names = column_names.flatten
1008
+ def rename_table_indexes(table_name, new_name)
1009
+ indexes(new_name).each do |index|
1010
+ generated_index_name = index_name(table_name, column: index.columns)
1011
+ if generated_index_name == index.name
1012
+ rename_index new_name, generated_index_name, index_name(new_name, column: index.columns)
1013
+ end
1014
+ end
1015
+ end
595
1016
 
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) }
1017
+ def rename_column_indexes(table_name, column_name, new_column_name)
1018
+ column_name, new_column_name = column_name.to_s, new_column_name.to_s
1019
+ indexes(table_name).each do |index|
1020
+ next unless index.columns.include?(new_column_name)
1021
+ old_columns = index.columns.dup
1022
+ old_columns[old_columns.index(new_column_name)] = column_name
1023
+ generated_index_name = index_name(table_name, column: old_columns)
1024
+ if generated_index_name == index.name
1025
+ rename_index table_name, generated_index_name, index_name(table_name, column: index.columns)
1026
+ end
1027
+ end
598
1028
  end
599
1029
 
600
1030
  private
601
- def table_definition
602
- TableDefinition.new(self)
1031
+ def create_table_definition(name, temporary, options, as = nil)
1032
+ TableDefinition.new native_database_types, name, temporary, options, as
1033
+ end
1034
+
1035
+ def create_alter_table(name)
1036
+ AlterTable.new create_table_definition(name, false, {})
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
603
1051
  end
604
1052
  end
605
1053
  end