activerecord 3.1.10 → 4.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  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 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  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 -102
  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 +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  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 +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  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 +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  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 +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  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 +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  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 +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  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 +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  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 +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  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 +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  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 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  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 +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,10 +1,14 @@
1
- require 'active_support/core_ext/array/wrap'
1
+ require 'active_record/migration/join_table'
2
+ require 'active_support/core_ext/string/access'
3
+ require 'digest'
2
4
 
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters # :nodoc:
5
7
  module SchemaStatements
6
- # Returns a Hash of mappings from the abstract data types to the native
7
- # database types. See TableDefinition#column for details on the recognized
8
+ include ActiveRecord::Migration::JoinTable
9
+
10
+ # Returns a hash of mappings from the abstract data types to the native
11
+ # database types. See TableDefinition#column for details on the recognized
8
12
  # abstract data types.
9
13
  def native_database_types
10
14
  {}
@@ -12,15 +16,27 @@ module ActiveRecord
12
16
 
13
17
  # Truncates a table alias according to the limits of the current adapter.
14
18
  def table_alias_for(table_name)
15
- 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
16
26
  end
17
27
 
18
- # def tables(name = nil) end
28
+ # Checks to see if the data source +name+ exists on the database.
29
+ #
30
+ # data_source_exists?(:ebooks)
31
+ #
32
+ def data_source_exists?(name)
33
+ data_sources.include?(name.to_s)
34
+ end
19
35
 
20
36
  # Checks to see if the table +table_name+ exists on the database.
21
37
  #
22
- # === Example
23
38
  # table_exists?(:developers)
39
+ #
24
40
  def table_exists?(table_name)
25
41
  tables.include?(table_name.to_s)
26
42
  end
@@ -30,79 +46,89 @@ module ActiveRecord
30
46
 
31
47
  # Checks to see if an index exists on a table for a given index definition.
32
48
  #
33
- # === Examples
34
- # # Check an index exists
35
- # index_exists?(:suppliers, :company_id)
49
+ # # Check an index exists
50
+ # index_exists?(:suppliers, :company_id)
51
+ #
52
+ # # Check an index on multiple columns exists
53
+ # index_exists?(:suppliers, [:company_id, :company_type])
36
54
  #
37
- # # Check an index on multiple columns exists
38
- # index_exists?(:suppliers, [:company_id, :company_type])
55
+ # # Check a unique index exists
56
+ # index_exists?(:suppliers, :company_id, unique: true)
39
57
  #
40
- # # Check a unique index exists
41
- # index_exists?(:suppliers, :company_id, :unique => true)
58
+ # # Check an index with a custom name exists
59
+ # index_exists?(:suppliers, :company_id, name: "idx_company_id")
42
60
  #
43
- # # Check an index with a custom name exists
44
- # index_exists?(:suppliers, :company_id, :name => "idx_company_id"
45
61
  def index_exists?(table_name, column_name, options = {})
46
- column_names = Array.wrap(column_name)
47
- index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
48
- if options[:unique]
49
- indexes(table_name).any?{ |i| i.unique && i.name == index_name }
50
- else
51
- indexes(table_name).any?{ |i| i.name == index_name }
52
- end
62
+ column_names = Array(column_name).map(&:to_s)
63
+ index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, column: column_names)
64
+ checks = []
65
+ checks << lambda { |i| i.name == index_name }
66
+ checks << lambda { |i| i.columns == column_names }
67
+ checks << lambda { |i| i.unique } if options[:unique]
68
+
69
+ indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
53
70
  end
54
71
 
55
72
  # Returns an array of Column objects for the table specified by +table_name+.
56
73
  # See the concrete implementation for details on the expected parameter values.
57
- def columns(table_name, name = nil) end
74
+ def columns(table_name) end
58
75
 
59
76
  # Checks to see if a column exists in a given table.
60
77
  #
61
- # === Examples
62
- # # Check a column exists
63
- # 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)
64
83
  #
65
- # # Check a column exists of a particular type
66
- # 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)
67
89
  #
68
- # # Check a column exists with a specific definition
69
- # column_exists?(:suppliers, :name, :string, :limit => 100)
70
90
  def column_exists?(table_name, column_name, type = nil, options = {})
71
- columns(table_name).any?{ |c| c.name == column_name.to_s &&
72
- (!type || c.type == type) &&
73
- (!options[:limit] || c.limit == options[:limit]) &&
74
- (!options[:precision] || c.precision == options[:precision]) &&
75
- (!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]) }
76
99
  end
77
100
 
78
101
  # Creates a new table with the name +table_name+. +table_name+ may either
79
102
  # be a String or a Symbol.
80
103
  #
81
- # There are two ways to work with +create_table+. You can use the block
104
+ # There are two ways to work with +create_table+. You can use the block
82
105
  # form or the regular form, like this:
83
106
  #
84
107
  # === Block form
85
- # # create_table() passes a TableDefinition object to the block.
86
- # # This form will not only create the table, but also columns for the
87
- # # table.
88
108
  #
89
- # create_table(:suppliers) do |t|
90
- # t.column :name, :string, :limit => 60
91
- # # Other fields here
92
- # 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
93
117
  #
94
118
  # === Block form, with shorthand
95
- # # You can also use the column types as method calls, rather than calling the column method.
96
- # create_table(:suppliers) do |t|
97
- # t.string :name, :limit => 60
98
- # # Other fields here
99
- # 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
100
125
  #
101
126
  # === Regular form
102
- # # Creates a table called 'suppliers' with no columns.
103
- # create_table(:suppliers)
104
- # # Add a column to 'suppliers'.
105
- # 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})
106
132
  #
107
133
  # The +options+ hash can include the following keys:
108
134
  # [<tt>:id</tt>]
@@ -112,9 +138,9 @@ module ActiveRecord
112
138
  # The name of the primary key, if one is to be added automatically.
113
139
  # Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
114
140
  #
115
- # Also note that this just sets the primary key in the table. You additionally
116
- # need to configure the primary key in the model via the +set_primary_key+ macro.
117
- # 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.
118
144
  #
119
145
  # [<tt>:options</tt>]
120
146
  # Any extra options you want appended to the table definition.
@@ -122,41 +148,70 @@ module ActiveRecord
122
148
  # Make a temporary table.
123
149
  # [<tt>:force</tt>]
124
150
  # Set to true to drop the table before creating it.
151
+ # Set to +:cascade+ to drop dependent objects as well.
125
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.
126
156
  #
127
- # ===== Examples
128
157
  # ====== Add a backend specific option to the generated SQL (MySQL)
129
- # create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
158
+ #
159
+ # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
160
+ #
130
161
  # generates:
131
- # CREATE TABLE suppliers (
132
- # id int(11) DEFAULT NULL auto_increment PRIMARY KEY
133
- # ) 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
134
166
  #
135
167
  # ====== Rename the primary key column
136
- # create_table(:objects, :primary_key => 'guid') do |t|
137
- # t.column :name, :string, :limit => 80
138
- # end
168
+ #
169
+ # create_table(:objects, primary_key: 'guid') do |t|
170
+ # t.column :name, :string, limit: 80
171
+ # end
172
+ #
139
173
  # generates:
140
- # CREATE TABLE objects (
141
- # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
142
- # name varchar(80)
143
- # )
174
+ #
175
+ # CREATE TABLE objects (
176
+ # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
177
+ # name varchar(80)
178
+ # )
144
179
  #
145
180
  # ====== Do not add a primary key column
146
- # create_table(:categories_suppliers, :id => false) do |t|
147
- # t.column :category_id, :integer
148
- # t.column :supplier_id, :integer
149
- # 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
+ #
150
187
  # generates:
151
- # CREATE TABLE categories_suppliers (
152
- # category_id int,
153
- # supplier_id int
154
- # )
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
155
203
  #
156
204
  # See also TableDefinition#column for details on how to create columns.
157
205
  def create_table(table_name, options = {})
158
- td = table_definition
159
- 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
160
215
 
161
216
  yield td if block_given?
162
217
 
@@ -164,95 +219,182 @@ module ActiveRecord
164
219
  drop_table(table_name, options)
165
220
  end
166
221
 
167
- create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
168
- create_sql << "#{quote_table_name(table_name)} ("
169
- create_sql << td.to_sql
170
- create_sql << ") #{options[:options]}"
171
- 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)
172
299
  end
173
300
 
174
301
  # A block for changing columns in +table+.
175
302
  #
176
- # === Example
177
- # # change_table() yields a Table instance
178
- # change_table(:suppliers) do |t|
179
- # t.column :name, :string, :limit => 60
180
- # # Other column alterations here
181
- # 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
182
308
  #
183
309
  # The +options+ hash can include the following keys:
184
310
  # [<tt>:bulk</tt>]
185
311
  # Set this to true to make this a bulk alter query, such as
186
- # 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 ...
187
314
  #
188
315
  # Defaults to false.
189
316
  #
190
- # ===== Examples
191
317
  # ====== Add a column
192
- # change_table(:suppliers) do |t|
193
- # t.column :name, :string, :limit => 60
194
- # end
318
+ #
319
+ # change_table(:suppliers) do |t|
320
+ # t.column :name, :string, limit: 60
321
+ # end
195
322
  #
196
323
  # ====== Add 2 integer columns
197
- # change_table(:suppliers) do |t|
198
- # t.integer :width, :height, :null => false, :default => 0
199
- # end
324
+ #
325
+ # change_table(:suppliers) do |t|
326
+ # t.integer :width, :height, null: false, default: 0
327
+ # end
200
328
  #
201
329
  # ====== Add created_at/updated_at columns
202
- # change_table(:suppliers) do |t|
203
- # t.timestamps
204
- # end
330
+ #
331
+ # change_table(:suppliers) do |t|
332
+ # t.timestamps
333
+ # end
205
334
  #
206
335
  # ====== Add a foreign key column
207
- # change_table(:suppliers) do |t|
208
- # t.references :company
209
- # end
210
336
  #
211
- # 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.
212
342
  #
213
343
  # ====== Add a polymorphic foreign key column
344
+ #
214
345
  # change_table(:suppliers) do |t|
215
- # t.belongs_to :company, :polymorphic => true
346
+ # t.belongs_to :company, polymorphic: true
216
347
  # end
217
348
  #
218
- # 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.
219
350
  #
220
351
  # ====== Remove a column
352
+ #
221
353
  # change_table(:suppliers) do |t|
222
354
  # t.remove :company
223
355
  # end
224
356
  #
225
357
  # ====== Remove several columns
358
+ #
226
359
  # change_table(:suppliers) do |t|
227
360
  # t.remove :company_id
228
361
  # t.remove :width, :height
229
362
  # end
230
363
  #
231
364
  # ====== Remove an index
365
+ #
232
366
  # change_table(:suppliers) do |t|
233
367
  # t.remove_index :company_id
234
368
  # end
235
369
  #
236
- # See also Table for details on
237
- # all of the various column transformation
370
+ # See also Table for details on all of the various column transformation.
238
371
  def change_table(table_name, options = {})
239
372
  if supports_bulk_alter? && options[:bulk]
240
373
  recorder = ActiveRecord::Migration::CommandRecorder.new(self)
241
- yield Table.new(table_name, recorder)
374
+ yield update_table_definition(table_name, recorder)
242
375
  bulk_change_table(table_name, recorder.commands)
243
376
  else
244
- yield Table.new(table_name, self)
377
+ yield update_table_definition(table_name, self)
245
378
  end
246
379
  end
247
380
 
248
381
  # Renames a table.
249
- # ===== Example
250
- # rename_table('octopuses', 'octopi')
382
+ #
383
+ # rename_table('octopuses', 'octopi')
384
+ #
251
385
  def rename_table(table_name, new_name)
252
386
  raise NotImplementedError, "rename_table is not implemented"
253
387
  end
254
388
 
255
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.
256
398
  def drop_table(table_name, options = {})
257
399
  execute "DROP TABLE #{quote_table_name(table_name)}"
258
400
  end
@@ -260,100 +402,193 @@ module ActiveRecord
260
402
  # Adds a new column to the named table.
261
403
  # See TableDefinition#column for details of the options you can use.
262
404
  def add_column(table_name, column_name, type, options = {})
263
- 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])}"
264
- add_column_options!(add_column_sql, options)
265
- execute(add_column_sql)
405
+ at = create_alter_table table_name
406
+ at.add_column(column_name, type, options)
407
+ execute schema_creation.accept at
408
+ end
409
+
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)
418
+ end
266
419
  end
267
420
 
268
- # Removes the column(s) from the table definition.
269
- # ===== Examples
270
- # remove_column(:suppliers, :qualification)
271
- # remove_columns(:suppliers, :qualification, :experience)
272
- def remove_column(table_name, *column_names)
273
- columns_for_remove(table_name, *column_names).each {|column_name| execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{column_name}" }
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)}"
274
430
  end
275
- alias :remove_columns :remove_column
276
431
 
277
432
  # Changes the column's definition according to the new options.
278
433
  # See TableDefinition#column for details of the options you can use.
279
- # ===== Examples
280
- # change_column(:suppliers, :name, :string, :limit => 80)
281
- # change_column(:accounts, :description, :text)
434
+ #
435
+ # change_column(:suppliers, :name, :string, limit: 80)
436
+ # change_column(:accounts, :description, :text)
437
+ #
282
438
  def change_column(table_name, column_name, type, options = {})
283
439
  raise NotImplementedError, "change_column is not implemented"
284
440
  end
285
441
 
286
- # Sets a new default value for a column.
287
- # ===== Examples
288
- # change_column_default(:suppliers, :qualification, 'new')
289
- # change_column_default(:accounts, :authorized, 1)
290
- # 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
+ #
291
451
  def change_column_default(table_name, column_name, default)
292
452
  raise NotImplementedError, "change_column_default is not implemented"
293
453
  end
294
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
+
295
475
  # Renames a column.
296
- # ===== Example
297
- # rename_column(:suppliers, :description, :name)
476
+ #
477
+ # rename_column(:suppliers, :description, :name)
478
+ #
298
479
  def rename_column(table_name, column_name, new_column_name)
299
480
  raise NotImplementedError, "rename_column is not implemented"
300
481
  end
301
482
 
302
- # Adds a new index to the table. +column_name+ can be a single Symbol, or
483
+ # Adds a new index to the table. +column_name+ can be a single Symbol, or
303
484
  # an Array of Symbols.
304
485
  #
305
- # The index will be named after the table and the first column name,
306
- # unless you pass <tt>:name</tt> as an option.
486
+ # The index will be named after the table and the column name(s), unless
487
+ # you pass <tt>:name</tt> as an option.
307
488
  #
308
- # When creating an index on multiple columns, the first column is used as a name
309
- # for the index. For example, when you specify an index on two columns
310
- # [<tt>:first</tt>, <tt>:last</tt>], the DBMS creates an index for both columns as well as an
311
- # index for the first column <tt>:first</tt>. Using just the first name for this index
312
- # makes sense, because you will never have to create a singular index with this
313
- # name.
489
+ # ====== Creating a simple index
314
490
  #
315
- # ===== Examples
491
+ # add_index(:suppliers, :name)
316
492
  #
317
- # ====== Creating a simple index
318
- # add_index(:suppliers, :name)
319
- # generates
320
- # CREATE INDEX suppliers_name_index ON suppliers(name)
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))
526
+ #
527
+ # Note: SQLite doesn't support index length.
528
+ #
529
+ # ====== Creating an index with a sort order (desc or asc, asc is the default)
530
+ #
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
546
+ #
547
+ # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
340
548
  #
341
- # Note: SQLite doesn't support index length
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.
342
569
  def add_index(table_name, column_name, options = {})
343
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
344
- 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}"
345
572
  end
346
573
 
347
- # 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.
348
577
  #
349
- # Remove the index_accounts_on_column in the accounts table.
350
578
  # remove_index :accounts, :column
351
- # Remove the index named index_accounts_on_branch_id in the accounts table.
352
- # remove_index :accounts, :column => :branch_id
353
- # Remove the index named index_accounts_on_branch_id_and_party_id in the accounts table.
354
- # remove_index :accounts, :column => [:branch_id, :party_id]
355
- # Remove the index named by_branch_party in the accounts table.
356
- # 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
+ #
357
592
  def remove_index(table_name, options = {})
358
593
  remove_index!(table_name, index_name_for_remove(table_name, options))
359
594
  end
@@ -362,22 +597,26 @@ module ActiveRecord
362
597
  execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
363
598
  end
364
599
 
365
- # Rename an index.
600
+ # Renames an index.
601
+ #
602
+ # Rename the +index_people_on_last_name+ index to +index_users_on_last_name+:
366
603
  #
367
- # Rename the index_people_on_last_name index to index_users_on_last_name
368
604
  # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
605
+ #
369
606
  def rename_index(table_name, old_name, new_name)
607
+ validate_index_length!(table_name, new_name)
608
+
370
609
  # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
371
610
  old_index_def = indexes(table_name).detect { |i| i.name == old_name }
372
611
  return unless old_index_def
373
- remove_index(table_name, :name => old_name)
374
- 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)
375
614
  end
376
615
 
377
616
  def index_name(table_name, options) #:nodoc:
378
- if Hash === options # legacy support
617
+ if Hash === options
379
618
  if options[:column]
380
- "index_#{table_name}_on_#{Array.wrap(options[:column]) * '_and_'}"
619
+ "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
381
620
  elsif options[:name]
382
621
  options[:name]
383
622
  else
@@ -388,7 +627,7 @@ module ActiveRecord
388
627
  end
389
628
  end
390
629
 
391
- # Verify the existence of an index with a given name.
630
+ # Verifies the existence of an index with a given name.
392
631
  #
393
632
  # The default argument is returned if the underlying implementation does not define the indexes method,
394
633
  # as there's no way to determine the correct answer in that case.
@@ -398,51 +637,213 @@ module ActiveRecord
398
637
  indexes(table_name).detect { |i| i.name == index_name }
399
638
  end
400
639
 
401
- # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
402
- # entire structure of the database.
403
- 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
404
684
  end
685
+ alias :add_belongs_to :add_reference
405
686
 
406
- def dump_schema_information #:nodoc:
407
- sm_table = ActiveRecord::Migrator.schema_migrations_table_name
408
- migrated = select_values("SELECT version FROM #{sm_table}")
409
- 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]
410
710
  end
711
+ alias :remove_belongs_to :remove_reference
411
712
 
412
- # Should not be called normally, but this operation is non-destructive.
413
- # The migrations module handles this automatically.
414
- def initialize_schema_migrations_table
415
- 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
416
718
 
417
- unless table_exists?(sm_table)
418
- create_table(sm_table, :id => false) do |schema_migrations_table|
419
- schema_migrations_table.column :version, :string, :null => false
420
- end
421
- add_index sm_table, :version, :unique => true,
422
- :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)
765
+
766
+ options = {
767
+ column: options[:column],
768
+ primary_key: options[:primary_key],
769
+ name: foreign_key_name(from_table, options),
770
+ on_delete: options[:on_delete],
771
+ on_update: options[:on_update]
772
+ }
773
+ at = create_alter_table from_table
774
+ at.add_foreign_key to_table, options
775
+
776
+ execute schema_creation.accept(at)
777
+ end
423
778
 
424
- # Backwards-compatibility: if we find schema_info, assume we've
425
- # migrated up to that point:
426
- si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix
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
427
801
 
428
- if table_exists?(si_table)
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 }
429
804
 
430
- old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i
431
- assume_migrated_upto_version(old_version)
432
- drop_table(si_table)
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]}'"
433
809
  end
434
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
435
837
  end
436
838
 
437
839
  def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
438
- migrations_paths = Array.wrap(migrations_paths)
840
+ migrations_paths = Array(migrations_paths)
439
841
  version = version.to_i
440
842
  sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
441
843
 
442
- migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i }
443
- paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" }
444
- versions = Dir[*paths].map do |filename|
445
- 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
446
847
  end
447
848
 
448
849
  unless migrated.include?(version)
@@ -474,7 +875,7 @@ module ActiveRecord
474
875
  column_type_sql << "(#{precision})"
475
876
  end
476
877
  elsif scale
477
- 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"
478
879
  end
479
880
 
480
881
  elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
@@ -483,94 +884,170 @@ module ActiveRecord
483
884
 
484
885
  column_type_sql
485
886
  else
486
- type
887
+ type.to_s
487
888
  end
488
889
  end
489
890
 
490
- def add_column_options!(sql, options) #:nodoc:
491
- sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
492
- # must explicitly check for :null to allow change_column to work on migrations
493
- if options[:null] == false
494
- sql << " NOT NULL"
495
- end
496
- end
497
-
498
- # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
499
- # 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"])
500
896
  #
501
- # distinct("posts.id", "posts.created_at desc")
502
- def distinct(columns, order_by)
503
- "DISTINCT #{columns}"
897
+ def columns_for_distinct(columns, orders) # :nodoc:
898
+ columns
504
899
  end
505
900
 
506
- # Adds timestamps (created_at and updated_at) columns to the named table.
507
- # ===== Examples
508
- # add_timestamps(:suppliers)
509
- def add_timestamps(table_name)
510
- add_column table_name, :created_at, :datetime
511
- 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
512
911
  end
513
912
 
514
- # Removes the timestamp columns (created_at and updated_at) from the table definition.
515
- # ===== Examples
913
+ # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
914
+ #
516
915
  # remove_timestamps(:suppliers)
517
- def remove_timestamps(table_name)
916
+ #
917
+ def remove_timestamps(table_name, options = {})
518
918
  remove_column table_name, :updated_at
519
919
  remove_column table_name, :created_at
520
920
  end
521
921
 
522
- protected
523
- # Overridden by the mysql adapter for supporting index lengths
524
- def quoted_columns_for_index(column_names, options = {})
525
- column_names.map {|name| quote_column_name(name) }
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
+ }
526
941
  end
527
942
 
528
- def options_include_default?(options)
529
- options.include?(:default) && !(options[:null] == false && options[:default].nil?)
943
+ using = "USING #{options[:using]}" if options[:using].present?
944
+
945
+ if supports_partial_index?
946
+ index_options = options[:where] ? " WHERE #{options[:where]}" : ""
530
947
  end
531
948
 
532
- def add_index_options(table_name, column_name, options = {})
533
- column_names = Array.wrap(column_name)
534
- index_name = index_name(table_name, :column => column_names)
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(", ")
535
956
 
536
- if Hash === options # legacy support, since this param was a string
537
- index_type = options[:unique] ? "UNIQUE" : ""
538
- index_name = options[:name].to_s if options.key?(:name)
539
- else
540
- index_type = options
541
- end
957
+ [index_name, index_type, index_columns, index_options, algorithm, using]
958
+ end
542
959
 
543
- if index_name.length > index_name_length
544
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
960
+ protected
961
+ def add_index_sort_order(option_strings, column_names, options = {})
962
+ if options.is_a?(Hash) && order = options[:order]
963
+ case order
964
+ when Hash
965
+ column_names.each {|name| option_strings[name] += " #{order[name].upcase}" if order.has_key?(name)}
966
+ when String
967
+ column_names.each {|name| option_strings[name] += " #{order.upcase}"}
968
+ end
545
969
  end
546
- if index_name_exists?(table_name, index_name, false)
547
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
970
+
971
+ return option_strings
972
+ end
973
+
974
+ # Overridden by the MySQL adapter for supporting index lengths
975
+ def quoted_columns_for_index(column_names, options = {})
976
+ option_strings = Hash[column_names.map {|name| [name, '']}]
977
+
978
+ # add index sort order if supported
979
+ if supports_index_sort_order?
980
+ option_strings = add_index_sort_order(option_strings, column_names, options)
548
981
  end
549
- index_columns = quoted_columns_for_index(column_names, options).join(", ")
550
982
 
551
- [index_name, index_type, index_columns]
983
+ column_names.map {|name| quote_column_name(name) + option_strings[name]}
984
+ end
985
+
986
+ def options_include_default?(options)
987
+ options.include?(:default) && !(options[:null] == false && options[:default].nil?)
552
988
  end
553
989
 
554
990
  def index_name_for_remove(table_name, options = {})
555
991
  index_name = index_name(table_name, options)
556
992
 
557
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
+
558
1002
  raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
559
1003
  end
560
1004
 
561
1005
  index_name
562
1006
  end
563
1007
 
564
- def columns_for_remove(table_name, *column_names)
565
- 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
566
1016
 
567
- raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.blank?
568
- 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
569
1028
  end
570
1029
 
571
1030
  private
572
- def table_definition
573
- 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
574
1051
  end
575
1052
  end
576
1053
  end