activerecord 4.1.15 → 4.2.11.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +1162 -1792
- data/README.rdoc +15 -10
- data/lib/active_record.rb +4 -0
- data/lib/active_record/aggregations.rb +15 -8
- data/lib/active_record/association_relation.rb +13 -0
- data/lib/active_record/associations.rb +158 -49
- data/lib/active_record/associations/alias_tracker.rb +3 -12
- data/lib/active_record/associations/association.rb +16 -4
- data/lib/active_record/associations/association_scope.rb +83 -38
- data/lib/active_record/associations/belongs_to_association.rb +28 -10
- 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/collection_association.rb +5 -1
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -13
- 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 +63 -27
- data/lib/active_record/associations/collection_proxy.rb +29 -35
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +83 -22
- data/lib/active_record/associations/has_many_through_association.rb +49 -26
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +26 -13
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
- data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
- data/lib/active_record/associations/preloader.rb +36 -26
- data/lib/active_record/associations/preloader/association.rb +14 -11
- data/lib/active_record/associations/preloader/through_association.rb +4 -3
- data/lib/active_record/associations/singular_association.rb +17 -2
- data/lib/active_record/associations/through_association.rb +5 -12
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +19 -11
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods.rb +56 -94
- data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
- data/lib/active_record/attribute_methods/dirty.rb +107 -43
- data/lib/active_record/attribute_methods/primary_key.rb +7 -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 +16 -150
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -40
- data/lib/active_record/attribute_methods/write.rb +9 -24
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +19 -12
- data/lib/active_record/base.rb +13 -24
- data/lib/active_record/callbacks.rb +6 -6
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +84 -52
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +138 -56
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +268 -71
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
- data/lib/active_record/connection_adapters/abstract_adapter.rb +171 -59
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +293 -139
- data/lib/active_record/connection_adapters/column.rb +29 -240
- data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
- data/lib/active_record/connection_adapters/mysql_adapter.rb +67 -144
- 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 +40 -25
- data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -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 +15 -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 +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -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 +19 -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 +109 -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/quoting.rb +46 -136
- 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 +131 -43
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
- data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
- data/lib/active_record/connection_handling.rb +1 -1
- data/lib/active_record/core.rb +163 -39
- data/lib/active_record/counter_cache.rb +60 -6
- data/lib/active_record/enum.rb +9 -11
- data/lib/active_record/errors.rb +53 -30
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixtures.rb +55 -69
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +35 -10
- data/lib/active_record/integration.rb +4 -4
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locking/optimistic.rb +46 -26
- data/lib/active_record/migration.rb +71 -46
- data/lib/active_record/migration/command_recorder.rb +19 -2
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/model_schema.rb +52 -58
- data/lib/active_record/nested_attributes.rb +5 -5
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/persistence.rb +46 -26
- data/lib/active_record/query_cache.rb +3 -3
- data/lib/active_record/querying.rb +10 -7
- data/lib/active_record/railtie.rb +18 -11
- data/lib/active_record/railties/databases.rake +50 -51
- data/lib/active_record/readonly_attributes.rb +0 -1
- data/lib/active_record/reflection.rb +273 -114
- data/lib/active_record/relation.rb +57 -25
- data/lib/active_record/relation/batches.rb +0 -2
- data/lib/active_record/relation/calculations.rb +41 -37
- data/lib/active_record/relation/finder_methods.rb +70 -47
- data/lib/active_record/relation/merger.rb +39 -29
- data/lib/active_record/relation/predicate_builder.rb +16 -8
- data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -5
- data/lib/active_record/relation/query_methods.rb +114 -65
- data/lib/active_record/relation/spawn_methods.rb +3 -0
- 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 +59 -28
- data/lib/active_record/schema_migration.rb +5 -4
- data/lib/active_record/scoping/default.rb +6 -4
- 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 +5 -5
- data/lib/active_record/tasks/database_tasks.rb +61 -6
- data/lib/active_record/tasks/mysql_database_tasks.rb +32 -17
- data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
- data/lib/active_record/timestamp.rb +9 -7
- data/lib/active_record/transactions.rb +53 -27
- data/lib/active_record/type.rb +23 -0
- 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 +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -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 +23 -0
- data/lib/active_record/type/integer.rb +59 -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 +62 -0
- data/lib/active_record/type/string.rb +40 -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 +110 -0
- data/lib/active_record/validations.rb +25 -19
- data/lib/active_record/validations/associated.rb +5 -3
- data/lib/active_record/validations/presence.rb +5 -3
- data/lib/active_record/validations/uniqueness.rb +25 -29
- data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
- metadata +66 -11
- data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -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
|
@@ -10,7 +8,7 @@ module ActiveRecord
|
|
10
8
|
module ColumnDumper
|
11
9
|
def column_spec(column, types)
|
12
10
|
spec = prepare_column_options(column, types)
|
13
|
-
(spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k
|
11
|
+
(spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k}: ")}
|
14
12
|
spec
|
15
13
|
end
|
16
14
|
|
@@ -20,19 +18,17 @@ module ActiveRecord
|
|
20
18
|
def prepare_column_options(column, types)
|
21
19
|
spec = {}
|
22
20
|
spec[:name] = column.name.inspect
|
21
|
+
spec[:type] = column.type.to_s
|
22
|
+
spec[:null] = 'false' unless column.null
|
23
23
|
|
24
|
-
|
25
|
-
|
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'
|
24
|
+
limit = column.limit || types[column.type][:limit]
|
25
|
+
spec[:limit] = limit.inspect if limit
|
32
26
|
spec[:precision] = column.precision.inspect if column.precision
|
33
27
|
spec[:scale] = column.scale.inspect if column.scale
|
34
|
-
|
35
|
-
|
28
|
+
|
29
|
+
default = schema_default(column) if column.has_default?
|
30
|
+
spec[:default] = default unless default.nil?
|
31
|
+
|
36
32
|
spec
|
37
33
|
end
|
38
34
|
|
@@ -43,28 +39,12 @@ module ActiveRecord
|
|
43
39
|
|
44
40
|
private
|
45
41
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
42
|
+
def schema_default(column)
|
43
|
+
default = column.type_cast_from_database(column.default)
|
44
|
+
unless default.nil?
|
45
|
+
column.type_cast_for_schema(default)
|
67
46
|
end
|
47
|
+
end
|
68
48
|
end
|
69
49
|
end
|
70
50
|
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'active_record/migration/join_table'
|
2
|
+
require 'active_support/core_ext/string/access'
|
3
|
+
require 'digest'
|
2
4
|
|
3
5
|
module ActiveRecord
|
4
6
|
module ConnectionAdapters # :nodoc:
|
@@ -17,6 +19,20 @@ module ActiveRecord
|
|
17
19
|
table_name[0...table_alias_length].tr('.', '_')
|
18
20
|
end
|
19
21
|
|
22
|
+
# Returns the relation names useable to back Active Record models.
|
23
|
+
# For most adapters this means all tables and views.
|
24
|
+
def data_sources
|
25
|
+
tables
|
26
|
+
end
|
27
|
+
|
28
|
+
# Checks to see if the data source +name+ exists on the database.
|
29
|
+
#
|
30
|
+
# data_source_exists?(:ebooks)
|
31
|
+
#
|
32
|
+
def data_source_exists?(name)
|
33
|
+
data_sources.include?(name.to_s)
|
34
|
+
end
|
35
|
+
|
20
36
|
# Checks to see if the table +table_name+ exists on the database.
|
21
37
|
#
|
22
38
|
# table_exists?(:developers)
|
@@ -43,13 +59,14 @@ module ActiveRecord
|
|
43
59
|
# index_exists?(:suppliers, :company_id, name: "idx_company_id")
|
44
60
|
#
|
45
61
|
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
|
-
|
62
|
+
column_names = Array(column_name).map(&:to_s)
|
63
|
+
index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, column: column_names)
|
64
|
+
checks = []
|
65
|
+
checks << lambda { |i| i.name == index_name }
|
66
|
+
checks << lambda { |i| i.columns == column_names }
|
67
|
+
checks << lambda { |i| i.unique } if options[:unique]
|
68
|
+
|
69
|
+
indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
|
53
70
|
end
|
54
71
|
|
55
72
|
# Returns an array of Column objects for the table specified by +table_name+.
|
@@ -71,7 +88,8 @@ module ActiveRecord
|
|
71
88
|
# column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
|
72
89
|
#
|
73
90
|
def column_exists?(table_name, column_name, type = nil, options = {})
|
74
|
-
|
91
|
+
column_name = column_name.to_s
|
92
|
+
columns(table_name).any?{ |c| c.name == column_name &&
|
75
93
|
(!type || c.type == type) &&
|
76
94
|
(!options.key?(:limit) || c.limit == options[:limit]) &&
|
77
95
|
(!options.key?(:precision) || c.precision == options[:precision]) &&
|
@@ -130,6 +148,7 @@ module ActiveRecord
|
|
130
148
|
# Make a temporary table.
|
131
149
|
# [<tt>:force</tt>]
|
132
150
|
# Set to true to drop the table before creating it.
|
151
|
+
# Set to +:cascade+ to drop dependent objects as well.
|
133
152
|
# Defaults to false.
|
134
153
|
# [<tt>:as</tt>]
|
135
154
|
# SQL to use to generate the table. When this option is used, the block is
|
@@ -186,24 +205,33 @@ module ActiveRecord
|
|
186
205
|
def create_table(table_name, options = {})
|
187
206
|
td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
|
188
207
|
|
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
|
208
|
+
if options[:id] != false && !options[:as]
|
209
|
+
pk = options.fetch(:primary_key) do
|
210
|
+
Base.get_primary_key table_name.to_s.singularize
|
196
211
|
end
|
197
212
|
|
198
|
-
|
213
|
+
td.primary_key pk, options.fetch(:id, :primary_key), options
|
199
214
|
end
|
200
215
|
|
216
|
+
yield td if block_given?
|
217
|
+
|
201
218
|
if options[:force] && table_exists?(table_name)
|
202
219
|
drop_table(table_name, options)
|
203
220
|
end
|
204
221
|
|
205
|
-
execute schema_creation.accept td
|
206
|
-
|
222
|
+
result = execute schema_creation.accept td
|
223
|
+
|
224
|
+
unless supports_indexes_in_create?
|
225
|
+
td.indexes.each_pair do |column_name, index_options|
|
226
|
+
add_index(table_name, column_name, index_options)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
td.foreign_keys.each do |other_table_name, foreign_key_options|
|
231
|
+
add_foreign_key(table_name, other_table_name, foreign_key_options)
|
232
|
+
end
|
233
|
+
|
234
|
+
result
|
207
235
|
end
|
208
236
|
|
209
237
|
# Creates a new join table with the name created using the lexical order of the first two
|
@@ -360,8 +388,12 @@ module ActiveRecord
|
|
360
388
|
|
361
389
|
# Drops a table from the database.
|
362
390
|
#
|
363
|
-
#
|
364
|
-
# to
|
391
|
+
# [<tt>:force</tt>]
|
392
|
+
# Set to +:cascade+ to drop dependent objects as well.
|
393
|
+
# Defaults to false.
|
394
|
+
#
|
395
|
+
# Although this command ignores most +options+ and the block if one is given,
|
396
|
+
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
|
365
397
|
# In that case, +options+ and the block will be used by create_table.
|
366
398
|
def drop_table(table_name, options = {})
|
367
399
|
execute "DROP TABLE #{quote_table_name(table_name)}"
|
@@ -512,6 +544,8 @@ module ActiveRecord
|
|
512
544
|
#
|
513
545
|
# CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
|
514
546
|
#
|
547
|
+
# Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
|
548
|
+
#
|
515
549
|
# ====== Creating an index with a specific method
|
516
550
|
#
|
517
551
|
# add_index(:developers, :name, using: 'btree')
|
@@ -570,6 +604,8 @@ module ActiveRecord
|
|
570
604
|
# rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
|
571
605
|
#
|
572
606
|
def rename_index(table_name, old_name, new_name)
|
607
|
+
validate_index_length!(table_name, new_name)
|
608
|
+
|
573
609
|
# this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
|
574
610
|
old_index_def = indexes(table_name).detect { |i| i.name == old_name }
|
575
611
|
return unless old_index_def
|
@@ -601,27 +637,50 @@ module ActiveRecord
|
|
601
637
|
indexes(table_name).detect { |i| i.name == index_name }
|
602
638
|
end
|
603
639
|
|
604
|
-
# Adds a reference.
|
640
|
+
# Adds a reference. The reference column is an integer by default,
|
641
|
+
# the <tt>:type</tt> option can be used to specify a different type.
|
642
|
+
# Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
|
605
643
|
# <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
|
606
644
|
#
|
607
|
-
#
|
645
|
+
# The +options+ hash can include the following keys:
|
646
|
+
# [<tt>:type</tt>]
|
647
|
+
# The reference column type. Defaults to +:integer+.
|
648
|
+
# [<tt>:index</tt>]
|
649
|
+
# Add an appropriate index. Defaults to false.
|
650
|
+
# [<tt>:foreign_key</tt>]
|
651
|
+
# Add an appropriate foreign key. Defaults to false.
|
652
|
+
# [<tt>:polymorphic</tt>]
|
653
|
+
# Wether an additional +_type+ column should be added. Defaults to false.
|
654
|
+
#
|
655
|
+
# ====== Create a user_id integer column
|
608
656
|
#
|
609
657
|
# add_reference(:products, :user)
|
610
658
|
#
|
611
|
-
# ====== Create a
|
659
|
+
# ====== Create a user_id string column
|
612
660
|
#
|
613
|
-
#
|
661
|
+
# add_reference(:products, :user, type: :string)
|
614
662
|
#
|
615
|
-
# ====== Create
|
663
|
+
# ====== Create supplier_id, supplier_type columns and appropriate index
|
616
664
|
#
|
617
665
|
# add_reference(:products, :supplier, polymorphic: true, index: true)
|
618
666
|
#
|
619
667
|
def add_reference(table_name, ref_name, options = {})
|
620
668
|
polymorphic = options.delete(:polymorphic)
|
621
669
|
index_options = options.delete(:index)
|
622
|
-
|
670
|
+
type = options.delete(:type) || :integer
|
671
|
+
foreign_key_options = options.delete(:foreign_key)
|
672
|
+
|
673
|
+
if polymorphic && foreign_key_options
|
674
|
+
raise ArgumentError, "Cannot add a foreign key to a polymorphic relation"
|
675
|
+
end
|
676
|
+
|
677
|
+
add_column(table_name, "#{ref_name}_id", type, options)
|
623
678
|
add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
|
624
|
-
add_index(table_name, polymorphic ? %w[id
|
679
|
+
add_index(table_name, polymorphic ? %w[type id].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
|
680
|
+
if foreign_key_options
|
681
|
+
to_table = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
|
682
|
+
add_foreign_key(table_name, to_table, foreign_key_options.is_a?(Hash) ? foreign_key_options : {})
|
683
|
+
end
|
625
684
|
end
|
626
685
|
alias :add_belongs_to :add_reference
|
627
686
|
|
@@ -636,12 +695,133 @@ module ActiveRecord
|
|
636
695
|
#
|
637
696
|
# remove_reference(:products, :supplier, polymorphic: true)
|
638
697
|
#
|
698
|
+
# ====== Remove the reference with a foreign key
|
699
|
+
#
|
700
|
+
# remove_reference(:products, :user, index: true, foreign_key: true)
|
701
|
+
#
|
639
702
|
def remove_reference(table_name, ref_name, options = {})
|
703
|
+
if options[:foreign_key]
|
704
|
+
to_table = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
|
705
|
+
remove_foreign_key(table_name, to_table)
|
706
|
+
end
|
707
|
+
|
640
708
|
remove_column(table_name, "#{ref_name}_id")
|
641
709
|
remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
|
642
710
|
end
|
643
711
|
alias :remove_belongs_to :remove_reference
|
644
712
|
|
713
|
+
# Returns an array of foreign keys for the given table.
|
714
|
+
# The foreign keys are represented as +ForeignKeyDefinition+ objects.
|
715
|
+
def foreign_keys(table_name)
|
716
|
+
raise NotImplementedError, "foreign_keys is not implemented"
|
717
|
+
end
|
718
|
+
|
719
|
+
# Adds a new foreign key. +from_table+ is the table with the key column,
|
720
|
+
# +to_table+ contains the referenced primary key.
|
721
|
+
#
|
722
|
+
# The foreign key will be named after the following pattern: <tt>fk_rails_<identifier></tt>.
|
723
|
+
# +identifier+ is a 10 character long string which is deterministically generated from the
|
724
|
+
# +from_table+ and +column+. A custom name can be specified with the <tt>:name</tt> option.
|
725
|
+
#
|
726
|
+
# ====== Creating a simple foreign key
|
727
|
+
#
|
728
|
+
# add_foreign_key :articles, :authors
|
729
|
+
#
|
730
|
+
# generates:
|
731
|
+
#
|
732
|
+
# ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
|
733
|
+
#
|
734
|
+
# ====== Creating a foreign key on a specific column
|
735
|
+
#
|
736
|
+
# add_foreign_key :articles, :users, column: :author_id, primary_key: :lng_id
|
737
|
+
#
|
738
|
+
# generates:
|
739
|
+
#
|
740
|
+
# ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
|
741
|
+
#
|
742
|
+
# ====== Creating a cascading foreign key
|
743
|
+
#
|
744
|
+
# add_foreign_key :articles, :authors, on_delete: :cascade
|
745
|
+
#
|
746
|
+
# generates:
|
747
|
+
#
|
748
|
+
# ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
|
749
|
+
#
|
750
|
+
# The +options+ hash can include the following keys:
|
751
|
+
# [<tt>:column</tt>]
|
752
|
+
# The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>
|
753
|
+
# [<tt>:primary_key</tt>]
|
754
|
+
# The primary key column name on +to_table+. Defaults to +id+.
|
755
|
+
# [<tt>:name</tt>]
|
756
|
+
# The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
|
757
|
+
# [<tt>:on_delete</tt>]
|
758
|
+
# Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
|
759
|
+
# [<tt>:on_update</tt>]
|
760
|
+
# Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
|
761
|
+
def add_foreign_key(from_table, to_table, options = {})
|
762
|
+
return unless supports_foreign_keys?
|
763
|
+
|
764
|
+
options[:column] ||= foreign_key_column_for(to_table)
|
765
|
+
|
766
|
+
options = {
|
767
|
+
column: options[:column],
|
768
|
+
primary_key: options[:primary_key],
|
769
|
+
name: foreign_key_name(from_table, options),
|
770
|
+
on_delete: options[:on_delete],
|
771
|
+
on_update: options[:on_update]
|
772
|
+
}
|
773
|
+
at = create_alter_table from_table
|
774
|
+
at.add_foreign_key to_table, options
|
775
|
+
|
776
|
+
execute schema_creation.accept(at)
|
777
|
+
end
|
778
|
+
|
779
|
+
# Removes the given foreign key from the table.
|
780
|
+
#
|
781
|
+
# Removes the foreign key on +accounts.branch_id+.
|
782
|
+
#
|
783
|
+
# remove_foreign_key :accounts, :branches
|
784
|
+
#
|
785
|
+
# Removes the foreign key on +accounts.owner_id+.
|
786
|
+
#
|
787
|
+
# remove_foreign_key :accounts, column: :owner_id
|
788
|
+
#
|
789
|
+
# Removes the foreign key named +special_fk_name+ on the +accounts+ table.
|
790
|
+
#
|
791
|
+
# remove_foreign_key :accounts, name: :special_fk_name
|
792
|
+
#
|
793
|
+
def remove_foreign_key(from_table, options_or_to_table = {})
|
794
|
+
return unless supports_foreign_keys?
|
795
|
+
|
796
|
+
if options_or_to_table.is_a?(Hash)
|
797
|
+
options = options_or_to_table
|
798
|
+
else
|
799
|
+
options = { column: foreign_key_column_for(options_or_to_table) }
|
800
|
+
end
|
801
|
+
|
802
|
+
fk_name_to_delete = options.fetch(:name) do
|
803
|
+
fk_to_delete = foreign_keys(from_table).detect {|fk| fk.column == options[:column].to_s }
|
804
|
+
|
805
|
+
if fk_to_delete
|
806
|
+
fk_to_delete.name
|
807
|
+
else
|
808
|
+
raise ArgumentError, "Table '#{from_table}' has no foreign key on column '#{options[:column]}'"
|
809
|
+
end
|
810
|
+
end
|
811
|
+
|
812
|
+
at = create_alter_table from_table
|
813
|
+
at.drop_foreign_key fk_name_to_delete
|
814
|
+
|
815
|
+
execute schema_creation.accept(at)
|
816
|
+
end
|
817
|
+
|
818
|
+
def foreign_key_column_for(table_name) # :nodoc:
|
819
|
+
prefix = Base.table_name_prefix
|
820
|
+
suffix = Base.table_name_suffix
|
821
|
+
name = table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s
|
822
|
+
"#{name.singularize}_id"
|
823
|
+
end
|
824
|
+
|
645
825
|
def dump_schema_information #:nodoc:
|
646
826
|
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
|
647
827
|
|
@@ -661,10 +841,9 @@ module ActiveRecord
|
|
661
841
|
version = version.to_i
|
662
842
|
sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
|
663
843
|
|
664
|
-
migrated = select_values("SELECT version FROM #{sm_table}").map
|
665
|
-
|
666
|
-
|
667
|
-
filename.split('/').last.split('_').first.to_i
|
844
|
+
migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
|
845
|
+
versions = ActiveRecord::Migrator.migration_files(migrations_paths).map do |file|
|
846
|
+
ActiveRecord::Migrator.parse_migration_filename(file).first.to_i
|
668
847
|
end
|
669
848
|
|
670
849
|
unless migrated.include?(version)
|
@@ -710,19 +889,23 @@ module ActiveRecord
|
|
710
889
|
end
|
711
890
|
|
712
891
|
# Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
|
713
|
-
#
|
892
|
+
# PostgreSQL, MySQL, and Oracle overrides this for custom DISTINCT syntax - they
|
714
893
|
# require the order columns appear in the SELECT.
|
715
894
|
#
|
716
895
|
# columns_for_distinct("posts.id", ["posts.created_at desc"])
|
717
|
-
|
896
|
+
#
|
897
|
+
def columns_for_distinct(columns, orders) # :nodoc:
|
718
898
|
columns
|
719
899
|
end
|
720
900
|
|
721
|
-
|
901
|
+
include TimestampDefaultDeprecation
|
902
|
+
# Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
|
903
|
+
# Additional options (like <tt>null: false</tt>) are forwarded to #add_column.
|
722
904
|
#
|
723
|
-
# add_timestamps(:suppliers)
|
905
|
+
# add_timestamps(:suppliers, null: false)
|
724
906
|
#
|
725
907
|
def add_timestamps(table_name, options = {})
|
908
|
+
emit_warning_if_null_unspecified(:add_timestamps, options)
|
726
909
|
add_column table_name, :created_at, :datetime, options
|
727
910
|
add_column table_name, :updated_at, :datetime, options
|
728
911
|
end
|
@@ -731,7 +914,7 @@ module ActiveRecord
|
|
731
914
|
#
|
732
915
|
# remove_timestamps(:suppliers)
|
733
916
|
#
|
734
|
-
def remove_timestamps(table_name)
|
917
|
+
def remove_timestamps(table_name, options = {})
|
735
918
|
remove_column table_name, :updated_at
|
736
919
|
remove_column table_name, :created_at
|
737
920
|
end
|
@@ -740,6 +923,40 @@ module ActiveRecord
|
|
740
923
|
Table.new(table_name, base)
|
741
924
|
end
|
742
925
|
|
926
|
+
def add_index_options(table_name, column_name, options = {}) #:nodoc:
|
927
|
+
column_names = Array(column_name)
|
928
|
+
index_name = index_name(table_name, column: column_names)
|
929
|
+
|
930
|
+
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
|
931
|
+
|
932
|
+
index_type = options[:unique] ? "UNIQUE" : ""
|
933
|
+
index_type = options[:type].to_s if options.key?(:type)
|
934
|
+
index_name = options[:name].to_s if options.key?(:name)
|
935
|
+
max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
|
936
|
+
|
937
|
+
if options.key?(:algorithm)
|
938
|
+
algorithm = index_algorithms.fetch(options[:algorithm]) {
|
939
|
+
raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
|
940
|
+
}
|
941
|
+
end
|
942
|
+
|
943
|
+
using = "USING #{options[:using]}" if options[:using].present?
|
944
|
+
|
945
|
+
if supports_partial_index?
|
946
|
+
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
|
947
|
+
end
|
948
|
+
|
949
|
+
if index_name.length > max_index_length
|
950
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
|
951
|
+
end
|
952
|
+
if table_exists?(table_name) && index_name_exists?(table_name, index_name, false)
|
953
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
|
954
|
+
end
|
955
|
+
index_columns = quoted_columns_for_index(column_names, options).join(", ")
|
956
|
+
|
957
|
+
[index_name, index_type, index_columns, index_options, algorithm, using]
|
958
|
+
end
|
959
|
+
|
743
960
|
protected
|
744
961
|
def add_index_sort_order(option_strings, column_names, options = {})
|
745
962
|
if options.is_a?(Hash) && order = options[:order]
|
@@ -754,7 +971,7 @@ module ActiveRecord
|
|
754
971
|
return option_strings
|
755
972
|
end
|
756
973
|
|
757
|
-
# Overridden by the
|
974
|
+
# Overridden by the MySQL adapter for supporting index lengths
|
758
975
|
def quoted_columns_for_index(column_names, options = {})
|
759
976
|
option_strings = Hash[column_names.map {|name| [name, '']}]
|
760
977
|
|
@@ -770,40 +987,6 @@ module ActiveRecord
|
|
770
987
|
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
|
771
988
|
end
|
772
989
|
|
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
990
|
def index_name_for_remove(table_name, options = {})
|
808
991
|
index_name = index_name(table_name, options)
|
809
992
|
|
@@ -852,6 +1035,20 @@ module ActiveRecord
|
|
852
1035
|
def create_alter_table(name)
|
853
1036
|
AlterTable.new create_table_definition(name, false, {})
|
854
1037
|
end
|
1038
|
+
|
1039
|
+
def foreign_key_name(table_name, options) # :nodoc:
|
1040
|
+
identifier = "#{table_name}_#{options.fetch(:column)}_fk"
|
1041
|
+
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
|
1042
|
+
options.fetch(:name) do
|
1043
|
+
"fk_rails_#{hashed_identifier}"
|
1044
|
+
end
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
def validate_index_length!(table_name, new_name)
|
1048
|
+
if new_name.length > allowed_index_name_length
|
1049
|
+
raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
|
1050
|
+
end
|
1051
|
+
end
|
855
1052
|
end
|
856
1053
|
end
|
857
1054
|
end
|