activerecord 4.1.16 → 4.2.0.beta1

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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +634 -2185
  3. data/README.rdoc +15 -10
  4. data/lib/active_record.rb +2 -1
  5. data/lib/active_record/aggregations.rb +12 -8
  6. data/lib/active_record/associations.rb +58 -33
  7. data/lib/active_record/associations/association.rb +1 -1
  8. data/lib/active_record/associations/association_scope.rb +53 -21
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  10. data/lib/active_record/associations/builder/association.rb +16 -5
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -11
  13. data/lib/active_record/associations/builder/has_one.rb +2 -2
  14. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  15. data/lib/active_record/associations/collection_association.rb +32 -44
  16. data/lib/active_record/associations/collection_proxy.rb +1 -10
  17. data/lib/active_record/associations/has_many_association.rb +60 -14
  18. data/lib/active_record/associations/has_many_through_association.rb +34 -23
  19. data/lib/active_record/associations/has_one_association.rb +0 -1
  20. data/lib/active_record/associations/join_dependency.rb +7 -9
  21. data/lib/active_record/associations/join_dependency/join_association.rb +18 -14
  22. data/lib/active_record/associations/preloader.rb +2 -2
  23. data/lib/active_record/associations/preloader/association.rb +9 -5
  24. data/lib/active_record/associations/preloader/through_association.rb +3 -3
  25. data/lib/active_record/associations/singular_association.rb +16 -1
  26. data/lib/active_record/associations/through_association.rb +6 -22
  27. data/lib/active_record/attribute.rb +131 -0
  28. data/lib/active_record/attribute_assignment.rb +19 -11
  29. data/lib/active_record/attribute_decorators.rb +66 -0
  30. data/lib/active_record/attribute_methods.rb +53 -90
  31. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  32. data/lib/active_record/attribute_methods/dirty.rb +85 -42
  33. data/lib/active_record/attribute_methods/primary_key.rb +6 -8
  34. data/lib/active_record/attribute_methods/read.rb +14 -57
  35. data/lib/active_record/attribute_methods/serialization.rb +12 -146
  36. data/lib/active_record/attribute_methods/time_zone_conversion.rb +32 -40
  37. data/lib/active_record/attribute_methods/write.rb +8 -23
  38. data/lib/active_record/attribute_set.rb +77 -0
  39. data/lib/active_record/attribute_set/builder.rb +32 -0
  40. data/lib/active_record/attributes.rb +122 -0
  41. data/lib/active_record/autosave_association.rb +11 -21
  42. data/lib/active_record/base.rb +9 -19
  43. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +69 -45
  44. data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -42
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -60
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +37 -2
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +102 -21
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +9 -33
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +178 -55
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +120 -115
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +143 -57
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +156 -107
  53. data/lib/active_record/connection_adapters/column.rb +13 -244
  54. data/lib/active_record/connection_adapters/connection_specification.rb +6 -20
  55. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -15
  56. data/lib/active_record/connection_adapters/mysql_adapter.rb +55 -143
  57. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  58. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  59. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -20
  60. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +96 -0
  62. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  64. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  67. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +76 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +85 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  86. data/lib/active_record/connection_adapters/postgresql/quoting.rb +42 -122
  87. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  88. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +154 -0
  89. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +86 -34
  90. data/lib/active_record/connection_adapters/postgresql/utils.rb +66 -0
  91. data/lib/active_record/connection_adapters/postgresql_adapter.rb +188 -452
  92. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -47
  94. data/lib/active_record/connection_handling.rb +1 -1
  95. data/lib/active_record/core.rb +119 -22
  96. data/lib/active_record/counter_cache.rb +60 -6
  97. data/lib/active_record/enum.rb +9 -10
  98. data/lib/active_record/errors.rb +27 -26
  99. data/lib/active_record/explain.rb +1 -1
  100. data/lib/active_record/fixtures.rb +52 -45
  101. data/lib/active_record/gem_version.rb +3 -3
  102. data/lib/active_record/inheritance.rb +33 -8
  103. data/lib/active_record/integration.rb +4 -4
  104. data/lib/active_record/locking/optimistic.rb +34 -16
  105. data/lib/active_record/migration.rb +22 -32
  106. data/lib/active_record/migration/command_recorder.rb +19 -2
  107. data/lib/active_record/migration/join_table.rb +1 -1
  108. data/lib/active_record/model_schema.rb +39 -48
  109. data/lib/active_record/nested_attributes.rb +8 -18
  110. data/lib/active_record/persistence.rb +39 -22
  111. data/lib/active_record/query_cache.rb +3 -3
  112. data/lib/active_record/querying.rb +1 -8
  113. data/lib/active_record/railtie.rb +17 -10
  114. data/lib/active_record/railties/databases.rake +47 -42
  115. data/lib/active_record/readonly_attributes.rb +0 -1
  116. data/lib/active_record/reflection.rb +225 -92
  117. data/lib/active_record/relation.rb +35 -11
  118. data/lib/active_record/relation/batches.rb +0 -2
  119. data/lib/active_record/relation/calculations.rb +28 -32
  120. data/lib/active_record/relation/delegation.rb +1 -1
  121. data/lib/active_record/relation/finder_methods.rb +42 -20
  122. data/lib/active_record/relation/merger.rb +0 -1
  123. data/lib/active_record/relation/predicate_builder.rb +1 -22
  124. data/lib/active_record/relation/predicate_builder/array_handler.rb +16 -11
  125. data/lib/active_record/relation/predicate_builder/relation_handler.rb +0 -4
  126. data/lib/active_record/relation/query_methods.rb +98 -62
  127. data/lib/active_record/relation/spawn_methods.rb +6 -7
  128. data/lib/active_record/result.rb +16 -9
  129. data/lib/active_record/sanitization.rb +8 -1
  130. data/lib/active_record/schema.rb +0 -1
  131. data/lib/active_record/schema_dumper.rb +51 -9
  132. data/lib/active_record/schema_migration.rb +4 -0
  133. data/lib/active_record/scoping/default.rb +5 -4
  134. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  135. data/lib/active_record/statement_cache.rb +79 -5
  136. data/lib/active_record/store.rb +5 -5
  137. data/lib/active_record/tasks/database_tasks.rb +37 -5
  138. data/lib/active_record/tasks/mysql_database_tasks.rb +10 -16
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -2
  140. data/lib/active_record/timestamp.rb +9 -7
  141. data/lib/active_record/transactions.rb +35 -21
  142. data/lib/active_record/type.rb +20 -0
  143. data/lib/active_record/type/binary.rb +40 -0
  144. data/lib/active_record/type/boolean.rb +19 -0
  145. data/lib/active_record/type/date.rb +46 -0
  146. data/lib/active_record/type/date_time.rb +43 -0
  147. data/lib/active_record/type/decimal.rb +40 -0
  148. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  149. data/lib/active_record/type/float.rb +19 -0
  150. data/lib/active_record/type/hash_lookup_type_map.rb +19 -0
  151. data/lib/active_record/type/integer.rb +23 -0
  152. data/lib/active_record/type/mutable.rb +16 -0
  153. data/lib/active_record/type/numeric.rb +36 -0
  154. data/lib/active_record/type/serialized.rb +51 -0
  155. data/lib/active_record/type/string.rb +36 -0
  156. data/lib/active_record/type/text.rb +11 -0
  157. data/lib/active_record/type/time.rb +26 -0
  158. data/lib/active_record/type/time_value.rb +38 -0
  159. data/lib/active_record/type/type_map.rb +48 -0
  160. data/lib/active_record/type/value.rb +101 -0
  161. data/lib/active_record/validations.rb +21 -16
  162. data/lib/active_record/validations/uniqueness.rb +9 -23
  163. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  164. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  165. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  166. metadata +71 -14
  167. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  end
14
14
 
15
15
  def visit_AddColumn(o)
16
- sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
16
+ sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale)
17
17
  sql = "ADD #{quote_column_name(o.name)} #{sql_type}"
18
18
  add_column_options!(sql, column_options(o))
19
19
  end
@@ -23,10 +23,12 @@ module ActiveRecord
23
23
  def visit_AlterTable(o)
24
24
  sql = "ALTER TABLE #{quote_table_name(o.name)} "
25
25
  sql << o.adds.map { |col| visit_AddColumn col }.join(' ')
26
+ sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(' ')
27
+ sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(' ')
26
28
  end
27
29
 
28
30
  def visit_ColumnDefinition(o)
29
- sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
31
+ sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale)
30
32
  column_sql = "#{quote_column_name(o.name)} #{sql_type}"
31
33
  add_column_options!(column_sql, column_options(o)) unless o.primary_key?
32
34
  column_sql
@@ -41,6 +43,21 @@ module ActiveRecord
41
43
  create_sql
42
44
  end
43
45
 
46
+ def visit_AddForeignKey(o)
47
+ sql = <<-SQL.strip_heredoc
48
+ ADD CONSTRAINT #{quote_column_name(o.name)}
49
+ FOREIGN KEY (#{quote_column_name(o.column)})
50
+ REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)})
51
+ SQL
52
+ sql << " #{action_sql('DELETE', o.on_delete)}" if o.on_delete
53
+ sql << " #{action_sql('UPDATE', o.on_update)}" if o.on_update
54
+ sql
55
+ end
56
+
57
+ def visit_DropForeignKey(name)
58
+ "DROP CONSTRAINT #{quote_column_name(name)}"
59
+ end
60
+
44
61
  def column_options(o)
45
62
  column_options = {}
46
63
  column_options[:null] = o.null unless o.null.nil?
@@ -77,6 +94,7 @@ module ActiveRecord
77
94
 
78
95
  def quote_value(value, column)
79
96
  column.sql_type ||= type_to_sql(column.type, column.limit, column.precision, column.scale)
97
+ column.cast_type ||= type_for_column(column)
80
98
 
81
99
  @conn.quote(value, column)
82
100
  end
@@ -84,6 +102,23 @@ module ActiveRecord
84
102
  def options_include_default?(options)
85
103
  options.include?(:default) && !(options[:null] == false && options[:default].nil?)
86
104
  end
105
+
106
+ def action_sql(action, dependency)
107
+ case dependency
108
+ when :nullify then "ON #{action} SET NULL"
109
+ when :cascade then "ON #{action} CASCADE"
110
+ when :restrict then "ON #{action} RESTRICT"
111
+ else
112
+ raise ArgumentError, <<-MSG.strip_heredoc
113
+ '#{dependency}' is not supported for :on_update or :on_delete.
114
+ Supported values are: :nullify, :cascade, :restrict
115
+ MSG
116
+ end
117
+ end
118
+
119
+ def type_for_column(column)
120
+ @conn.lookup_cast_type(column.sql_type)
121
+ end
87
122
  end
88
123
  end
89
124
  end
@@ -2,6 +2,7 @@ require 'date'
2
2
  require 'set'
3
3
  require 'bigdecimal'
4
4
  require 'bigdecimal/util'
5
+ require 'active_support/core_ext/string/strip'
5
6
 
6
7
  module ActiveRecord
7
8
  module ConnectionAdapters #:nodoc:
@@ -15,7 +16,7 @@ module ActiveRecord
15
16
  # are typically created by methods in TableDefinition, and added to the
16
17
  # +columns+ attribute of said TableDefinition object, in order to be used
17
18
  # for generating a number of table creation or table changing SQL statements.
18
- class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key, :sql_type) #:nodoc:
19
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key, :sql_type, :cast_type) #:nodoc:
19
20
 
20
21
  def primary_key?
21
22
  primary_key || type.to_sym == :primary_key
@@ -25,6 +26,50 @@ module ActiveRecord
25
26
  class ChangeColumnDefinition < Struct.new(:column, :type, :options) #:nodoc:
26
27
  end
27
28
 
29
+ class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc:
30
+ def name
31
+ options[:name]
32
+ end
33
+
34
+ def column
35
+ options[:column]
36
+ end
37
+
38
+ def primary_key
39
+ options[:primary_key] || default_primary_key
40
+ end
41
+
42
+ def on_delete
43
+ options[:on_delete]
44
+ end
45
+
46
+ def on_update
47
+ options[:on_update]
48
+ end
49
+
50
+ def custom_primary_key?
51
+ options[:primary_key] != default_primary_key
52
+ end
53
+
54
+ private
55
+ def default_primary_key
56
+ "id"
57
+ end
58
+ end
59
+
60
+ module TimestampDefaultDeprecation # :nodoc:
61
+ def emit_warning_if_null_unspecified(options)
62
+ return if options.key?(:null)
63
+
64
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
65
+ `timestamp` was called without specifying an option for `null`. In Rails
66
+ 5.0, this behavior will change to `null: false`. You should manually
67
+ specify `null: true` to prevent the behavior of your existing migrations
68
+ from changing.
69
+ MESSAGE
70
+ end
71
+ end
72
+
28
73
  # Represents the schema of an SQL table in an abstract way. This class
29
74
  # provides methods for manipulating the schema representation.
30
75
  #
@@ -46,6 +91,8 @@ module ActiveRecord
46
91
  # The table definitions
47
92
  # The Columns are stored as a ColumnDefinition in the +columns+ attribute.
48
93
  class TableDefinition
94
+ include TimestampDefaultDeprecation
95
+
49
96
  # An array of ColumnDefinition objects, representing the column changes
50
97
  # that have been defined.
51
98
  attr_accessor :indexes
@@ -99,9 +146,11 @@ module ActiveRecord
99
146
  # Specifies the precision for a <tt>:decimal</tt> column.
100
147
  # * <tt>:scale</tt> -
101
148
  # Specifies the scale for a <tt>:decimal</tt> column.
149
+ # * <tt>:index</tt> -
150
+ # Create an index for the column. Can be either <tt>true</tt> or an options hash.
102
151
  #
103
- # For clarity's sake: the precision is the number of significant digits,
104
- # while the scale is the number of digits that can be stored following
152
+ # Note: The precision is the total number of significant digits
153
+ # and the scale is the number of digits that can be stored following
105
154
  # the decimal point. For example, the number 123.45 has a precision of 5
106
155
  # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
107
156
  # range from -999.99 to 999.99.
@@ -123,17 +172,8 @@ module ActiveRecord
123
172
  # Default is (38,0).
124
173
  # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
125
174
  # Default unknown.
126
- # * Firebird: <tt>:precision</tt> [1..18], <tt>:scale</tt> [0..18].
127
- # Default (9,0). Internal types NUMERIC and DECIMAL have different
128
- # storage rules, decimal being better.
129
- # * FrontBase?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
130
- # Default (38,0). WARNING Max <tt>:precision</tt>/<tt>:scale</tt> for
131
- # NUMERIC is 19, and DECIMAL is 38.
132
175
  # * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
133
176
  # Default (38,0).
134
- # * Sybase: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
135
- # Default (38,0).
136
- # * OpenBase?: Documentation unclear. Claims storage in <tt>double</tt>.
137
177
  #
138
178
  # This method returns <tt>self</tt>.
139
179
  #
@@ -172,18 +212,21 @@ module ActiveRecord
172
212
  # What can be written like this with the regular calls to column:
173
213
  #
174
214
  # create_table :products do |t|
175
- # t.column :shop_id, :integer
176
- # t.column :creator_id, :integer
177
- # t.column :name, :string, default: "Untitled"
178
- # t.column :value, :string, default: "Untitled"
179
- # t.column :created_at, :datetime
180
- # t.column :updated_at, :datetime
215
+ # t.column :shop_id, :integer
216
+ # t.column :creator_id, :integer
217
+ # t.column :item_number, :string
218
+ # t.column :name, :string, default: "Untitled"
219
+ # t.column :value, :string, default: "Untitled"
220
+ # t.column :created_at, :datetime
221
+ # t.column :updated_at, :datetime
181
222
  # end
223
+ # add_index :products, :item_number
182
224
  #
183
225
  # can also be written as follows using the short-hand:
184
226
  #
185
227
  # create_table :products do |t|
186
228
  # t.integer :shop_id, :creator_id
229
+ # t.string :item_number, index: true
187
230
  # t.string :name, :value, default: "Untitled"
188
231
  # t.timestamps
189
232
  # end
@@ -219,6 +262,8 @@ module ActiveRecord
219
262
  raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
220
263
  end
221
264
 
265
+ index_options = options.delete(:index)
266
+ index(name, index_options.is_a?(Hash) ? index_options : {}) if index_options
222
267
  @columns_hash[name] = new_column_definition(name, type, options)
223
268
  self
224
269
  end
@@ -247,16 +292,27 @@ module ActiveRecord
247
292
  # <tt>:updated_at</tt> to the table.
248
293
  def timestamps(*args)
249
294
  options = args.extract_options!
295
+ emit_warning_if_null_unspecified(options)
250
296
  column(:created_at, :datetime, options)
251
297
  column(:updated_at, :datetime, options)
252
298
  end
253
299
 
300
+ # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
301
+ # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+
302
+ # by default, the <tt>:type</tt> option can be used to specify a different type.
303
+ #
304
+ # t.references(:user)
305
+ # t.references(:user, type: "string")
306
+ # t.belongs_to(:supplier, polymorphic: true)
307
+ #
308
+ # See SchemaStatements#add_reference
254
309
  def references(*args)
255
310
  options = args.extract_options!
256
311
  polymorphic = options.delete(:polymorphic)
257
312
  index_options = options.delete(:index)
313
+ type = options.delete(:type) || :integer
258
314
  args.each do |col|
259
- column("#{col}_id", :integer, options)
315
+ column("#{col}_id", type, options)
260
316
  column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
261
317
  index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
262
318
  end
@@ -264,6 +320,7 @@ module ActiveRecord
264
320
  alias :belongs_to :references
265
321
 
266
322
  def new_column_definition(name, type, options) # :nodoc:
323
+ type = aliased_types[type] || type
267
324
  column = create_column_definition name, type
268
325
  limit = options.fetch(:limit) do
269
326
  native[type][:limit] if native[type].is_a?(Hash)
@@ -294,18 +351,36 @@ module ActiveRecord
294
351
  def native
295
352
  @native
296
353
  end
354
+
355
+ def aliased_types
356
+ HashWithIndifferentAccess.new(
357
+ timestamp: :datetime,
358
+ )
359
+ end
297
360
  end
298
361
 
299
362
  class AlterTable # :nodoc:
300
363
  attr_reader :adds
364
+ attr_reader :foreign_key_adds
365
+ attr_reader :foreign_key_drops
301
366
 
302
367
  def initialize(td)
303
368
  @td = td
304
369
  @adds = []
370
+ @foreign_key_adds = []
371
+ @foreign_key_drops = []
305
372
  end
306
373
 
307
374
  def name; @td.name; end
308
375
 
376
+ def add_foreign_key(to_table, options)
377
+ @foreign_key_adds << ForeignKeyDefinition.new(name, to_table, options)
378
+ end
379
+
380
+ def drop_foreign_key(name)
381
+ @foreign_key_drops << name
382
+ end
383
+
309
384
  def add_column(name, type, options)
310
385
  name = name.to_s
311
386
  type = type.to_sym
@@ -347,6 +422,8 @@ module ActiveRecord
347
422
  # end
348
423
  #
349
424
  class Table
425
+ include TimestampDefaultDeprecation
426
+
350
427
  def initialize(table_name, base)
351
428
  @table_name = table_name
352
429
  @base = base
@@ -395,6 +472,7 @@ module ActiveRecord
395
472
  #
396
473
  # t.timestamps
397
474
  def timestamps(options = {})
475
+ emit_warning_if_null_unspecified(options)
398
476
  @base.add_timestamps(@table_name, options)
399
477
  end
400
478
 
@@ -452,11 +530,14 @@ module ActiveRecord
452
530
  end
453
531
 
454
532
  # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
455
- # <tt>references</tt> and <tt>belongs_to</tt> are acceptable.
533
+ # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+
534
+ # by default, the <tt>:type</tt> option can be used to specify a different type.
456
535
  #
457
536
  # t.references(:user)
537
+ # t.references(:user, type: "string")
458
538
  # t.belongs_to(:supplier, polymorphic: true)
459
539
  #
540
+ # See SchemaStatements#add_reference
460
541
  def references(*args)
461
542
  options = args.extract_options!
462
543
  args.each do |ref_name|
@@ -471,6 +552,7 @@ module ActiveRecord
471
552
  # t.remove_references(:user)
472
553
  # t.remove_belongs_to(:supplier, polymorphic: true)
473
554
  #
555
+ # See SchemaStatements#remove_reference
474
556
  def remove_references(*args)
475
557
  options = args.extract_options!
476
558
  args.each do |ref_name|
@@ -497,6 +579,5 @@ module ActiveRecord
497
579
  @base.native_database_types
498
580
  end
499
581
  end
500
-
501
582
  end
502
583
  end
@@ -1,5 +1,3 @@
1
- require 'ipaddr'
2
-
3
1
  module ActiveRecord
4
2
  module ConnectionAdapters # :nodoc:
5
3
  # The goal of this module is to move Adapter specific column
@@ -20,19 +18,13 @@ module ActiveRecord
20
18
  def prepare_column_options(column, types)
21
19
  spec = {}
22
20
  spec[:name] = column.name.inspect
23
-
24
- # AR has an optimization which handles zero-scale decimals as integers. This
25
- # code ensures that the dumper still dumps the column as a decimal.
26
- spec[:type] = if column.type == :integer && /^(numeric|decimal)/ =~ column.sql_type
27
- 'decimal'
28
- else
29
- column.type.to_s
30
- end
31
- spec[:limit] = column.limit.inspect if column.limit != types[column.type][:limit] && spec[:type] != 'decimal'
21
+ spec[:type] = column.type.to_s
22
+ spec[:limit] = column.limit.inspect if column.limit != types[column.type][:limit]
32
23
  spec[:precision] = column.precision.inspect if column.precision
33
24
  spec[:scale] = column.scale.inspect if column.scale
34
25
  spec[:null] = 'false' unless column.null
35
- spec[:default] = default_string(column.default) if column.has_default?
26
+ spec[:default] = schema_default(column) if column.has_default?
27
+ spec.delete(:default) if spec[:default].nil?
36
28
  spec
37
29
  end
38
30
 
@@ -43,28 +35,12 @@ module ActiveRecord
43
35
 
44
36
  private
45
37
 
46
- def default_string(value)
47
- case value
48
- when BigDecimal
49
- value.to_s
50
- when Date, DateTime, Time
51
- "'#{value.to_s(:db)}'"
52
- when Range
53
- # infinity dumps as Infinity, which causes uninitialized constant error
54
- value.inspect.gsub('Infinity', '::Float::INFINITY')
55
- when IPAddr
56
- subnet_mask = value.instance_variable_get(:@mask_addr)
57
-
58
- # If the subnet mask is equal to /32, don't output it
59
- if subnet_mask == (2**32 - 1)
60
- "\"#{value.to_s}\""
61
- else
62
- "\"#{value.to_s}/#{subnet_mask.to_s(2).count('1')}\""
63
- end
64
- else
65
- value.inspect
66
- end
38
+ def schema_default(column)
39
+ default = column.type_cast_from_database(column.default)
40
+ unless default.nil?
41
+ column.type_cast_for_schema(default)
67
42
  end
43
+ end
68
44
  end
69
45
  end
70
46
  end
@@ -43,13 +43,14 @@ module ActiveRecord
43
43
  # index_exists?(:suppliers, :company_id, name: "idx_company_id")
44
44
  #
45
45
  def index_exists?(table_name, column_name, options = {})
46
- column_names = Array(column_name)
47
- index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
48
- if options[:unique]
49
- indexes(table_name).any?{ |i| i.unique && i.name == index_name }
50
- else
51
- indexes(table_name).any?{ |i| i.name == index_name }
52
- end
46
+ column_names = Array(column_name).map(&:to_s)
47
+ index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, column: column_names)
48
+ checks = []
49
+ checks << lambda { |i| i.name == index_name }
50
+ checks << lambda { |i| i.columns == column_names }
51
+ checks << lambda { |i| i.unique } if options[:unique]
52
+
53
+ indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
53
54
  end
54
55
 
55
56
  # Returns an array of Column objects for the table specified by +table_name+.
@@ -71,7 +72,8 @@ module ActiveRecord
71
72
  # column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
72
73
  #
73
74
  def column_exists?(table_name, column_name, type = nil, options = {})
74
- columns(table_name).any?{ |c| c.name == column_name.to_s &&
75
+ column_name = column_name.to_s
76
+ columns(table_name).any?{ |c| c.name == column_name &&
75
77
  (!type || c.type == type) &&
76
78
  (!options.key?(:limit) || c.limit == options[:limit]) &&
77
79
  (!options.key?(:precision) || c.precision == options[:precision]) &&
@@ -186,24 +188,23 @@ module ActiveRecord
186
188
  def create_table(table_name, options = {})
187
189
  td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
188
190
 
189
- if !options[:as]
190
- unless options[:id] == false
191
- pk = options.fetch(:primary_key) {
192
- Base.get_primary_key table_name.to_s.singularize
193
- }
194
-
195
- td.primary_key pk, options.fetch(:id, :primary_key), options
191
+ if options[:id] != false && !options[:as]
192
+ pk = options.fetch(:primary_key) do
193
+ Base.get_primary_key table_name.to_s.singularize
196
194
  end
197
195
 
198
- yield td if block_given?
196
+ td.primary_key pk, options.fetch(:id, :primary_key), options
199
197
  end
200
198
 
199
+ yield td if block_given?
200
+
201
201
  if options[:force] && table_exists?(table_name)
202
202
  drop_table(table_name, options)
203
203
  end
204
204
 
205
- execute schema_creation.accept td
206
- td.indexes.each_pair { |c,o| add_index table_name, c, o }
205
+ result = execute schema_creation.accept td
206
+ td.indexes.each_pair { |c, o| add_index(table_name, c, o) } unless supports_indexes_in_create?
207
+ result
207
208
  end
208
209
 
209
210
  # Creates a new join table with the name created using the lexical order of the first two
@@ -602,12 +603,18 @@ module ActiveRecord
602
603
  end
603
604
 
604
605
  # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
606
+ # The reference column is an +integer+ by default, the <tt>:type</tt> option can be used to specify
607
+ # a different type.
605
608
  # <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
606
609
  #
607
- # ====== Create a user_id column
610
+ # ====== Create a user_id integer column
608
611
  #
609
612
  # add_reference(:products, :user)
610
613
  #
614
+ # ====== Create a user_id string column
615
+ #
616
+ # add_reference(:products, :user, type: :string)
617
+ #
611
618
  # ====== Create a supplier_id and supplier_type columns
612
619
  #
613
620
  # add_belongs_to(:products, :supplier, polymorphic: true)
@@ -619,7 +626,8 @@ module ActiveRecord
619
626
  def add_reference(table_name, ref_name, options = {})
620
627
  polymorphic = options.delete(:polymorphic)
621
628
  index_options = options.delete(:index)
622
- add_column(table_name, "#{ref_name}_id", :integer, options)
629
+ type = options.delete(:type) || :integer
630
+ add_column(table_name, "#{ref_name}_id", type, options)
623
631
  add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
624
632
  add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
625
633
  end
@@ -642,6 +650,115 @@ module ActiveRecord
642
650
  end
643
651
  alias :remove_belongs_to :remove_reference
644
652
 
653
+ # Returns an array of foreign keys for the given table.
654
+ # The foreign keys are represented as +ForeignKeyDefinition+ objects.
655
+ def foreign_keys(table_name)
656
+ raise NotImplementedError, "foreign_keys is not implemented"
657
+ end
658
+
659
+ # Adds a new foreign key. +from_table+ is the table with the key column,
660
+ # +to_table+ contains the referenced primary key.
661
+ #
662
+ # The foreign key will be named after the following pattern: <tt>fk_rails_<identifier></tt>.
663
+ # +identifier+ is a 10 character long random string. A custom name can be specified with
664
+ # the <tt>:name</tt> option.
665
+ #
666
+ # ====== Creating a simple foreign key
667
+ #
668
+ # add_foreign_key :articles, :authors
669
+ #
670
+ # generates:
671
+ #
672
+ # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
673
+ #
674
+ # ====== Creating a foreign key on a specific column
675
+ #
676
+ # add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id"
677
+ #
678
+ # generates:
679
+ #
680
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
681
+ #
682
+ # ====== Creating a cascading foreign key
683
+ #
684
+ # add_foreign_key :articles, :authors, on_delete: :cascade
685
+ #
686
+ # generates:
687
+ #
688
+ # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
689
+ #
690
+ # The +options+ hash can include the following keys:
691
+ # [<tt>:column</tt>]
692
+ # The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>
693
+ # [<tt>:primary_key</tt>]
694
+ # The primary key column name on +to_table+. Defaults to +id+.
695
+ # [<tt>:name</tt>]
696
+ # The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
697
+ # [<tt>:on_delete</tt>]
698
+ # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
699
+ # [<tt>:on_update</tt>]
700
+ # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
701
+ def add_foreign_key(from_table, to_table, options = {})
702
+ return unless supports_foreign_keys?
703
+
704
+ options[:column] ||= foreign_key_column_for(to_table)
705
+
706
+ options = {
707
+ column: options[:column],
708
+ primary_key: options[:primary_key],
709
+ name: foreign_key_name(from_table, options),
710
+ on_delete: options[:on_delete],
711
+ on_update: options[:on_update]
712
+ }
713
+ at = create_alter_table from_table
714
+ at.add_foreign_key to_table, options
715
+
716
+ execute schema_creation.accept(at)
717
+ end
718
+
719
+ # Removes the given foreign key from the table.
720
+ #
721
+ # Removes the foreign key on +accounts.branch_id+.
722
+ #
723
+ # remove_foreign_key :accounts, :branches
724
+ #
725
+ # Removes the foreign key on +accounts.owner_id+.
726
+ #
727
+ # remove_foreign_key :accounts, column: :owner_id
728
+ #
729
+ # Removes the foreign key named +special_fk_name+ on the +accounts+ table.
730
+ #
731
+ # remove_foreign_key :accounts, name: :special_fk_name
732
+ #
733
+ def remove_foreign_key(from_table, options_or_to_table = {})
734
+ return unless supports_foreign_keys?
735
+
736
+ if options_or_to_table.is_a?(Hash)
737
+ options = options_or_to_table
738
+ else
739
+ options = { column: foreign_key_column_for(options_or_to_table) }
740
+ end
741
+
742
+ fk_name_to_delete = options.fetch(:name) do
743
+ fk_to_delete = foreign_keys(from_table).detect {|fk| fk.column == options[:column] }
744
+
745
+ if fk_to_delete
746
+ fk_to_delete.name
747
+ else
748
+ raise ArgumentError, "Table '#{from_table}' has no foreign key on column '#{options[:column]}'"
749
+ end
750
+ end
751
+
752
+ at = create_alter_table from_table
753
+ at.drop_foreign_key fk_name_to_delete
754
+
755
+ execute schema_creation.accept(at)
756
+ end
757
+
758
+ def foreign_key_column_for(table_name) # :nodoc:
759
+ "#{table_name.to_s.singularize}_id"
760
+ end
761
+
645
762
  def dump_schema_information #:nodoc:
646
763
  sm_table = ActiveRecord::Migrator.schema_migrations_table_name
647
764
 
@@ -740,6 +857,40 @@ module ActiveRecord
740
857
  Table.new(table_name, base)
741
858
  end
742
859
 
860
+ def add_index_options(table_name, column_name, options = {}) #:nodoc:
861
+ column_names = Array(column_name)
862
+ index_name = index_name(table_name, column: column_names)
863
+
864
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
865
+
866
+ index_type = options[:unique] ? "UNIQUE" : ""
867
+ index_type = options[:type].to_s if options.key?(:type)
868
+ index_name = options[:name].to_s if options.key?(:name)
869
+ max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
870
+
871
+ if options.key?(:algorithm)
872
+ algorithm = index_algorithms.fetch(options[:algorithm]) {
873
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
874
+ }
875
+ end
876
+
877
+ using = "USING #{options[:using]}" if options[:using].present?
878
+
879
+ if supports_partial_index?
880
+ index_options = options[:where] ? " WHERE #{options[:where]}" : ""
881
+ end
882
+
883
+ if index_name.length > max_index_length
884
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
885
+ end
886
+ if table_exists?(table_name) && index_name_exists?(table_name, index_name, false)
887
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
888
+ end
889
+ index_columns = quoted_columns_for_index(column_names, options).join(", ")
890
+
891
+ [index_name, index_type, index_columns, index_options, algorithm, using]
892
+ end
893
+
743
894
  protected
744
895
  def add_index_sort_order(option_strings, column_names, options = {})
745
896
  if options.is_a?(Hash) && order = options[:order]
@@ -754,7 +905,7 @@ module ActiveRecord
754
905
  return option_strings
755
906
  end
756
907
 
757
- # Overridden by the mysql adapter for supporting index lengths
908
+ # Overridden by the MySQL adapter for supporting index lengths
758
909
  def quoted_columns_for_index(column_names, options = {})
759
910
  option_strings = Hash[column_names.map {|name| [name, '']}]
760
911
 
@@ -770,40 +921,6 @@ module ActiveRecord
770
921
  options.include?(:default) && !(options[:null] == false && options[:default].nil?)
771
922
  end
772
923
 
773
- def add_index_options(table_name, column_name, options = {})
774
- column_names = Array(column_name)
775
- index_name = index_name(table_name, column: column_names)
776
-
777
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
778
-
779
- index_type = options[:unique] ? "UNIQUE" : ""
780
- index_type = options[:type].to_s if options.key?(:type)
781
- index_name = options[:name].to_s if options.key?(:name)
782
- max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
783
-
784
- if options.key?(:algorithm)
785
- algorithm = index_algorithms.fetch(options[:algorithm]) {
786
- raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
787
- }
788
- end
789
-
790
- using = "USING #{options[:using]}" if options[:using].present?
791
-
792
- if supports_partial_index?
793
- index_options = options[:where] ? " WHERE #{options[:where]}" : ""
794
- end
795
-
796
- if index_name.length > max_index_length
797
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
798
- end
799
- if index_name_exists?(table_name, index_name, false)
800
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
801
- end
802
- index_columns = quoted_columns_for_index(column_names, options).join(", ")
803
-
804
- [index_name, index_type, index_columns, index_options, algorithm, using]
805
- end
806
-
807
924
  def index_name_for_remove(table_name, options = {})
808
925
  index_name = index_name(table_name, options)
809
926
 
@@ -852,6 +969,12 @@ module ActiveRecord
852
969
  def create_alter_table(name)
853
970
  AlterTable.new create_table_definition(name, false, {})
854
971
  end
972
+
973
+ def foreign_key_name(table_name, options) # :nodoc:
974
+ options.fetch(:name) do
975
+ "fk_rails_#{SecureRandom.hex(5)}"
976
+ end
977
+ end
855
978
  end
856
979
  end
857
980
  end