activerecord 4.0.13 → 4.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +745 -2700
- data/README.rdoc +2 -2
- data/examples/performance.rb +30 -18
- data/examples/simple.rb +4 -4
- data/lib/active_record.rb +2 -6
- data/lib/active_record/aggregations.rb +2 -1
- data/lib/active_record/association_relation.rb +0 -4
- data/lib/active_record/associations.rb +87 -43
- data/lib/active_record/associations/alias_tracker.rb +1 -3
- data/lib/active_record/associations/association.rb +8 -16
- data/lib/active_record/associations/association_scope.rb +5 -16
- data/lib/active_record/associations/belongs_to_association.rb +34 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +78 -54
- data/lib/active_record/associations/builder/belongs_to.rb +91 -58
- data/lib/active_record/associations/builder/collection_association.rb +47 -45
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -25
- data/lib/active_record/associations/builder/has_many.rb +2 -2
- data/lib/active_record/associations/builder/has_one.rb +5 -7
- data/lib/active_record/associations/builder/singular_association.rb +6 -7
- data/lib/active_record/associations/collection_association.rb +68 -105
- data/lib/active_record/associations/collection_proxy.rb +12 -15
- data/lib/active_record/associations/has_many_association.rb +11 -9
- data/lib/active_record/associations/has_many_through_association.rb +16 -12
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +204 -165
- data/lib/active_record/associations/join_dependency/join_association.rb +43 -101
- data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
- data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
- data/lib/active_record/associations/join_helper.rb +2 -11
- data/lib/active_record/associations/preloader.rb +89 -34
- data/lib/active_record/associations/preloader/association.rb +43 -25
- data/lib/active_record/associations/preloader/collection_association.rb +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +58 -26
- data/lib/active_record/associations/singular_association.rb +6 -5
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/attribute_assignment.rb +5 -2
- data/lib/active_record/attribute_methods.rb +45 -40
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
- data/lib/active_record/attribute_methods/dirty.rb +8 -22
- data/lib/active_record/attribute_methods/primary_key.rb +1 -7
- data/lib/active_record/attribute_methods/read.rb +55 -28
- data/lib/active_record/attribute_methods/serialization.rb +12 -33
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -13
- data/lib/active_record/attribute_methods/write.rb +37 -12
- data/lib/active_record/autosave_association.rb +207 -207
- data/lib/active_record/base.rb +5 -1
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -7
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +11 -22
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -14
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -5
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +84 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +52 -83
- data/lib/active_record/connection_adapters/abstract/transaction.rb +0 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +14 -97
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +58 -60
- data/lib/active_record/connection_adapters/column.rb +1 -35
- data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +3 -4
- data/lib/active_record/connection_adapters/mysql_adapter.rb +16 -15
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +24 -18
- data/lib/active_record/connection_adapters/postgresql/cast.rb +20 -16
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +23 -43
- data/lib/active_record/connection_adapters/postgresql/oid.rb +19 -12
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +28 -23
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +8 -30
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +92 -75
- data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +31 -64
- data/lib/active_record/connection_handling.rb +2 -2
- data/lib/active_record/core.rb +22 -43
- data/lib/active_record/counter_cache.rb +7 -7
- data/lib/active_record/enum.rb +100 -0
- data/lib/active_record/errors.rb +10 -5
- data/lib/active_record/fixture_set/file.rb +2 -1
- data/lib/active_record/fixtures.rb +171 -74
- data/lib/active_record/inheritance.rb +16 -22
- data/lib/active_record/integration.rb +52 -1
- data/lib/active_record/locking/optimistic.rb +7 -2
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +5 -12
- data/lib/active_record/migration.rb +62 -46
- data/lib/active_record/migration/command_recorder.rb +7 -13
- data/lib/active_record/model_schema.rb +7 -14
- data/lib/active_record/nested_attributes.rb +10 -8
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +3 -3
- data/lib/active_record/persistence.rb +16 -34
- data/lib/active_record/querying.rb +14 -12
- data/lib/active_record/railtie.rb +0 -50
- data/lib/active_record/railties/databases.rake +12 -15
- data/lib/active_record/readonly_attributes.rb +0 -6
- data/lib/active_record/reflection.rb +189 -75
- data/lib/active_record/relation.rb +69 -94
- data/lib/active_record/relation/batches.rb +57 -23
- data/lib/active_record/relation/calculations.rb +36 -43
- data/lib/active_record/relation/delegation.rb +54 -39
- data/lib/active_record/relation/finder_methods.rb +107 -62
- data/lib/active_record/relation/merger.rb +7 -20
- data/lib/active_record/relation/predicate_builder.rb +57 -38
- data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/query_methods.rb +110 -98
- data/lib/active_record/relation/spawn_methods.rb +1 -2
- data/lib/active_record/result.rb +45 -6
- data/lib/active_record/runtime_registry.rb +5 -0
- data/lib/active_record/sanitization.rb +6 -8
- data/lib/active_record/schema_dumper.rb +16 -5
- data/lib/active_record/schema_migration.rb +24 -25
- data/lib/active_record/scoping/default.rb +5 -18
- data/lib/active_record/scoping/named.rb +8 -29
- data/lib/active_record/store.rb +56 -28
- data/lib/active_record/tasks/database_tasks.rb +8 -4
- data/lib/active_record/timestamp.rb +4 -4
- data/lib/active_record/transactions.rb +8 -10
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +1 -6
- data/lib/active_record/version.rb +1 -1
- data/lib/rails/generators/active_record.rb +2 -8
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
- metadata +32 -45
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
- data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
- data/lib/active_record/test_case.rb +0 -102
@@ -7,7 +7,7 @@ module ActiveRecord
|
|
7
7
|
|
8
8
|
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
|
9
9
|
:order, :joins, :where, :having, :bind, :references,
|
10
|
-
:extending]
|
10
|
+
:extending, :unscope]
|
11
11
|
|
12
12
|
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
|
13
13
|
:reverse_order, :distinct, :create_with, :uniq]
|
@@ -17,18 +17,14 @@ module ActiveRecord
|
|
17
17
|
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
|
18
18
|
|
19
19
|
attr_reader :table, :klass, :loaded
|
20
|
-
attr_accessor :default_scoped
|
21
20
|
alias :model :klass
|
22
21
|
alias :loaded? :loaded
|
23
|
-
alias :default_scoped? :default_scoped
|
24
22
|
|
25
23
|
def initialize(klass, table, values = {})
|
26
|
-
@klass
|
27
|
-
@table
|
28
|
-
@values
|
29
|
-
@
|
30
|
-
@loaded = false
|
31
|
-
@default_scoped = false
|
24
|
+
@klass = klass
|
25
|
+
@table = table
|
26
|
+
@values = values
|
27
|
+
@loaded = false
|
32
28
|
end
|
33
29
|
|
34
30
|
def initialize_copy(other)
|
@@ -39,7 +35,7 @@ module ActiveRecord
|
|
39
35
|
reset
|
40
36
|
end
|
41
37
|
|
42
|
-
def insert(values)
|
38
|
+
def insert(values) # :nodoc:
|
43
39
|
primary_key_value = nil
|
44
40
|
|
45
41
|
if primary_key && Hash === values
|
@@ -56,16 +52,7 @@ module ActiveRecord
|
|
56
52
|
im = arel.create_insert
|
57
53
|
im.into @table
|
58
54
|
|
59
|
-
|
60
|
-
|
61
|
-
substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
|
62
|
-
binds = substitutes.map do |arel_attr, value|
|
63
|
-
[@klass.columns_hash[arel_attr.name], value]
|
64
|
-
end
|
65
|
-
|
66
|
-
substitutes.each_with_index do |tuple, i|
|
67
|
-
tuple[1] = conn.substitute_at(binds[i][0], i)
|
68
|
-
end
|
55
|
+
substitutes, binds = substitute_values values
|
69
56
|
|
70
57
|
if values.empty? # empty insert
|
71
58
|
im.values = Arel.sql(connection.empty_insert_statement_value)
|
@@ -73,7 +60,7 @@ module ActiveRecord
|
|
73
60
|
im.insert substitutes
|
74
61
|
end
|
75
62
|
|
76
|
-
|
63
|
+
@klass.connection.insert(
|
77
64
|
im,
|
78
65
|
'SQL',
|
79
66
|
primary_key,
|
@@ -82,6 +69,29 @@ module ActiveRecord
|
|
82
69
|
binds)
|
83
70
|
end
|
84
71
|
|
72
|
+
def update_record(values, id, id_was) # :nodoc:
|
73
|
+
substitutes, binds = substitute_values values
|
74
|
+
um = @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes, @klass.primary_key)
|
75
|
+
|
76
|
+
@klass.connection.update(
|
77
|
+
um,
|
78
|
+
'SQL',
|
79
|
+
binds)
|
80
|
+
end
|
81
|
+
|
82
|
+
def substitute_values(values) # :nodoc:
|
83
|
+
substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
|
84
|
+
binds = substitutes.map do |arel_attr, value|
|
85
|
+
[@klass.columns_hash[arel_attr.name], value]
|
86
|
+
end
|
87
|
+
|
88
|
+
substitutes.each_with_index do |tuple, i|
|
89
|
+
tuple[1] = @klass.connection.substitute_at(binds[i][0], i)
|
90
|
+
end
|
91
|
+
|
92
|
+
[substitutes, binds]
|
93
|
+
end
|
94
|
+
|
85
95
|
# Initializes new record from relation while maintaining the current
|
86
96
|
# scope.
|
87
97
|
#
|
@@ -234,8 +244,13 @@ module ActiveRecord
|
|
234
244
|
def empty?
|
235
245
|
return @records.empty? if loaded?
|
236
246
|
|
237
|
-
|
238
|
-
|
247
|
+
if limit_value == 0
|
248
|
+
true
|
249
|
+
else
|
250
|
+
# FIXME: This count is not compatible with #select('authors.*') or other select narrows
|
251
|
+
c = count
|
252
|
+
c.respond_to?(:zero?) ? c.zero? : c.empty?
|
253
|
+
end
|
239
254
|
end
|
240
255
|
|
241
256
|
# Returns true if there are any records.
|
@@ -300,7 +315,7 @@ module ActiveRecord
|
|
300
315
|
stmt.table(table)
|
301
316
|
stmt.key = table[primary_key]
|
302
317
|
|
303
|
-
if
|
318
|
+
if joins_values.any?
|
304
319
|
@klass.connection.join_to_update(stmt, arel)
|
305
320
|
else
|
306
321
|
stmt.take(arel.limit)
|
@@ -425,7 +440,7 @@ module ActiveRecord
|
|
425
440
|
stmt = Arel::DeleteManager.new(arel.engine)
|
426
441
|
stmt.from(table)
|
427
442
|
|
428
|
-
if
|
443
|
+
if joins_values.any?
|
429
444
|
@klass.connection.join_to_delete(stmt, arel, table[primary_key])
|
430
445
|
else
|
431
446
|
stmt.wheres = arel.constraints
|
@@ -491,19 +506,34 @@ module ActiveRecord
|
|
491
506
|
# User.where(name: 'Oscar').to_sql
|
492
507
|
# # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
|
493
508
|
def to_sql
|
494
|
-
@to_sql ||=
|
509
|
+
@to_sql ||= begin
|
510
|
+
relation = self
|
511
|
+
connection = klass.connection
|
512
|
+
visitor = connection.visitor
|
513
|
+
|
514
|
+
if eager_loading?
|
515
|
+
find_with_associations { |rel| relation = rel }
|
516
|
+
end
|
517
|
+
|
518
|
+
ast = relation.arel.ast
|
519
|
+
binds = relation.bind_values.dup
|
520
|
+
visitor.accept(ast) do
|
521
|
+
connection.quote(*binds.shift.reverse)
|
522
|
+
end
|
523
|
+
end
|
495
524
|
end
|
496
525
|
|
497
526
|
# Returns a hash of where conditions.
|
498
527
|
#
|
499
528
|
# User.where(name: 'Oscar').where_values_hash
|
500
529
|
# # => {name: "Oscar"}
|
501
|
-
def where_values_hash
|
502
|
-
equalities =
|
503
|
-
node.left.relation.name ==
|
530
|
+
def where_values_hash
|
531
|
+
equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node|
|
532
|
+
node.left.relation.name == table_name
|
504
533
|
}
|
505
534
|
|
506
535
|
binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
|
536
|
+
binds.merge!(Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }])
|
507
537
|
|
508
538
|
Hash[equalities.map { |where|
|
509
539
|
name = where.left.name
|
@@ -539,8 +569,6 @@ module ActiveRecord
|
|
539
569
|
# Compares two relations for equality.
|
540
570
|
def ==(other)
|
541
571
|
case other
|
542
|
-
when Associations::CollectionProxy, AssociationRelation
|
543
|
-
self == other.to_a
|
544
572
|
when Relation
|
545
573
|
other.to_sql == to_sql
|
546
574
|
when Array
|
@@ -552,16 +580,6 @@ module ActiveRecord
|
|
552
580
|
q.pp(self.to_a)
|
553
581
|
end
|
554
582
|
|
555
|
-
def with_default_scope #:nodoc:
|
556
|
-
if default_scoped? && default_scope = klass.send(:build_default_scope)
|
557
|
-
default_scope = default_scope.merge(self)
|
558
|
-
default_scope.default_scoped = false
|
559
|
-
default_scope
|
560
|
-
else
|
561
|
-
self
|
562
|
-
end
|
563
|
-
end
|
564
|
-
|
565
583
|
# Returns true if relation is blank.
|
566
584
|
def blank?
|
567
585
|
to_a.blank?
|
@@ -581,34 +599,24 @@ module ActiveRecord
|
|
581
599
|
private
|
582
600
|
|
583
601
|
def exec_queries
|
584
|
-
|
585
|
-
|
586
|
-
if default_scoped.equal?(self)
|
587
|
-
@records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values)
|
588
|
-
|
589
|
-
preload = preload_values
|
590
|
-
preload += includes_values unless eager_loading?
|
591
|
-
preload.each do |associations|
|
592
|
-
ActiveRecord::Associations::Preloader.new(@records, associations).run
|
593
|
-
end
|
602
|
+
@records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values)
|
594
603
|
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
@records = default_scoped.to_a
|
604
|
+
preload = preload_values
|
605
|
+
preload += includes_values unless eager_loading?
|
606
|
+
preloader = ActiveRecord::Associations::Preloader.new
|
607
|
+
preload.each do |associations|
|
608
|
+
preloader.preload @records, associations
|
601
609
|
end
|
602
610
|
|
611
|
+
@records.each { |record| record.readonly! } if readonly_value
|
612
|
+
|
603
613
|
@loaded = true
|
604
614
|
@records
|
605
615
|
end
|
606
616
|
|
607
617
|
def references_eager_loaded_tables?
|
608
618
|
joined_tables = arel.join_sources.map do |join|
|
609
|
-
|
610
|
-
tables_in_string(join.left)
|
611
|
-
else
|
619
|
+
unless join.is_a?(Arel::Nodes::StringJoin)
|
612
620
|
[join.left.table_name, join.left.table_alias]
|
613
621
|
end
|
614
622
|
end
|
@@ -617,41 +625,8 @@ module ActiveRecord
|
|
617
625
|
|
618
626
|
# always convert table names to downcase as in Oracle quoted table names are in uppercase
|
619
627
|
joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq
|
620
|
-
string_tables = tables_in_string(to_sql)
|
621
628
|
|
622
|
-
|
623
|
-
true
|
624
|
-
elsif !ActiveRecord::Base.disable_implicit_join_references &&
|
625
|
-
(string_tables - joined_tables).any?
|
626
|
-
ActiveSupport::Deprecation.warn(
|
627
|
-
"It looks like you are eager loading table(s) (one of: #{string_tables.join(', ')}) " \
|
628
|
-
"that are referenced in a string SQL snippet. For example: \n" \
|
629
|
-
"\n" \
|
630
|
-
" Post.includes(:comments).where(\"comments.title = 'foo'\")\n" \
|
631
|
-
"\n" \
|
632
|
-
"Currently, Active Record recognizes the table in the string, and knows to JOIN the " \
|
633
|
-
"comments table to the query, rather than loading comments in a separate query. " \
|
634
|
-
"However, doing this without writing a full-blown SQL parser is inherently flawed. " \
|
635
|
-
"Since we don't want to write an SQL parser, we are removing this functionality. " \
|
636
|
-
"From now on, you must explicitly tell Active Record when you are referencing a table " \
|
637
|
-
"from a string:\n" \
|
638
|
-
"\n" \
|
639
|
-
" Post.includes(:comments).where(\"comments.title = 'foo'\").references(:comments)\n" \
|
640
|
-
"\n" \
|
641
|
-
"If you don't rely on implicit join references you can disable the feature entirely " \
|
642
|
-
"by setting `config.active_record.disable_implicit_join_references = true`."
|
643
|
-
)
|
644
|
-
true
|
645
|
-
else
|
646
|
-
false
|
647
|
-
end
|
648
|
-
end
|
649
|
-
|
650
|
-
def tables_in_string(string)
|
651
|
-
return [] if string.blank?
|
652
|
-
# always convert table names to downcase as in Oracle quoted table names are in uppercase
|
653
|
-
# ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
|
654
|
-
string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
|
629
|
+
(references_values - joined_tables).any?
|
655
630
|
end
|
656
631
|
end
|
657
632
|
end
|
@@ -11,7 +11,7 @@ module ActiveRecord
|
|
11
11
|
# The #find_each method uses #find_in_batches with a batch size of 1000 (or as
|
12
12
|
# specified by the +:batch_size+ option).
|
13
13
|
#
|
14
|
-
# Person.
|
14
|
+
# Person.find_each do |person|
|
15
15
|
# person.do_awesome_stuff
|
16
16
|
# end
|
17
17
|
#
|
@@ -19,40 +19,71 @@ module ActiveRecord
|
|
19
19
|
# person.party_all_night!
|
20
20
|
# end
|
21
21
|
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
def find_each(options = {})
|
25
|
-
find_in_batches(options) do |records|
|
26
|
-
records.each { |record| yield record }
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
# Yields each batch of records that was found by the find +options+ as
|
31
|
-
# an array. The size of each batch is set by the +:batch_size+
|
32
|
-
# option; the default is 1000.
|
22
|
+
# If you do not provide a block to #find_each, it will return an Enumerator
|
23
|
+
# for chaining with other methods:
|
33
24
|
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
# make worker 1 handle all the records between id 0 and 10,000 and
|
38
|
-
# worker 2 handle from 10,000 and beyond (by setting the +:start+
|
39
|
-
# option on that worker).
|
25
|
+
# Person.find_each.with_index do |person, index|
|
26
|
+
# person.award_trophy(index + 1)
|
27
|
+
# end
|
40
28
|
#
|
41
|
-
#
|
29
|
+
# ==== Options
|
30
|
+
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
|
31
|
+
# * <tt>:start</tt> - Specifies the starting point for the batch processing.
|
32
|
+
# This is especially useful if you want multiple workers dealing with
|
33
|
+
# the same processing queue. You can make worker 1 handle all the records
|
34
|
+
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
|
35
|
+
# (by setting the +:start+ option on that worker).
|
36
|
+
#
|
37
|
+
# # Let's process for a batch of 2000 records, skipping the first 2000 rows
|
38
|
+
# Person.find_each(start: 2000, batch_size: 2000) do |person|
|
39
|
+
# person.party_all_night!
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# NOTE: It's not possible to set the order. That is automatically set to
|
42
43
|
# ascending on the primary key ("id ASC") to make the batch ordering
|
43
44
|
# work. This also means that this method only works with integer-based
|
44
|
-
# primary keys.
|
45
|
+
# primary keys.
|
46
|
+
#
|
47
|
+
# NOTE: You can't set the limit either, that's used to control
|
45
48
|
# the batch sizes.
|
49
|
+
def find_each(options = {})
|
50
|
+
if block_given?
|
51
|
+
find_in_batches(options) do |records|
|
52
|
+
records.each { |record| yield record }
|
53
|
+
end
|
54
|
+
else
|
55
|
+
enum_for :find_each, options
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Yields each batch of records that was found by the find +options+ as
|
60
|
+
# an array.
|
46
61
|
#
|
47
62
|
# Person.where("age > 21").find_in_batches do |group|
|
48
63
|
# sleep(50) # Make sure it doesn't get too crowded in there!
|
49
64
|
# group.each { |person| person.party_all_night! }
|
50
65
|
# end
|
51
66
|
#
|
67
|
+
# ==== Options
|
68
|
+
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
|
69
|
+
# * <tt>:start</tt> - Specifies the starting point for the batch processing.
|
70
|
+
# This is especially useful if you want multiple workers dealing with
|
71
|
+
# the same processing queue. You can make worker 1 handle all the records
|
72
|
+
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
|
73
|
+
# (by setting the +:start+ option on that worker).
|
74
|
+
#
|
52
75
|
# # Let's process the next 2000 records
|
53
|
-
# Person.
|
76
|
+
# Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
|
54
77
|
# group.each { |person| person.party_all_night! }
|
55
78
|
# end
|
79
|
+
#
|
80
|
+
# NOTE: It's not possible to set the order. That is automatically set to
|
81
|
+
# ascending on the primary key ("id ASC") to make the batch ordering
|
82
|
+
# work. This also means that this method only works with integer-based
|
83
|
+
# primary keys.
|
84
|
+
#
|
85
|
+
# NOTE: You can't set the limit either, that's used to control
|
86
|
+
# the batch sizes.
|
56
87
|
def find_in_batches(options = {})
|
57
88
|
options.assert_valid_keys(:start, :batch_size)
|
58
89
|
|
@@ -71,13 +102,16 @@ module ActiveRecord
|
|
71
102
|
while records.any?
|
72
103
|
records_size = records.size
|
73
104
|
primary_key_offset = records.last.id
|
74
|
-
raise "Primary key not included in the custom select clause" unless primary_key_offset
|
75
105
|
|
76
106
|
yield records
|
77
107
|
|
78
108
|
break if records_size < batch_size
|
79
109
|
|
80
|
-
|
110
|
+
if primary_key_offset
|
111
|
+
records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
|
112
|
+
else
|
113
|
+
raise "Primary key not included in the custom select clause"
|
114
|
+
end
|
81
115
|
end
|
82
116
|
end
|
83
117
|
|
@@ -20,6 +20,8 @@ module ActiveRecord
|
|
20
20
|
# Person.group(:city).count
|
21
21
|
# # => { 'Rome' => 5, 'Paris' => 3 }
|
22
22
|
def count(column_name = nil, options = {})
|
23
|
+
# TODO: Remove options argument as soon we remove support to
|
24
|
+
# activerecord-deprecated_finders.
|
23
25
|
column_name, options = nil, column_name if column_name.is_a?(Hash)
|
24
26
|
calculate(:count, column_name, options)
|
25
27
|
end
|
@@ -27,8 +29,10 @@ module ActiveRecord
|
|
27
29
|
# Calculates the average value on a given column. Returns +nil+ if there's
|
28
30
|
# no row. See +calculate+ for examples with options.
|
29
31
|
#
|
30
|
-
# Person.average(
|
32
|
+
# Person.average(:age) # => 35.8
|
31
33
|
def average(column_name, options = {})
|
34
|
+
# TODO: Remove options argument as soon we remove support to
|
35
|
+
# activerecord-deprecated_finders.
|
32
36
|
calculate(:average, column_name, options)
|
33
37
|
end
|
34
38
|
|
@@ -36,8 +40,10 @@ module ActiveRecord
|
|
36
40
|
# with the same data type of the column, or +nil+ if there's no row. See
|
37
41
|
# +calculate+ for examples with options.
|
38
42
|
#
|
39
|
-
# Person.minimum(
|
43
|
+
# Person.minimum(:age) # => 7
|
40
44
|
def minimum(column_name, options = {})
|
45
|
+
# TODO: Remove options argument as soon we remove support to
|
46
|
+
# activerecord-deprecated_finders.
|
41
47
|
calculate(:minimum, column_name, options)
|
42
48
|
end
|
43
49
|
|
@@ -45,8 +51,10 @@ module ActiveRecord
|
|
45
51
|
# with the same data type of the column, or +nil+ if there's no row. See
|
46
52
|
# +calculate+ for examples with options.
|
47
53
|
#
|
48
|
-
# Person.maximum(
|
54
|
+
# Person.maximum(:age) # => 93
|
49
55
|
def maximum(column_name, options = {})
|
56
|
+
# TODO: Remove options argument as soon we remove support to
|
57
|
+
# activerecord-deprecated_finders.
|
50
58
|
calculate(:maximum, column_name, options)
|
51
59
|
end
|
52
60
|
|
@@ -54,17 +62,9 @@ module ActiveRecord
|
|
54
62
|
# with the same data type of the column, 0 if there's no row. See
|
55
63
|
# +calculate+ for examples with options.
|
56
64
|
#
|
57
|
-
# Person.sum(
|
65
|
+
# Person.sum(:age) # => 4562
|
58
66
|
def sum(*args)
|
59
|
-
|
60
|
-
ActiveSupport::Deprecation.warn(
|
61
|
-
"Calling #sum with a block is deprecated and will be removed in Rails 4.1. " \
|
62
|
-
"If you want to perform sum calculation over the array of elements, use `to_a.sum(&block)`."
|
63
|
-
)
|
64
|
-
self.to_a.sum(*args) {|*block_args| yield(*block_args)}
|
65
|
-
else
|
66
|
-
calculate(:sum, *args)
|
67
|
-
end
|
67
|
+
calculate(:sum, *args)
|
68
68
|
end
|
69
69
|
|
70
70
|
# This calculates aggregate values in the given column. Methods for count, sum, average,
|
@@ -99,19 +99,17 @@ module ActiveRecord
|
|
99
99
|
#
|
100
100
|
# Person.sum("2 * age")
|
101
101
|
def calculate(operation, column_name, options = {})
|
102
|
-
|
102
|
+
# TODO: Remove options argument as soon we remove support to
|
103
|
+
# activerecord-deprecated_finders.
|
104
|
+
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
|
105
|
+
column_name = attribute_alias(column_name)
|
106
|
+
end
|
103
107
|
|
104
|
-
if
|
105
|
-
|
106
|
-
construct_relation_for_association_calculations.calculate(operation, column_name, options)
|
107
|
-
else
|
108
|
-
perform_calculation(operation, column_name, options)
|
109
|
-
end
|
108
|
+
if has_include?(column_name)
|
109
|
+
construct_relation_for_association_calculations.calculate(operation, column_name, options)
|
110
110
|
else
|
111
|
-
|
111
|
+
perform_calculation(operation, column_name, options)
|
112
112
|
end
|
113
|
-
rescue ThrowResult
|
114
|
-
0
|
115
113
|
end
|
116
114
|
|
117
115
|
# Use <tt>pluck</tt> as a shortcut to select one or more attributes without
|
@@ -149,10 +147,10 @@ module ActiveRecord
|
|
149
147
|
#
|
150
148
|
def pluck(*column_names)
|
151
149
|
column_names.map! do |column_name|
|
152
|
-
if column_name.is_a?(Symbol) &&
|
153
|
-
|
150
|
+
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
|
151
|
+
attribute_alias(column_name)
|
154
152
|
else
|
155
|
-
column_name
|
153
|
+
column_name.to_s
|
156
154
|
end
|
157
155
|
end
|
158
156
|
|
@@ -160,22 +158,20 @@ module ActiveRecord
|
|
160
158
|
construct_relation_for_association_calculations.pluck(*column_names)
|
161
159
|
else
|
162
160
|
relation = spawn
|
163
|
-
relation.select_values = column_names
|
161
|
+
relation.select_values = column_names.map { |cn|
|
162
|
+
columns_hash.key?(cn) ? arel_table[cn] : cn
|
163
|
+
}
|
164
164
|
result = klass.connection.select_all(relation.arel, nil, bind_values)
|
165
165
|
columns = result.columns.map do |key|
|
166
166
|
klass.column_types.fetch(key) {
|
167
|
-
result.column_types.fetch(key) {
|
168
|
-
Class.new { def type_cast(v); v; end }.new
|
169
|
-
}
|
167
|
+
result.column_types.fetch(key) { result.identity_type }
|
170
168
|
}
|
171
169
|
end
|
172
170
|
|
173
171
|
result = result.map do |attributes|
|
174
172
|
values = klass.initialize_attributes(attributes).values
|
175
173
|
|
176
|
-
columns.zip(values).map
|
177
|
-
column.type_cast(value)
|
178
|
-
end
|
174
|
+
columns.zip(values).map { |column, value| column.type_cast value }
|
179
175
|
end
|
180
176
|
columns.one? ? result.map!(&:first) : result
|
181
177
|
end
|
@@ -196,26 +192,22 @@ module ActiveRecord
|
|
196
192
|
end
|
197
193
|
|
198
194
|
def perform_calculation(operation, column_name, options = {})
|
195
|
+
# TODO: Remove options argument as soon we remove support to
|
196
|
+
# activerecord-deprecated_finders.
|
199
197
|
operation = operation.to_s.downcase
|
200
198
|
|
201
199
|
# If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count)
|
202
200
|
distinct = self.distinct_value
|
203
|
-
if options.has_key?(:distinct)
|
204
|
-
ActiveSupport::Deprecation.warn "The :distinct option for `Relation#count` is deprecated. " \
|
205
|
-
"Please use `Relation#distinct` instead. (eg. `relation.distinct.count`)"
|
206
|
-
distinct = options[:distinct]
|
207
|
-
end
|
208
201
|
|
209
202
|
if operation == "count"
|
210
|
-
column_name ||=
|
203
|
+
column_name ||= select_for_count
|
211
204
|
|
212
205
|
unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
|
213
206
|
distinct = true
|
214
207
|
end
|
215
208
|
|
216
209
|
column_name = primary_key if column_name == :all && distinct
|
217
|
-
|
218
|
-
distinct = nil if column_name =~ /\s*DISTINCT\s+/i
|
210
|
+
distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
|
219
211
|
end
|
220
212
|
|
221
213
|
if group_values.any?
|
@@ -381,8 +373,9 @@ module ActiveRecord
|
|
381
373
|
# TODO: refactor to allow non-string `select_values` (eg. Arel nodes).
|
382
374
|
def select_for_count
|
383
375
|
if select_values.present?
|
384
|
-
|
385
|
-
|
376
|
+
select_values.join(", ")
|
377
|
+
else
|
378
|
+
:all
|
386
379
|
end
|
387
380
|
end
|
388
381
|
|