activerecord 4.1.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +776 -1330
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +12 -8
  5. data/lib/active_record/association_relation.rb +4 -0
  6. data/lib/active_record/associations/alias_tracker.rb +14 -13
  7. data/lib/active_record/associations/association.rb +2 -2
  8. data/lib/active_record/associations/association_scope.rb +83 -43
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  10. data/lib/active_record/associations/builder/association.rb +15 -4
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -6
  13. data/lib/active_record/associations/builder/has_many.rb +1 -1
  14. data/lib/active_record/associations/builder/has_one.rb +2 -2
  15. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  16. data/lib/active_record/associations/collection_association.rb +66 -29
  17. data/lib/active_record/associations/collection_proxy.rb +22 -26
  18. data/lib/active_record/associations/has_many_association.rb +65 -18
  19. data/lib/active_record/associations/has_many_through_association.rb +55 -27
  20. data/lib/active_record/associations/has_one_association.rb +0 -1
  21. data/lib/active_record/associations/join_dependency/join_association.rb +19 -15
  22. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  23. data/lib/active_record/associations/join_dependency.rb +20 -12
  24. data/lib/active_record/associations/preloader/association.rb +34 -11
  25. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  26. data/lib/active_record/associations/preloader.rb +49 -59
  27. data/lib/active_record/associations/singular_association.rb +25 -4
  28. data/lib/active_record/associations/through_association.rb +23 -14
  29. data/lib/active_record/associations.rb +171 -42
  30. data/lib/active_record/attribute.rb +149 -0
  31. data/lib/active_record/attribute_assignment.rb +18 -10
  32. data/lib/active_record/attribute_decorators.rb +66 -0
  33. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  34. data/lib/active_record/attribute_methods/dirty.rb +98 -44
  35. data/lib/active_record/attribute_methods/primary_key.rb +14 -8
  36. data/lib/active_record/attribute_methods/query.rb +1 -1
  37. data/lib/active_record/attribute_methods/read.rb +22 -59
  38. data/lib/active_record/attribute_methods/serialization.rb +37 -147
  39. data/lib/active_record/attribute_methods/time_zone_conversion.rb +34 -28
  40. data/lib/active_record/attribute_methods/write.rb +14 -21
  41. data/lib/active_record/attribute_methods.rb +67 -94
  42. data/lib/active_record/attribute_set/builder.rb +86 -0
  43. data/lib/active_record/attribute_set.rb +77 -0
  44. data/lib/active_record/attributes.rb +139 -0
  45. data/lib/active_record/autosave_association.rb +45 -38
  46. data/lib/active_record/base.rb +10 -20
  47. data/lib/active_record/callbacks.rb +7 -7
  48. data/lib/active_record/coders/json.rb +13 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +78 -52
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +38 -59
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -55
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -5
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +126 -54
  55. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +198 -64
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +126 -114
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -55
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +240 -135
  60. data/lib/active_record/connection_adapters/column.rb +28 -239
  61. data/lib/active_record/connection_adapters/connection_specification.rb +16 -25
  62. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -22
  63. data/lib/active_record/connection_adapters/mysql_adapter.rb +65 -149
  64. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  65. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  66. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -27
  67. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -374
  93. data/lib/active_record/connection_adapters/postgresql/quoting.rb +55 -135
  94. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  96. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +127 -38
  97. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  98. data/lib/active_record/connection_adapters/postgresql_adapter.rb +220 -466
  99. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  100. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -61
  101. data/lib/active_record/connection_handling.rb +3 -3
  102. data/lib/active_record/core.rb +143 -32
  103. data/lib/active_record/counter_cache.rb +60 -7
  104. data/lib/active_record/enum.rb +10 -11
  105. data/lib/active_record/errors.rb +49 -27
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/fixtures.rb +56 -70
  108. data/lib/active_record/gem_version.rb +2 -2
  109. data/lib/active_record/inheritance.rb +35 -10
  110. data/lib/active_record/integration.rb +4 -4
  111. data/lib/active_record/locking/optimistic.rb +35 -17
  112. data/lib/active_record/log_subscriber.rb +1 -1
  113. data/lib/active_record/migration/command_recorder.rb +19 -2
  114. data/lib/active_record/migration/join_table.rb +1 -1
  115. data/lib/active_record/migration.rb +52 -49
  116. data/lib/active_record/model_schema.rb +49 -57
  117. data/lib/active_record/nested_attributes.rb +7 -7
  118. data/lib/active_record/null_relation.rb +19 -5
  119. data/lib/active_record/persistence.rb +50 -31
  120. data/lib/active_record/query_cache.rb +3 -3
  121. data/lib/active_record/querying.rb +10 -7
  122. data/lib/active_record/railtie.rb +14 -11
  123. data/lib/active_record/railties/databases.rake +56 -54
  124. data/lib/active_record/readonly_attributes.rb +0 -1
  125. data/lib/active_record/reflection.rb +286 -102
  126. data/lib/active_record/relation/batches.rb +0 -1
  127. data/lib/active_record/relation/calculations.rb +39 -31
  128. data/lib/active_record/relation/delegation.rb +2 -2
  129. data/lib/active_record/relation/finder_methods.rb +80 -36
  130. data/lib/active_record/relation/merger.rb +25 -30
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +31 -13
  132. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  133. data/lib/active_record/relation/predicate_builder.rb +11 -10
  134. data/lib/active_record/relation/query_methods.rb +141 -55
  135. data/lib/active_record/relation/spawn_methods.rb +3 -0
  136. data/lib/active_record/relation.rb +69 -30
  137. data/lib/active_record/result.rb +18 -7
  138. data/lib/active_record/sanitization.rb +12 -2
  139. data/lib/active_record/schema.rb +0 -1
  140. data/lib/active_record/schema_dumper.rb +58 -26
  141. data/lib/active_record/schema_migration.rb +11 -0
  142. data/lib/active_record/scoping/default.rb +8 -7
  143. data/lib/active_record/scoping/named.rb +4 -0
  144. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  145. data/lib/active_record/statement_cache.rb +95 -10
  146. data/lib/active_record/store.rb +19 -10
  147. data/lib/active_record/tasks/database_tasks.rb +73 -7
  148. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -2
  149. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  151. data/lib/active_record/timestamp.rb +11 -9
  152. data/lib/active_record/transactions.rb +37 -21
  153. data/lib/active_record/type/big_integer.rb +13 -0
  154. data/lib/active_record/type/binary.rb +50 -0
  155. data/lib/active_record/type/boolean.rb +30 -0
  156. data/lib/active_record/type/date.rb +46 -0
  157. data/lib/active_record/type/date_time.rb +43 -0
  158. data/lib/active_record/type/decimal.rb +40 -0
  159. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  160. data/lib/active_record/type/decorator.rb +14 -0
  161. data/lib/active_record/type/float.rb +19 -0
  162. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  163. data/lib/active_record/type/integer.rb +55 -0
  164. data/lib/active_record/type/mutable.rb +16 -0
  165. data/lib/active_record/type/numeric.rb +36 -0
  166. data/lib/active_record/type/serialized.rb +56 -0
  167. data/lib/active_record/type/string.rb +36 -0
  168. data/lib/active_record/type/text.rb +11 -0
  169. data/lib/active_record/type/time.rb +26 -0
  170. data/lib/active_record/type/time_value.rb +38 -0
  171. data/lib/active_record/type/type_map.rb +64 -0
  172. data/lib/active_record/type/unsigned_integer.rb +15 -0
  173. data/lib/active_record/type/value.rb +101 -0
  174. data/lib/active_record/type.rb +23 -0
  175. data/lib/active_record/validations/associated.rb +5 -3
  176. data/lib/active_record/validations/presence.rb +6 -4
  177. data/lib/active_record/validations/uniqueness.rb +11 -17
  178. data/lib/active_record/validations.rb +25 -19
  179. data/lib/active_record.rb +3 -0
  180. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  181. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +4 -1
  182. data/lib/rails/generators/active_record/migration/templates/migration.rb +6 -0
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +65 -10
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -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]) &&
@@ -130,6 +132,7 @@ module ActiveRecord
130
132
  # Make a temporary table.
131
133
  # [<tt>:force</tt>]
132
134
  # Set to true to drop the table before creating it.
135
+ # Set to +:cascade+ to drop dependent objects as well.
133
136
  # Defaults to false.
134
137
  # [<tt>:as</tt>]
135
138
  # SQL to use to generate the table. When this option is used, the block is
@@ -186,24 +189,23 @@ module ActiveRecord
186
189
  def create_table(table_name, options = {})
187
190
  td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
188
191
 
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
192
+ if options[:id] != false && !options[:as]
193
+ pk = options.fetch(:primary_key) do
194
+ Base.get_primary_key table_name.to_s.singularize
196
195
  end
197
196
 
198
- yield td if block_given?
197
+ td.primary_key pk, options.fetch(:id, :primary_key), options
199
198
  end
200
199
 
200
+ yield td if block_given?
201
+
201
202
  if options[:force] && table_exists?(table_name)
202
203
  drop_table(table_name, options)
203
204
  end
204
205
 
205
- execute schema_creation.accept td
206
- td.indexes.each_pair { |c,o| add_index table_name, c, o }
206
+ result = execute schema_creation.accept td
207
+ td.indexes.each_pair { |c, o| add_index(table_name, c, o) } unless supports_indexes_in_create?
208
+ result
207
209
  end
208
210
 
209
211
  # Creates a new join table with the name created using the lexical order of the first two
@@ -360,8 +362,12 @@ module ActiveRecord
360
362
 
361
363
  # Drops a table from the database.
362
364
  #
363
- # Although this command ignores +options+ and the block if one is given, it can be helpful
364
- # to provide these in a migration's +change+ method so it can be reverted.
365
+ # [<tt>:force</tt>]
366
+ # Set to +:cascade+ to drop dependent objects as well.
367
+ # Defaults to false.
368
+ #
369
+ # Although this command ignores most +options+ and the block if one is given,
370
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
365
371
  # In that case, +options+ and the block will be used by create_table.
366
372
  def drop_table(table_name, options = {})
367
373
  execute "DROP TABLE #{quote_table_name(table_name)}"
@@ -570,6 +576,9 @@ module ActiveRecord
570
576
  # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
571
577
  #
572
578
  def rename_index(table_name, old_name, new_name)
579
+ if new_name.length > allowed_index_name_length
580
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
581
+ end
573
582
  # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
574
583
  old_index_def = indexes(table_name).detect { |i| i.name == old_name }
575
584
  return unless old_index_def
@@ -602,12 +611,18 @@ module ActiveRecord
602
611
  end
603
612
 
604
613
  # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
614
+ # The reference column is an +integer+ by default, the <tt>:type</tt> option can be used to specify
615
+ # a different type.
605
616
  # <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
606
617
  #
607
- # ====== Create a user_id column
618
+ # ====== Create a user_id integer column
608
619
  #
609
620
  # add_reference(:products, :user)
610
621
  #
622
+ # ====== Create a user_id string column
623
+ #
624
+ # add_reference(:products, :user, type: :string)
625
+ #
611
626
  # ====== Create a supplier_id and supplier_type columns
612
627
  #
613
628
  # add_belongs_to(:products, :supplier, polymorphic: true)
@@ -619,9 +634,10 @@ module ActiveRecord
619
634
  def add_reference(table_name, ref_name, options = {})
620
635
  polymorphic = options.delete(:polymorphic)
621
636
  index_options = options.delete(:index)
622
- add_column(table_name, "#{ref_name}_id", :integer, options)
637
+ type = options.delete(:type) || :integer
638
+ add_column(table_name, "#{ref_name}_id", type, options)
623
639
  add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
624
- add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
640
+ 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
625
641
  end
626
642
  alias :add_belongs_to :add_reference
627
643
 
@@ -642,6 +658,115 @@ module ActiveRecord
642
658
  end
643
659
  alias :remove_belongs_to :remove_reference
644
660
 
661
+ # Returns an array of foreign keys for the given table.
662
+ # The foreign keys are represented as +ForeignKeyDefinition+ objects.
663
+ def foreign_keys(table_name)
664
+ raise NotImplementedError, "foreign_keys is not implemented"
665
+ end
666
+
667
+ # Adds a new foreign key. +from_table+ is the table with the key column,
668
+ # +to_table+ contains the referenced primary key.
669
+ #
670
+ # The foreign key will be named after the following pattern: <tt>fk_rails_<identifier></tt>.
671
+ # +identifier+ is a 10 character long random string. A custom name can be specified with
672
+ # the <tt>:name</tt> option.
673
+ #
674
+ # ====== Creating a simple foreign key
675
+ #
676
+ # add_foreign_key :articles, :authors
677
+ #
678
+ # generates:
679
+ #
680
+ # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
681
+ #
682
+ # ====== Creating a foreign key on a specific column
683
+ #
684
+ # add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id"
685
+ #
686
+ # generates:
687
+ #
688
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
689
+ #
690
+ # ====== Creating a cascading foreign key
691
+ #
692
+ # add_foreign_key :articles, :authors, on_delete: :cascade
693
+ #
694
+ # generates:
695
+ #
696
+ # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
697
+ #
698
+ # The +options+ hash can include the following keys:
699
+ # [<tt>:column</tt>]
700
+ # The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>
701
+ # [<tt>:primary_key</tt>]
702
+ # The primary key column name on +to_table+. Defaults to +id+.
703
+ # [<tt>:name</tt>]
704
+ # The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
705
+ # [<tt>:on_delete</tt>]
706
+ # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
707
+ # [<tt>:on_update</tt>]
708
+ # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
709
+ def add_foreign_key(from_table, to_table, options = {})
710
+ return unless supports_foreign_keys?
711
+
712
+ options[:column] ||= foreign_key_column_for(to_table)
713
+
714
+ options = {
715
+ column: options[:column],
716
+ primary_key: options[:primary_key],
717
+ name: foreign_key_name(from_table, options),
718
+ on_delete: options[:on_delete],
719
+ on_update: options[:on_update]
720
+ }
721
+ at = create_alter_table from_table
722
+ at.add_foreign_key to_table, options
723
+
724
+ execute schema_creation.accept(at)
725
+ end
726
+
727
+ # Removes the given foreign key from the table.
728
+ #
729
+ # Removes the foreign key on +accounts.branch_id+.
730
+ #
731
+ # remove_foreign_key :accounts, :branches
732
+ #
733
+ # Removes the foreign key on +accounts.owner_id+.
734
+ #
735
+ # remove_foreign_key :accounts, column: :owner_id
736
+ #
737
+ # Removes the foreign key named +special_fk_name+ on the +accounts+ table.
738
+ #
739
+ # remove_foreign_key :accounts, name: :special_fk_name
740
+ #
741
+ def remove_foreign_key(from_table, options_or_to_table = {})
742
+ return unless supports_foreign_keys?
743
+
744
+ if options_or_to_table.is_a?(Hash)
745
+ options = options_or_to_table
746
+ else
747
+ options = { column: foreign_key_column_for(options_or_to_table) }
748
+ end
749
+
750
+ fk_name_to_delete = options.fetch(:name) do
751
+ fk_to_delete = foreign_keys(from_table).detect {|fk| fk.column == options[:column].to_s }
752
+
753
+ if fk_to_delete
754
+ fk_to_delete.name
755
+ else
756
+ raise ArgumentError, "Table '#{from_table}' has no foreign key on column '#{options[:column]}'"
757
+ end
758
+ end
759
+
760
+ at = create_alter_table from_table
761
+ at.drop_foreign_key fk_name_to_delete
762
+
763
+ execute schema_creation.accept(at)
764
+ end
765
+
766
+ def foreign_key_column_for(table_name) # :nodoc:
767
+ "#{table_name.to_s.singularize}_id"
768
+ end
769
+
645
770
  def dump_schema_information #:nodoc:
646
771
  sm_table = ActiveRecord::Migrator.schema_migrations_table_name
647
772
 
@@ -718,20 +843,23 @@ module ActiveRecord
718
843
  columns
719
844
  end
720
845
 
721
- # Adds timestamps (+created_at+ and +updated_at+) columns to the named table.
846
+ include TimestampDefaultDeprecation
847
+ # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
848
+ # Additional options (like <tt>null: false</tt>) are forwarded to #add_column.
722
849
  #
723
- # add_timestamps(:suppliers)
850
+ # add_timestamps(:suppliers, null: false)
724
851
  #
725
- def add_timestamps(table_name)
726
- add_column table_name, :created_at, :datetime
727
- add_column table_name, :updated_at, :datetime
852
+ def add_timestamps(table_name, options = {})
853
+ emit_warning_if_null_unspecified(options)
854
+ add_column table_name, :created_at, :datetime, options
855
+ add_column table_name, :updated_at, :datetime, options
728
856
  end
729
857
 
730
858
  # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
731
859
  #
732
860
  # remove_timestamps(:suppliers)
733
861
  #
734
- def remove_timestamps(table_name)
862
+ def remove_timestamps(table_name, options = {})
735
863
  remove_column table_name, :updated_at
736
864
  remove_column table_name, :created_at
737
865
  end
@@ -740,6 +868,40 @@ module ActiveRecord
740
868
  Table.new(table_name, base)
741
869
  end
742
870
 
871
+ def add_index_options(table_name, column_name, options = {}) #:nodoc:
872
+ column_names = Array(column_name)
873
+ index_name = index_name(table_name, column: column_names)
874
+
875
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
876
+
877
+ index_type = options[:unique] ? "UNIQUE" : ""
878
+ index_type = options[:type].to_s if options.key?(:type)
879
+ index_name = options[:name].to_s if options.key?(:name)
880
+ max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
881
+
882
+ if options.key?(:algorithm)
883
+ algorithm = index_algorithms.fetch(options[:algorithm]) {
884
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
885
+ }
886
+ end
887
+
888
+ using = "USING #{options[:using]}" if options[:using].present?
889
+
890
+ if supports_partial_index?
891
+ index_options = options[:where] ? " WHERE #{options[:where]}" : ""
892
+ end
893
+
894
+ if index_name.length > max_index_length
895
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
896
+ end
897
+ if table_exists?(table_name) && index_name_exists?(table_name, index_name, false)
898
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
899
+ end
900
+ index_columns = quoted_columns_for_index(column_names, options).join(", ")
901
+
902
+ [index_name, index_type, index_columns, index_options, algorithm, using]
903
+ end
904
+
743
905
  protected
744
906
  def add_index_sort_order(option_strings, column_names, options = {})
745
907
  if options.is_a?(Hash) && order = options[:order]
@@ -754,7 +916,7 @@ module ActiveRecord
754
916
  return option_strings
755
917
  end
756
918
 
757
- # Overridden by the mysql adapter for supporting index lengths
919
+ # Overridden by the MySQL adapter for supporting index lengths
758
920
  def quoted_columns_for_index(column_names, options = {})
759
921
  option_strings = Hash[column_names.map {|name| [name, '']}]
760
922
 
@@ -770,40 +932,6 @@ module ActiveRecord
770
932
  options.include?(:default) && !(options[:null] == false && options[:default].nil?)
771
933
  end
772
934
 
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
935
  def index_name_for_remove(table_name, options = {})
808
936
  index_name = index_name(table_name, options)
809
937
 
@@ -852,6 +980,12 @@ module ActiveRecord
852
980
  def create_alter_table(name)
853
981
  AlterTable.new create_table_definition(name, false, {})
854
982
  end
983
+
984
+ def foreign_key_name(table_name, options) # :nodoc:
985
+ options.fetch(:name) do
986
+ "fk_rails_#{SecureRandom.hex(5)}"
987
+ end
988
+ end
855
989
  end
856
990
  end
857
991
  end
@@ -1,20 +1,7 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
- class Transaction #:nodoc:
4
- attr_reader :connection
5
-
6
- def initialize(connection)
7
- @connection = connection
8
- @state = TransactionState.new
9
- end
10
-
11
- def state
12
- @state
13
- end
14
- end
15
-
16
3
  class TransactionState
17
- attr_accessor :parent
4
+ attr_reader :parent
18
5
 
19
6
  VALID_STATES = Set.new([:committed, :rolledback, nil])
20
7
 
@@ -35,6 +22,10 @@ module ActiveRecord
35
22
  @state == :rolledback
36
23
  end
37
24
 
25
+ def completed?
26
+ committed? || rolledback?
27
+ end
28
+
38
29
  def set_state(state)
39
30
  if !VALID_STATES.include?(state)
40
31
  raise ArgumentError, "Invalid transaction state: #{state}"
@@ -43,82 +34,24 @@ module ActiveRecord
43
34
  end
44
35
  end
45
36
 
46
- class ClosedTransaction < Transaction #:nodoc:
47
- def number
48
- 0
49
- end
50
-
51
- def begin(options = {})
52
- RealTransaction.new(connection, self, options)
53
- end
54
-
55
- def closed?
56
- true
57
- end
58
-
59
- def open?
60
- false
61
- end
62
-
63
- def joinable?
64
- false
65
- end
66
-
67
- # This is a noop when there are no open transactions
68
- def add_record(record)
69
- end
37
+ class NullTransaction #:nodoc:
38
+ def initialize; end
39
+ def closed?; true; end
40
+ def open?; false; end
41
+ def joinable?; false; end
42
+ def add_record(record); end
70
43
  end
71
44
 
72
- class OpenTransaction < Transaction #:nodoc:
73
- attr_reader :parent, :records
74
- attr_writer :joinable
75
-
76
- def initialize(connection, parent, options = {})
77
- super connection
78
-
79
- @parent = parent
80
- @records = []
81
- @finishing = false
82
- @joinable = options.fetch(:joinable, true)
83
- end
84
-
85
- # This state is necessary so that we correctly handle stuff that might
86
- # happen in a commit/rollback. But it's kinda distasteful. Maybe we can
87
- # find a better way to structure it in the future.
88
- def finishing?
89
- @finishing
90
- end
91
-
92
- def joinable?
93
- @joinable && !finishing?
94
- end
95
-
96
- def number
97
- if finishing?
98
- parent.number
99
- else
100
- parent.number + 1
101
- end
102
- end
103
-
104
- def begin(options = {})
105
- if finishing?
106
- parent.begin
107
- else
108
- SavepointTransaction.new(connection, self, options)
109
- end
110
- end
45
+ class Transaction #:nodoc:
111
46
 
112
- def rollback
113
- @finishing = true
114
- perform_rollback
115
- parent
116
- end
47
+ attr_reader :connection, :state, :records, :savepoint_name
48
+ attr_writer :joinable
117
49
 
118
- def commit
119
- @finishing = true
120
- perform_commit
121
- parent
50
+ def initialize(connection, options)
51
+ @connection = connection
52
+ @state = TransactionState.new
53
+ @records = []
54
+ @joinable = options.fetch(:joinable, true)
122
55
  end
123
56
 
124
57
  def add_record(record)
@@ -129,41 +62,82 @@ module ActiveRecord
129
62
  end
130
63
  end
131
64
 
132
- def rollback_records
65
+ def rollback
133
66
  @state.set_state(:rolledback)
134
- records.uniq.each do |record|
67
+ end
68
+
69
+ def rollback_records
70
+ ite = records.uniq
71
+ while record = ite.shift
135
72
  begin
136
- record.rolledback!(parent.closed?)
73
+ record.rolledback! full_rollback?
137
74
  rescue => e
75
+ raise if ActiveRecord::Base.raise_in_transactional_callbacks
138
76
  record.logger.error(e) if record.respond_to?(:logger) && record.logger
139
77
  end
140
78
  end
79
+ ensure
80
+ ite.each do |i|
81
+ i.rolledback!(full_rollback?, false)
82
+ end
141
83
  end
142
84
 
143
- def commit_records
85
+ def commit
144
86
  @state.set_state(:committed)
145
- records.uniq.each do |record|
87
+ end
88
+
89
+ def commit_records
90
+ ite = records.uniq
91
+ while record = ite.shift
146
92
  begin
147
93
  record.committed!
148
94
  rescue => e
95
+ raise if ActiveRecord::Base.raise_in_transactional_callbacks
149
96
  record.logger.error(e) if record.respond_to?(:logger) && record.logger
150
97
  end
151
98
  end
99
+ ensure
100
+ ite.each do |i|
101
+ i.committed!(false)
102
+ end
152
103
  end
153
104
 
154
- def closed?
155
- false
105
+ def full_rollback?; true; end
106
+ def joinable?; @joinable; end
107
+ def closed?; false; end
108
+ def open?; !closed?; end
109
+ end
110
+
111
+ class SavepointTransaction < Transaction
112
+
113
+ def initialize(connection, savepoint_name, options)
114
+ super(connection, options)
115
+ if options[:isolation]
116
+ raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
117
+ end
118
+ connection.create_savepoint(@savepoint_name = savepoint_name)
156
119
  end
157
120
 
158
- def open?
159
- true
121
+ def rollback
122
+ connection.rollback_to_savepoint(savepoint_name)
123
+ super
124
+ rollback_records
160
125
  end
161
- end
162
126
 
163
- class RealTransaction < OpenTransaction #:nodoc:
164
- def initialize(connection, parent, options = {})
127
+ def commit
128
+ connection.release_savepoint(savepoint_name)
165
129
  super
130
+ parent = connection.transaction_manager.current_transaction
131
+ records.each { |r| parent.add_record(r) }
132
+ end
133
+
134
+ def full_rollback?; false; end
135
+ end
136
+
137
+ class RealTransaction < Transaction
166
138
 
139
+ def initialize(connection, options)
140
+ super
167
141
  if options[:isolation]
168
142
  connection.begin_isolated_db_transaction(options[:isolation])
169
143
  else
@@ -171,37 +145,75 @@ module ActiveRecord
171
145
  end
172
146
  end
173
147
 
174
- def perform_rollback
148
+ def rollback
175
149
  connection.rollback_db_transaction
150
+ super
176
151
  rollback_records
177
152
  end
178
153
 
179
- def perform_commit
154
+ def commit
180
155
  connection.commit_db_transaction
156
+ super
181
157
  commit_records
182
158
  end
183
159
  end
184
160
 
185
- class SavepointTransaction < OpenTransaction #:nodoc:
186
- def initialize(connection, parent, options = {})
187
- if options[:isolation]
188
- raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
189
- end
161
+ class TransactionManager #:nodoc:
162
+ def initialize(connection)
163
+ @stack = []
164
+ @connection = connection
165
+ end
190
166
 
191
- super
192
- connection.create_savepoint
167
+ def begin_transaction(options = {})
168
+ transaction =
169
+ if @stack.empty?
170
+ RealTransaction.new(@connection, options)
171
+ else
172
+ SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options)
173
+ end
174
+ @stack.push(transaction)
175
+ transaction
176
+ end
177
+
178
+ def commit_transaction
179
+ @stack.pop.commit
180
+ end
181
+
182
+ def rollback_transaction
183
+ @stack.pop.rollback
184
+ end
185
+
186
+ def within_new_transaction(options = {})
187
+ transaction = begin_transaction options
188
+ yield
189
+ rescue Exception => error
190
+ rollback_transaction if transaction
191
+ raise
192
+ ensure
193
+ unless error
194
+ if Thread.current.status == 'aborting'
195
+ rollback_transaction
196
+ else
197
+ begin
198
+ commit_transaction
199
+ rescue Exception
200
+ transaction.rollback unless transaction.state.completed?
201
+ raise
202
+ end
203
+ end
204
+ end
193
205
  end
194
206
 
195
- def perform_rollback
196
- connection.rollback_to_savepoint
197
- rollback_records
207
+ def open_transactions
208
+ @stack.size
198
209
  end
199
210
 
200
- def perform_commit
201
- @state.set_state(:committed)
202
- @state.parent = parent.state
203
- connection.release_savepoint
211
+ def current_transaction
212
+ @stack.last || NULL_TRANSACTION
204
213
  end
214
+
215
+ private
216
+ NULL_TRANSACTION = NullTransaction.new
205
217
  end
206
218
  end
207
219
  end