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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +776 -1330
- data/README.rdoc +15 -10
- data/lib/active_record/aggregations.rb +12 -8
- data/lib/active_record/association_relation.rb +4 -0
- data/lib/active_record/associations/alias_tracker.rb +14 -13
- data/lib/active_record/associations/association.rb +2 -2
- data/lib/active_record/associations/association_scope.rb +83 -43
- data/lib/active_record/associations/belongs_to_association.rb +15 -5
- data/lib/active_record/associations/builder/association.rb +15 -4
- data/lib/active_record/associations/builder/belongs_to.rb +7 -29
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -6
- data/lib/active_record/associations/builder/has_many.rb +1 -1
- data/lib/active_record/associations/builder/has_one.rb +2 -2
- data/lib/active_record/associations/builder/singular_association.rb +8 -1
- data/lib/active_record/associations/collection_association.rb +66 -29
- data/lib/active_record/associations/collection_proxy.rb +22 -26
- data/lib/active_record/associations/has_many_association.rb +65 -18
- data/lib/active_record/associations/has_many_through_association.rb +55 -27
- data/lib/active_record/associations/has_one_association.rb +0 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +19 -15
- data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
- data/lib/active_record/associations/join_dependency.rb +20 -12
- data/lib/active_record/associations/preloader/association.rb +34 -11
- data/lib/active_record/associations/preloader/through_association.rb +4 -3
- data/lib/active_record/associations/preloader.rb +49 -59
- data/lib/active_record/associations/singular_association.rb +25 -4
- data/lib/active_record/associations/through_association.rb +23 -14
- data/lib/active_record/associations.rb +171 -42
- data/lib/active_record/attribute.rb +149 -0
- data/lib/active_record/attribute_assignment.rb +18 -10
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
- data/lib/active_record/attribute_methods/dirty.rb +98 -44
- data/lib/active_record/attribute_methods/primary_key.rb +14 -8
- data/lib/active_record/attribute_methods/query.rb +1 -1
- data/lib/active_record/attribute_methods/read.rb +22 -59
- data/lib/active_record/attribute_methods/serialization.rb +37 -147
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +34 -28
- data/lib/active_record/attribute_methods/write.rb +14 -21
- data/lib/active_record/attribute_methods.rb +67 -94
- data/lib/active_record/attribute_set/builder.rb +86 -0
- data/lib/active_record/attribute_set.rb +77 -0
- data/lib/active_record/attributes.rb +139 -0
- data/lib/active_record/autosave_association.rb +45 -38
- data/lib/active_record/base.rb +10 -20
- data/lib/active_record/callbacks.rb +7 -7
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +78 -52
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +38 -59
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -55
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +126 -54
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +198 -64
- data/lib/active_record/connection_adapters/abstract/transaction.rb +126 -114
- data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -55
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +240 -135
- data/lib/active_record/connection_adapters/column.rb +28 -239
- data/lib/active_record/connection_adapters/connection_specification.rb +16 -25
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -22
- data/lib/active_record/connection_adapters/mysql_adapter.rb +65 -149
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -27
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -374
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +55 -135
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +127 -38
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +220 -466
- data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -61
- data/lib/active_record/connection_handling.rb +3 -3
- data/lib/active_record/core.rb +143 -32
- data/lib/active_record/counter_cache.rb +60 -7
- data/lib/active_record/enum.rb +10 -11
- data/lib/active_record/errors.rb +49 -27
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/fixtures.rb +56 -70
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +35 -10
- data/lib/active_record/integration.rb +4 -4
- data/lib/active_record/locking/optimistic.rb +35 -17
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +19 -2
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/migration.rb +52 -49
- data/lib/active_record/model_schema.rb +49 -57
- data/lib/active_record/nested_attributes.rb +7 -7
- data/lib/active_record/null_relation.rb +19 -5
- data/lib/active_record/persistence.rb +50 -31
- data/lib/active_record/query_cache.rb +3 -3
- data/lib/active_record/querying.rb +10 -7
- data/lib/active_record/railtie.rb +14 -11
- data/lib/active_record/railties/databases.rake +56 -54
- data/lib/active_record/readonly_attributes.rb +0 -1
- data/lib/active_record/reflection.rb +286 -102
- data/lib/active_record/relation/batches.rb +0 -1
- data/lib/active_record/relation/calculations.rb +39 -31
- data/lib/active_record/relation/delegation.rb +2 -2
- data/lib/active_record/relation/finder_methods.rb +80 -36
- data/lib/active_record/relation/merger.rb +25 -30
- data/lib/active_record/relation/predicate_builder/array_handler.rb +31 -13
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +11 -10
- data/lib/active_record/relation/query_methods.rb +141 -55
- data/lib/active_record/relation/spawn_methods.rb +3 -0
- data/lib/active_record/relation.rb +69 -30
- data/lib/active_record/result.rb +18 -7
- data/lib/active_record/sanitization.rb +12 -2
- data/lib/active_record/schema.rb +0 -1
- data/lib/active_record/schema_dumper.rb +58 -26
- data/lib/active_record/schema_migration.rb +11 -0
- data/lib/active_record/scoping/default.rb +8 -7
- data/lib/active_record/scoping/named.rb +4 -0
- data/lib/active_record/serializers/xml_serializer.rb +3 -7
- data/lib/active_record/statement_cache.rb +95 -10
- data/lib/active_record/store.rb +19 -10
- data/lib/active_record/tasks/database_tasks.rb +73 -7
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -2
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +11 -9
- data/lib/active_record/transactions.rb +37 -21
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +30 -0
- data/lib/active_record/type/date.rb +46 -0
- data/lib/active_record/type/date_time.rb +43 -0
- data/lib/active_record/type/decimal.rb +40 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
- data/lib/active_record/type/integer.rb +55 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +56 -0
- data/lib/active_record/type/string.rb +36 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +101 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +5 -3
- data/lib/active_record/validations/presence.rb +6 -4
- data/lib/active_record/validations/uniqueness.rb +11 -17
- data/lib/active_record/validations.rb +25 -19
- data/lib/active_record.rb +3 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +4 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +6 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
- metadata +65 -10
- 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, :
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
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
|
-
|
191
|
-
|
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
|
-
|
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
|
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
|
-
#
|
364
|
-
# to
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
727
|
-
add_column table_name, :
|
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
|
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
|
-
|
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
|
47
|
-
def
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
def
|
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
|
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
|
-
|
113
|
-
|
114
|
-
perform_rollback
|
115
|
-
parent
|
116
|
-
end
|
47
|
+
attr_reader :connection, :state, :records, :savepoint_name
|
48
|
+
attr_writer :joinable
|
117
49
|
|
118
|
-
def
|
119
|
-
@
|
120
|
-
|
121
|
-
|
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
|
65
|
+
def rollback
|
133
66
|
@state.set_state(:rolledback)
|
134
|
-
|
67
|
+
end
|
68
|
+
|
69
|
+
def rollback_records
|
70
|
+
ite = records.uniq
|
71
|
+
while record = ite.shift
|
135
72
|
begin
|
136
|
-
record.rolledback!
|
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
|
85
|
+
def commit
|
144
86
|
@state.set_state(:committed)
|
145
|
-
|
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
|
155
|
-
|
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
|
159
|
-
|
121
|
+
def rollback
|
122
|
+
connection.rollback_to_savepoint(savepoint_name)
|
123
|
+
super
|
124
|
+
rollback_records
|
160
125
|
end
|
161
|
-
end
|
162
126
|
|
163
|
-
|
164
|
-
|
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
|
148
|
+
def rollback
|
175
149
|
connection.rollback_db_transaction
|
150
|
+
super
|
176
151
|
rollback_records
|
177
152
|
end
|
178
153
|
|
179
|
-
def
|
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
|
186
|
-
def initialize(connection
|
187
|
-
|
188
|
-
|
189
|
-
|
161
|
+
class TransactionManager #:nodoc:
|
162
|
+
def initialize(connection)
|
163
|
+
@stack = []
|
164
|
+
@connection = connection
|
165
|
+
end
|
190
166
|
|
191
|
-
|
192
|
-
|
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
|
196
|
-
|
197
|
-
rollback_records
|
207
|
+
def open_transactions
|
208
|
+
@stack.size
|
198
209
|
end
|
199
210
|
|
200
|
-
def
|
201
|
-
@
|
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
|