activerecord 5.1.0.beta1 → 5.1.0.rc1
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 +93 -6
- data/lib/active_record/associations.rb +4 -0
- data/lib/active_record/associations/association_scope.rb +8 -8
- data/lib/active_record/associations/belongs_to_association.rb +4 -0
- data/lib/active_record/associations/builder/belongs_to.rb +8 -1
- data/lib/active_record/associations/collection_proxy.rb +5 -4
- data/lib/active_record/associations/join_dependency.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +4 -23
- data/lib/active_record/attribute_methods/dirty.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/quoting.rb +20 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +3 -7
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +30 -16
- data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -5
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +16 -80
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +33 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +56 -96
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +4 -12
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +4 -51
- data/lib/active_record/core.rb +0 -1
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/locking/optimistic.rb +2 -6
- data/lib/active_record/migration.rb +32 -17
- data/lib/active_record/null_relation.rb +1 -1
- data/lib/active_record/querying.rb +1 -1
- data/lib/active_record/railties/databases.rake +3 -19
- data/lib/active_record/reflection.rb +67 -16
- data/lib/active_record/relation.rb +0 -4
- data/lib/active_record/relation/calculations.rb +7 -10
- data/lib/active_record/relation/delegation.rb +2 -2
- data/lib/active_record/relation/finder_methods.rb +102 -100
- data/lib/active_record/relation/query_methods.rb +6 -1
- data/lib/active_record/result.rb +12 -1
- data/lib/active_record/sanitization.rb +1 -2
- data/lib/active_record/schema_dumper.rb +1 -1
- data/lib/active_record/schema_migration.rb +5 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -0
- data/lib/active_record/transactions.rb +1 -1
- data/lib/active_record/type/decimal_without_scale.rb +4 -0
- data/lib/active_record/type/serialized.rb +2 -0
- data/lib/rails/generators/active_record/migration.rb +1 -1
- metadata +8 -6
@@ -37,11 +37,8 @@ module ActiveRecord
|
|
37
37
|
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
|
38
38
|
# between databases. In invalid cases, an error from the database is thrown.
|
39
39
|
def count(column_name = nil)
|
40
|
-
if block_given?
|
41
|
-
|
42
|
-
else
|
43
|
-
calculate(:count, column_name)
|
44
|
-
end
|
40
|
+
return super() if block_given?
|
41
|
+
calculate(:count, column_name)
|
45
42
|
end
|
46
43
|
|
47
44
|
# Calculates the average value on a given column. Returns +nil+ if there's
|
@@ -75,8 +72,8 @@ module ActiveRecord
|
|
75
72
|
# #calculate for examples with options.
|
76
73
|
#
|
77
74
|
# Person.sum(:age) # => 4562
|
78
|
-
def sum(column_name = nil
|
79
|
-
return super(
|
75
|
+
def sum(column_name = nil)
|
76
|
+
return super() if block_given?
|
80
77
|
calculate(:sum, column_name)
|
81
78
|
end
|
82
79
|
|
@@ -232,7 +229,7 @@ module ActiveRecord
|
|
232
229
|
query_builder = build_count_subquery(spawn, column_name, distinct)
|
233
230
|
else
|
234
231
|
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
235
|
-
relation = unscope(:order)
|
232
|
+
relation = unscope(:order).distinct!(false)
|
236
233
|
|
237
234
|
column = aggregate_column(column_name)
|
238
235
|
|
@@ -282,7 +279,7 @@ module ActiveRecord
|
|
282
279
|
operation,
|
283
280
|
distinct).as(aggregate_alias)
|
284
281
|
]
|
285
|
-
select_values += select_values unless having_clause.empty?
|
282
|
+
select_values += self.select_values unless having_clause.empty?
|
286
283
|
|
287
284
|
select_values.concat group_columns.map { |aliaz, field|
|
288
285
|
if field.respond_to?(:as)
|
@@ -292,7 +289,7 @@ module ActiveRecord
|
|
292
289
|
end
|
293
290
|
}
|
294
291
|
|
295
|
-
relation = except(:group)
|
292
|
+
relation = except(:group).distinct!(false)
|
296
293
|
relation.group_values = group_fields
|
297
294
|
relation.select_values = select_values
|
298
295
|
|
@@ -36,9 +36,9 @@ module ActiveRecord
|
|
36
36
|
# may vary depending on the klass of a relation, so we create a subclass of Relation
|
37
37
|
# for each different klass, and the delegations are compiled into that subclass only.
|
38
38
|
|
39
|
-
delegate :to_xml, :encode_with, :length, :
|
39
|
+
delegate :to_xml, :encode_with, :length, :each, :uniq, :to_ary, :join,
|
40
40
|
:[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
|
41
|
-
:to_sentence, :to_formatted_s,
|
41
|
+
:to_sentence, :to_formatted_s, :as_json,
|
42
42
|
:shuffle, :split, :index, to: :records
|
43
43
|
|
44
44
|
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
|
@@ -147,7 +147,7 @@ module ActiveRecord
|
|
147
147
|
def last(limit = nil)
|
148
148
|
return find_last(limit) if loaded? || limit_value
|
149
149
|
|
150
|
-
result = limit(limit
|
150
|
+
result = limit(limit)
|
151
151
|
result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key
|
152
152
|
result = result.reverse_order!
|
153
153
|
|
@@ -430,140 +430,142 @@ module ActiveRecord
|
|
430
430
|
reflections.none?(&:collection?)
|
431
431
|
end
|
432
432
|
|
433
|
-
|
433
|
+
def find_with_ids(*ids)
|
434
|
+
raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
|
434
435
|
|
435
|
-
|
436
|
-
|
436
|
+
expects_array = ids.first.kind_of?(Array)
|
437
|
+
return ids.first if expects_array && ids.first.empty?
|
437
438
|
|
438
|
-
|
439
|
-
return ids.first if expects_array && ids.first.empty?
|
439
|
+
ids = ids.flatten.compact.uniq
|
440
440
|
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
else
|
450
|
-
find_some(ids)
|
451
|
-
end
|
452
|
-
rescue ::RangeError
|
453
|
-
raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
|
441
|
+
case ids.size
|
442
|
+
when 0
|
443
|
+
raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
|
444
|
+
when 1
|
445
|
+
result = find_one(ids.first)
|
446
|
+
expects_array ? [ result ] : result
|
447
|
+
else
|
448
|
+
find_some(ids)
|
454
449
|
end
|
450
|
+
rescue ::RangeError
|
451
|
+
raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
|
452
|
+
end
|
455
453
|
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
end
|
463
|
-
|
464
|
-
relation = where(primary_key => id)
|
465
|
-
record = relation.take
|
466
|
-
|
467
|
-
raise_record_not_found_exception!(id, 0, 1) unless record
|
468
|
-
|
469
|
-
record
|
454
|
+
def find_one(id)
|
455
|
+
if ActiveRecord::Base === id
|
456
|
+
raise ArgumentError, <<-MSG.squish
|
457
|
+
You are passing an instance of ActiveRecord::Base to `find`.
|
458
|
+
Please pass the id of the object by calling `.id`.
|
459
|
+
MSG
|
470
460
|
end
|
471
461
|
|
472
|
-
|
473
|
-
|
462
|
+
relation = where(primary_key => id)
|
463
|
+
record = relation.take
|
474
464
|
|
475
|
-
|
465
|
+
raise_record_not_found_exception!(id, 0, 1) unless record
|
476
466
|
|
477
|
-
|
478
|
-
|
479
|
-
limit_value
|
480
|
-
else
|
481
|
-
ids.size
|
482
|
-
end
|
467
|
+
record
|
468
|
+
end
|
483
469
|
|
484
|
-
|
485
|
-
|
486
|
-
expected_size = ids.size - offset_value
|
487
|
-
end
|
470
|
+
def find_some(ids)
|
471
|
+
return find_some_ordered(ids) unless order_values.present?
|
488
472
|
|
489
|
-
|
490
|
-
|
473
|
+
result = where(primary_key => ids).to_a
|
474
|
+
|
475
|
+
expected_size =
|
476
|
+
if limit_value && ids.size > limit_value
|
477
|
+
limit_value
|
491
478
|
else
|
492
|
-
|
479
|
+
ids.size
|
493
480
|
end
|
481
|
+
|
482
|
+
# 11 ids with limit 3, offset 9 should give 2 results.
|
483
|
+
if offset_value && (ids.size - offset_value < expected_size)
|
484
|
+
expected_size = ids.size - offset_value
|
494
485
|
end
|
495
486
|
|
496
|
-
|
497
|
-
|
487
|
+
if result.size == expected_size
|
488
|
+
result
|
489
|
+
else
|
490
|
+
raise_record_not_found_exception!(ids, result.size, expected_size)
|
491
|
+
end
|
492
|
+
end
|
498
493
|
|
499
|
-
|
494
|
+
def find_some_ordered(ids)
|
495
|
+
ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
|
500
496
|
|
501
|
-
|
502
|
-
pk_type = @klass.type_for_attribute(primary_key)
|
497
|
+
result = except(:limit, :offset).where(primary_key => ids).records
|
503
498
|
|
504
|
-
|
505
|
-
|
506
|
-
else
|
507
|
-
raise_record_not_found_exception!(ids, result.size, ids.size)
|
508
|
-
end
|
509
|
-
end
|
499
|
+
if result.size == ids.size
|
500
|
+
pk_type = @klass.type_for_attribute(primary_key)
|
510
501
|
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
@take ||= limit(1).records.first
|
516
|
-
end
|
502
|
+
records_by_id = result.index_by(&:id)
|
503
|
+
ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
|
504
|
+
else
|
505
|
+
raise_record_not_found_exception!(ids, result.size, ids.size)
|
517
506
|
end
|
507
|
+
end
|
518
508
|
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
end
|
509
|
+
def find_take
|
510
|
+
if loaded?
|
511
|
+
records.first
|
512
|
+
else
|
513
|
+
@take ||= limit(1).records.first
|
525
514
|
end
|
515
|
+
end
|
526
516
|
|
527
|
-
|
528
|
-
|
517
|
+
def find_take_with_limit(limit)
|
518
|
+
if loaded?
|
519
|
+
records.take(limit)
|
520
|
+
else
|
521
|
+
limit(limit).to_a
|
529
522
|
end
|
523
|
+
end
|
524
|
+
|
525
|
+
def find_nth(index)
|
526
|
+
@offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first
|
527
|
+
end
|
530
528
|
|
531
|
-
|
532
|
-
|
533
|
-
|
529
|
+
def find_nth_with_limit(index, limit)
|
530
|
+
if loaded?
|
531
|
+
records[index, limit] || []
|
532
|
+
else
|
533
|
+
relation = if order_values.empty? && primary_key
|
534
|
+
order(arel_attribute(primary_key).asc)
|
534
535
|
else
|
535
|
-
|
536
|
-
|
537
|
-
else
|
538
|
-
self
|
539
|
-
end
|
536
|
+
self
|
537
|
+
end
|
540
538
|
|
539
|
+
if limit_value.nil? || index < limit_value
|
541
540
|
relation = relation.offset(offset_index + index) unless index.zero?
|
542
541
|
relation.limit(limit).to_a
|
542
|
+
else
|
543
|
+
[]
|
543
544
|
end
|
544
545
|
end
|
546
|
+
end
|
545
547
|
|
546
|
-
|
547
|
-
|
548
|
-
|
548
|
+
def find_nth_from_last(index)
|
549
|
+
if loaded?
|
550
|
+
records[-index]
|
551
|
+
else
|
552
|
+
relation = if order_values.empty? && primary_key
|
553
|
+
order(arel_attribute(primary_key).asc)
|
549
554
|
else
|
550
|
-
|
551
|
-
order(arel_attribute(primary_key).asc)
|
552
|
-
else
|
553
|
-
self
|
554
|
-
end
|
555
|
-
|
556
|
-
relation.to_a[-index]
|
557
|
-
# TODO: can be made more performant on large result sets by
|
558
|
-
# for instance, last(index)[-index] (which would require
|
559
|
-
# refactoring the last(n) finder method to make test suite pass),
|
560
|
-
# or by using a combination of reverse_order, limit, and offset,
|
561
|
-
# e.g., reverse_order.offset(index-1).first
|
555
|
+
self
|
562
556
|
end
|
563
|
-
end
|
564
557
|
|
565
|
-
|
566
|
-
|
558
|
+
relation.to_a[-index]
|
559
|
+
# TODO: can be made more performant on large result sets by
|
560
|
+
# for instance, last(index)[-index] (which would require
|
561
|
+
# refactoring the last(n) finder method to make test suite pass),
|
562
|
+
# or by using a combination of reverse_order, limit, and offset,
|
563
|
+
# e.g., reverse_order.offset(index-1).first
|
567
564
|
end
|
565
|
+
end
|
566
|
+
|
567
|
+
def find_last(limit)
|
568
|
+
limit ? records.last(limit) : records.last
|
569
|
+
end
|
568
570
|
end
|
569
571
|
end
|
@@ -1130,7 +1130,12 @@ module ActiveRecord
|
|
1130
1130
|
arel_attribute(arg).asc
|
1131
1131
|
when Hash
|
1132
1132
|
arg.map { |field, dir|
|
1133
|
-
|
1133
|
+
case field
|
1134
|
+
when Arel::Nodes::SqlLiteral
|
1135
|
+
field.send(dir.downcase)
|
1136
|
+
else
|
1137
|
+
arel_attribute(field).send(dir.downcase)
|
1138
|
+
end
|
1134
1139
|
}
|
1135
1140
|
else
|
1136
1141
|
arg
|
data/lib/active_record/result.rb
CHANGED
@@ -41,10 +41,15 @@ module ActiveRecord
|
|
41
41
|
@column_types = column_types
|
42
42
|
end
|
43
43
|
|
44
|
+
# Returns the number of elements in the rows array.
|
44
45
|
def length
|
45
46
|
@rows.length
|
46
47
|
end
|
47
48
|
|
49
|
+
# Calls the given block once for each element in row collection, passing
|
50
|
+
# row as parameter.
|
51
|
+
#
|
52
|
+
# Returns an +Enumerator+ if no block is given.
|
48
53
|
def each
|
49
54
|
if block_given?
|
50
55
|
hash_rows.each { |row| yield row }
|
@@ -53,6 +58,7 @@ module ActiveRecord
|
|
53
58
|
end
|
54
59
|
end
|
55
60
|
|
61
|
+
# Returns an array of hashes representing each row record.
|
56
62
|
def to_hash
|
57
63
|
hash_rows
|
58
64
|
end
|
@@ -60,11 +66,12 @@ module ActiveRecord
|
|
60
66
|
alias :map! :map
|
61
67
|
alias :collect! :map
|
62
68
|
|
63
|
-
# Returns true if there are no records.
|
69
|
+
# Returns true if there are no records, otherwise false.
|
64
70
|
def empty?
|
65
71
|
rows.empty?
|
66
72
|
end
|
67
73
|
|
74
|
+
# Returns an array of hashes representing each row record.
|
68
75
|
def to_ary
|
69
76
|
hash_rows
|
70
77
|
end
|
@@ -73,11 +80,15 @@ module ActiveRecord
|
|
73
80
|
hash_rows[idx]
|
74
81
|
end
|
75
82
|
|
83
|
+
# Returns the first record from the rows collection.
|
84
|
+
# If the rows collection is empty, returns +nil+.
|
76
85
|
def first
|
77
86
|
return nil if @rows.empty?
|
78
87
|
Hash[@columns.zip(@rows.first)]
|
79
88
|
end
|
80
89
|
|
90
|
+
# Returns the last record from the rows collection.
|
91
|
+
# If the rows collection is empty, returns +nil+.
|
81
92
|
def last
|
82
93
|
return nil if @rows.empty?
|
83
94
|
Hash[@columns.zip(@rows.last)]
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
module ActiveRecord
|
3
2
|
module Sanitization
|
4
3
|
extend ActiveSupport::Concern
|
@@ -207,9 +206,9 @@ module ActiveRecord
|
|
207
206
|
end
|
208
207
|
end
|
209
208
|
|
210
|
-
# TODO: Deprecate this
|
211
209
|
def quoted_id # :nodoc:
|
212
210
|
self.class.connection.quote(@attributes[self.class.primary_key].value_for_database)
|
213
211
|
end
|
212
|
+
deprecate :quoted_id
|
214
213
|
end
|
215
214
|
end
|
@@ -39,7 +39,11 @@ module ActiveRecord
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def normalized_versions
|
42
|
-
|
42
|
+
all_versions.map { |v| normalize_migration_number v }
|
43
|
+
end
|
44
|
+
|
45
|
+
def all_versions
|
46
|
+
order(:version).pluck(:version)
|
43
47
|
end
|
44
48
|
end
|
45
49
|
|
@@ -1,8 +1,11 @@
|
|
1
|
+
require "tempfile"
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module Tasks # :nodoc:
|
3
5
|
class PostgreSQLDatabaseTasks # :nodoc:
|
4
6
|
DEFAULT_ENCODING = ENV["CHARSET"] || "utf8"
|
5
7
|
ON_ERROR_STOP_1 = "ON_ERROR_STOP=1".freeze
|
8
|
+
SQL_COMMENT_BEGIN = "--".freeze
|
6
9
|
|
7
10
|
delegate :connection, :establish_connection, :clear_active_connections!,
|
8
11
|
to: ActiveRecord::Base
|
@@ -65,6 +68,7 @@ module ActiveRecord
|
|
65
68
|
end
|
66
69
|
args << configuration["database"]
|
67
70
|
run_cmd("pg_dump", args, "dumping")
|
71
|
+
remove_sql_header_comments(filename)
|
68
72
|
File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" }
|
69
73
|
end
|
70
74
|
|
@@ -110,6 +114,22 @@ module ActiveRecord
|
|
110
114
|
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
|
111
115
|
msg
|
112
116
|
end
|
117
|
+
|
118
|
+
def remove_sql_header_comments(filename)
|
119
|
+
removing_comments = true
|
120
|
+
tempfile = Tempfile.open("uncommented_structure.sql")
|
121
|
+
begin
|
122
|
+
File.foreach(filename) do |line|
|
123
|
+
unless removing_comments && (line.start_with?(SQL_COMMENT_BEGIN) || line.blank?)
|
124
|
+
tempfile << line
|
125
|
+
removing_comments = false
|
126
|
+
end
|
127
|
+
end
|
128
|
+
ensure
|
129
|
+
tempfile.close
|
130
|
+
end
|
131
|
+
FileUtils.mv(tempfile.path, filename)
|
132
|
+
end
|
113
133
|
end
|
114
134
|
end
|
115
135
|
end
|