activerecord 6.0.0.beta1 → 6.0.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 +455 -9
- data/README.rdoc +3 -1
- data/lib/active_record/associations/association.rb +18 -1
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +5 -2
- data/lib/active_record/associations/builder/collection_association.rb +5 -15
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_association.rb +5 -6
- data/lib/active_record/associations/collection_proxy.rb +13 -42
- data/lib/active_record/associations/has_many_association.rb +1 -9
- data/lib/active_record/associations/has_many_through_association.rb +4 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
- data/lib/active_record/associations/join_dependency.rb +10 -9
- data/lib/active_record/associations/preloader/association.rb +37 -34
- data/lib/active_record/associations/preloader/through_association.rb +48 -39
- data/lib/active_record/associations/preloader.rb +11 -6
- data/lib/active_record/associations.rb +3 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
- data/lib/active_record/attribute_methods/dirty.rb +47 -14
- data/lib/active_record/attribute_methods/primary_key.rb +7 -15
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +3 -9
- data/lib/active_record/attribute_methods/write.rb +6 -12
- data/lib/active_record/attribute_methods.rb +3 -53
- data/lib/active_record/attributes.rb +13 -0
- data/lib/active_record/autosave_association.rb +15 -5
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/callbacks.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +124 -23
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +101 -70
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +11 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
- data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
- data/lib/active_record/connection_adapters/abstract_adapter.rb +108 -39
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +93 -134
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +1 -1
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +91 -24
- data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +69 -118
- data/lib/active_record/connection_handling.rb +32 -16
- data/lib/active_record/core.rb +27 -20
- data/lib/active_record/database_configurations/hash_config.rb +11 -11
- data/lib/active_record/database_configurations/url_config.rb +21 -16
- data/lib/active_record/database_configurations.rb +99 -50
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/enum.rb +15 -0
- data/lib/active_record/errors.rb +18 -13
- data/lib/active_record/fixtures.rb +11 -6
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +13 -1
- data/lib/active_record/internal_metadata.rb +5 -1
- data/lib/active_record/locking/optimistic.rb +3 -4
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +75 -0
- data/lib/active_record/migration/command_recorder.rb +28 -14
- data/lib/active_record/migration/compatibility.rb +72 -63
- data/lib/active_record/migration.rb +62 -44
- data/lib/active_record/persistence.rb +212 -19
- data/lib/active_record/querying.rb +18 -14
- data/lib/active_record/railtie.rb +9 -1
- data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
- data/lib/active_record/railties/databases.rake +124 -25
- data/lib/active_record/reflection.rb +18 -32
- data/lib/active_record/relation/calculations.rb +40 -44
- data/lib/active_record/relation/delegation.rb +23 -31
- data/lib/active_record/relation/finder_methods.rb +13 -13
- data/lib/active_record/relation/merger.rb +11 -16
- data/lib/active_record/relation/query_attribute.rb +5 -3
- data/lib/active_record/relation/query_methods.rb +217 -68
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +10 -10
- data/lib/active_record/relation.rb +184 -35
- data/lib/active_record/sanitization.rb +33 -4
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +10 -1
- data/lib/active_record/schema_migration.rb +1 -1
- data/lib/active_record/scoping/default.rb +7 -15
- data/lib/active_record/scoping/named.rb +10 -2
- data/lib/active_record/scoping.rb +6 -7
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/store.rb +48 -0
- data/lib/active_record/table_metadata.rb +9 -13
- data/lib/active_record/tasks/database_tasks.rb +109 -6
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
- data/lib/active_record/test_databases.rb +1 -16
- data/lib/active_record/test_fixtures.rb +2 -2
- data/lib/active_record/timestamp.rb +35 -19
- data/lib/active_record/touch_later.rb +4 -2
- data/lib/active_record/transactions.rb +55 -45
- data/lib/active_record/type_caster/connection.rb +16 -10
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record.rb +7 -1
- data/lib/arel/insert_manager.rb +3 -3
- data/lib/arel/nodes/and.rb +1 -1
- data/lib/arel/nodes/case.rb +1 -1
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/select_core.rb +16 -12
- data/lib/arel/nodes/unary.rb +1 -0
- data/lib/arel/nodes/values_list.rb +2 -17
- data/lib/arel/nodes.rb +2 -1
- data/lib/arel/select_manager.rb +10 -10
- data/lib/arel/visitors/depth_first.rb +7 -2
- data/lib/arel/visitors/dot.rb +7 -2
- data/lib/arel/visitors/ibm_db.rb +13 -0
- data/lib/arel/visitors/informix.rb +6 -0
- data/lib/arel/visitors/mssql.rb +15 -1
- data/lib/arel/visitors/oracle12.rb +4 -5
- data/lib/arel/visitors/postgresql.rb +4 -10
- data/lib/arel/visitors/to_sql.rb +107 -131
- data/lib/arel/visitors/visitor.rb +9 -5
- data/lib/arel.rb +7 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +17 -13
- data/lib/active_record/collection_cache_key.rb +0 -53
- data/lib/arel/nodes/values.rb +0 -16
@@ -63,7 +63,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
63
63
|
|
64
64
|
def middle_reflection(join_model)
|
65
65
|
middle_name = [lhs_model.name.downcase.pluralize,
|
66
|
-
association_name].join("_").gsub("::", "_").to_sym
|
66
|
+
association_name.to_s].sort.join("_").gsub("::", "_").to_sym
|
67
67
|
middle_options = middle_options join_model
|
68
68
|
|
69
69
|
HasMany.create_reflection(lhs_model,
|
@@ -13,5 +13,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
13
13
|
def self.valid_dependent_options
|
14
14
|
[:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
|
15
15
|
end
|
16
|
+
|
17
|
+
private_class_method :macro, :valid_options, :valid_dependent_options
|
16
18
|
end
|
17
19
|
end
|
@@ -7,7 +7,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.valid_options(options)
|
10
|
-
valid = super + [:as]
|
10
|
+
valid = super + [:as, :touch]
|
11
11
|
valid += [:through, :source, :source_type] if options[:through]
|
12
12
|
valid
|
13
13
|
end
|
@@ -16,6 +16,11 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
16
16
|
[:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
|
17
17
|
end
|
18
18
|
|
19
|
+
def self.define_callbacks(model, reflection)
|
20
|
+
super
|
21
|
+
add_touch_callbacks(model, reflection) if reflection.options[:touch]
|
22
|
+
end
|
23
|
+
|
19
24
|
def self.add_destroy_callbacks(model, reflection)
|
20
25
|
super unless reflection.options[:through]
|
21
26
|
end
|
@@ -26,5 +31,34 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
26
31
|
model.validates_presence_of reflection.name, message: :required
|
27
32
|
end
|
28
33
|
end
|
34
|
+
|
35
|
+
def self.touch_record(o, name, touch)
|
36
|
+
record = o.send name
|
37
|
+
|
38
|
+
return unless record && record.persisted?
|
39
|
+
|
40
|
+
if touch != true
|
41
|
+
record.touch(touch)
|
42
|
+
else
|
43
|
+
record.touch
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.add_touch_callbacks(model, reflection)
|
48
|
+
name = reflection.name
|
49
|
+
touch = reflection.options[:touch]
|
50
|
+
|
51
|
+
callback = lambda { |record|
|
52
|
+
HasOne.touch_record(record, name, touch)
|
53
|
+
}
|
54
|
+
|
55
|
+
model.after_create callback, if: :saved_changes?
|
56
|
+
model.after_update callback, if: :saved_changes?
|
57
|
+
model.after_destroy callback
|
58
|
+
model.after_touch callback
|
59
|
+
end
|
60
|
+
|
61
|
+
private_class_method :macro, :valid_options, :valid_dependent_options, :add_destroy_callbacks,
|
62
|
+
:define_callbacks, :define_validations, :add_touch_callbacks
|
29
63
|
end
|
30
64
|
end
|
@@ -109,9 +109,8 @@ module ActiveRecord
|
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
112
|
-
# Add +records+ to this association.
|
113
|
-
#
|
114
|
-
# +push+ and +concat+ behave identically.
|
112
|
+
# Add +records+ to this association. Since +<<+ flattens its argument list
|
113
|
+
# and inserts each record, +push+ and +concat+ behave identically.
|
115
114
|
def concat(*records)
|
116
115
|
records = records.flatten
|
117
116
|
if owner.new_record?
|
@@ -233,7 +232,7 @@ module ActiveRecord
|
|
233
232
|
# loaded and you are going to fetch the records anyway it is better to
|
234
233
|
# check <tt>collection.length.zero?</tt>.
|
235
234
|
def empty?
|
236
|
-
if loaded? || @association_ids
|
235
|
+
if loaded? || @association_ids || reflection.has_cached_counter?
|
237
236
|
size.zero?
|
238
237
|
else
|
239
238
|
target.empty? && !scope.exists?
|
@@ -347,7 +346,6 @@ module ActiveRecord
|
|
347
346
|
add_to_target(record) do
|
348
347
|
result = insert_record(record, true, raise) {
|
349
348
|
@_was_loaded = loaded?
|
350
|
-
@association_ids = nil
|
351
349
|
}
|
352
350
|
end
|
353
351
|
raise ActiveRecord::Rollback unless result
|
@@ -384,6 +382,7 @@ module ActiveRecord
|
|
384
382
|
|
385
383
|
delete_records(existing_records, method) if existing_records.any?
|
386
384
|
@target -= records
|
385
|
+
@association_ids = nil
|
387
386
|
|
388
387
|
records.each { |record| callback(:after_remove, record) }
|
389
388
|
end
|
@@ -424,7 +423,6 @@ module ActiveRecord
|
|
424
423
|
unless owner.new_record?
|
425
424
|
result &&= insert_record(record, true, raise) {
|
426
425
|
@_was_loaded = loaded?
|
427
|
-
@association_ids = nil
|
428
426
|
}
|
429
427
|
end
|
430
428
|
end
|
@@ -447,6 +445,7 @@ module ActiveRecord
|
|
447
445
|
if index
|
448
446
|
target[index] = record
|
449
447
|
elsif @_was_loaded || !loaded?
|
448
|
+
@association_ids = nil
|
450
449
|
target << record
|
451
450
|
end
|
452
451
|
|
@@ -2,11 +2,8 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Associations
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# object, known as the <tt>@target</tt>. The kind of association any proxy is
|
8
|
-
# about is available in <tt>@reflection</tt>. That's an instance of the class
|
9
|
-
# ActiveRecord::Reflection::AssociationReflection.
|
5
|
+
# Collection proxies in Active Record are middlemen between an
|
6
|
+
# <tt>association</tt>, and its <tt>target</tt> result set.
|
10
7
|
#
|
11
8
|
# For example, given
|
12
9
|
#
|
@@ -16,14 +13,14 @@ module ActiveRecord
|
|
16
13
|
#
|
17
14
|
# blog = Blog.first
|
18
15
|
#
|
19
|
-
#
|
20
|
-
# <tt
|
21
|
-
#
|
16
|
+
# The collection proxy returned by <tt>blog.posts</tt> is built from a
|
17
|
+
# <tt>:has_many</tt> <tt>association</tt>, and delegates to a collection
|
18
|
+
# of posts as the <tt>target</tt>.
|
22
19
|
#
|
23
|
-
# This class delegates unknown methods to <tt
|
24
|
-
#
|
20
|
+
# This class delegates unknown methods to the <tt>association</tt>'s
|
21
|
+
# relation class via a delegate cache.
|
25
22
|
#
|
26
|
-
# The <tt
|
23
|
+
# The <tt>target</tt> result set is not loaded until needed. For example,
|
27
24
|
#
|
28
25
|
# blog.posts.count
|
29
26
|
#
|
@@ -366,34 +363,6 @@ module ActiveRecord
|
|
366
363
|
@association.create!(attributes, &block)
|
367
364
|
end
|
368
365
|
|
369
|
-
# Add one or more records to the collection by setting their foreign keys
|
370
|
-
# to the association's primary key. Since #<< flattens its argument list and
|
371
|
-
# inserts each record, +push+ and #concat behave identically. Returns +self+
|
372
|
-
# so method calls may be chained.
|
373
|
-
#
|
374
|
-
# class Person < ActiveRecord::Base
|
375
|
-
# has_many :pets
|
376
|
-
# end
|
377
|
-
#
|
378
|
-
# person.pets.size # => 0
|
379
|
-
# person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
|
380
|
-
# person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
|
381
|
-
# person.pets.size # => 3
|
382
|
-
#
|
383
|
-
# person.id # => 1
|
384
|
-
# person.pets
|
385
|
-
# # => [
|
386
|
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
387
|
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
388
|
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
389
|
-
# # ]
|
390
|
-
#
|
391
|
-
# person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
|
392
|
-
# person.pets.size # => 5
|
393
|
-
def concat(*records)
|
394
|
-
@association.concat(*records)
|
395
|
-
end
|
396
|
-
|
397
366
|
# Replaces this collection with +other_array+. This will perform a diff
|
398
367
|
# and delete/add only records that have changed.
|
399
368
|
#
|
@@ -1033,8 +1002,9 @@ module ActiveRecord
|
|
1033
1002
|
end
|
1034
1003
|
|
1035
1004
|
# Adds one or more +records+ to the collection by setting their foreign keys
|
1036
|
-
# to the association's primary key.
|
1037
|
-
#
|
1005
|
+
# to the association's primary key. Since <tt><<</tt> flattens its argument list and
|
1006
|
+
# inserts each record, +push+ and +concat+ behave identically. Returns +self+
|
1007
|
+
# so several appends may be chained together.
|
1038
1008
|
#
|
1039
1009
|
# class Person < ActiveRecord::Base
|
1040
1010
|
# has_many :pets
|
@@ -1057,8 +1027,9 @@ module ActiveRecord
|
|
1057
1027
|
end
|
1058
1028
|
alias_method :push, :<<
|
1059
1029
|
alias_method :append, :<<
|
1030
|
+
alias_method :concat, :<<
|
1060
1031
|
|
1061
|
-
def prepend(*args)
|
1032
|
+
def prepend(*args) # :nodoc:
|
1062
1033
|
raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
|
1063
1034
|
end
|
1064
1035
|
|
@@ -36,14 +36,6 @@ module ActiveRecord
|
|
36
36
|
super
|
37
37
|
end
|
38
38
|
|
39
|
-
def empty?
|
40
|
-
if reflection.has_cached_counter?
|
41
|
-
size.zero?
|
42
|
-
else
|
43
|
-
super
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
39
|
private
|
48
40
|
|
49
41
|
# Returns the number of records in this collection.
|
@@ -69,7 +61,7 @@ module ActiveRecord
|
|
69
61
|
# If there's nothing in the database and @target has no new records
|
70
62
|
# we are certain the current target is an empty array. This is a
|
71
63
|
# documented side-effect of the method that may avoid an extra SELECT.
|
72
|
-
|
64
|
+
loaded! if count == 0
|
73
65
|
|
74
66
|
[association_scope.limit_value, count].compact.min
|
75
67
|
end
|
@@ -57,21 +57,14 @@ module ActiveRecord
|
|
57
57
|
@through_records[record.object_id] ||= begin
|
58
58
|
ensure_mutable
|
59
59
|
|
60
|
-
|
61
|
-
|
60
|
+
attributes = through_scope_attributes
|
61
|
+
attributes[source_reflection.name] = record
|
62
|
+
attributes[source_reflection.foreign_type] = options[:source_type] if options[:source_type]
|
62
63
|
|
63
|
-
|
64
|
-
through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
|
65
|
-
end
|
66
|
-
|
67
|
-
through_record
|
64
|
+
through_association.build(attributes)
|
68
65
|
end
|
69
66
|
end
|
70
67
|
|
71
|
-
def options_for_through_record
|
72
|
-
[through_scope_attributes]
|
73
|
-
end
|
74
|
-
|
75
68
|
def through_scope_attributes
|
76
69
|
scope.where_values_hash(through_association.reflection.name.to_s).
|
77
70
|
except!(through_association.reflection.foreign_key,
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_record/associations/join_dependency/join_part"
|
4
|
+
require "active_support/core_ext/array/extract"
|
4
5
|
|
5
6
|
module ActiveRecord
|
6
7
|
module Associations
|
@@ -30,17 +31,20 @@ module ActiveRecord
|
|
30
31
|
table = tables[-i]
|
31
32
|
klass = reflection.klass
|
32
33
|
|
33
|
-
|
34
|
+
join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
|
34
35
|
|
35
|
-
joins << table.create_join(table, table.create_on(constraint), join_type)
|
36
|
-
|
37
|
-
join_scope = reflection.join_scope(table, foreign_klass)
|
38
36
|
arel = join_scope.arel(alias_tracker.aliases)
|
37
|
+
nodes = arel.constraints.first
|
38
|
+
|
39
|
+
others = nodes.children.extract! do |node|
|
40
|
+
Arel.fetch_attribute(node) { |attr| attr.relation.name != table.name }
|
41
|
+
end
|
42
|
+
|
43
|
+
joins << table.create_join(table, table.create_on(nodes), join_type)
|
39
44
|
|
40
|
-
|
45
|
+
unless others.empty?
|
41
46
|
joins.concat arel.join_sources
|
42
|
-
|
43
|
-
right.expr = right.expr.and(arel.constraints)
|
47
|
+
append_constraints(joins.last, others)
|
44
48
|
end
|
45
49
|
|
46
50
|
# The current table in this iteration becomes the foreign table in the next
|
@@ -60,6 +64,16 @@ module ActiveRecord
|
|
60
64
|
|
61
65
|
@readonly = reflection.scope && reflection.scope_for(base_klass.unscoped).readonly_value
|
62
66
|
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def append_constraints(join, constraints)
|
70
|
+
if join.is_a?(Arel::Nodes::StringJoin)
|
71
|
+
join_string = table.create_and(constraints.unshift(join.left))
|
72
|
+
join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
|
73
|
+
else
|
74
|
+
join.right.expr.children.concat(constraints)
|
75
|
+
end
|
76
|
+
end
|
63
77
|
end
|
64
78
|
end
|
65
79
|
end
|
@@ -64,16 +64,17 @@ module ActiveRecord
|
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
-
def initialize(base, table, associations)
|
67
|
+
def initialize(base, table, associations, join_type)
|
68
68
|
tree = self.class.make_tree associations
|
69
69
|
@join_root = JoinBase.new(base, table, build(tree, base))
|
70
|
+
@join_type = join_type
|
70
71
|
end
|
71
72
|
|
72
73
|
def reflections
|
73
74
|
join_root.drop(1).map!(&:reflection)
|
74
75
|
end
|
75
76
|
|
76
|
-
def join_constraints(joins_to_add,
|
77
|
+
def join_constraints(joins_to_add, alias_tracker)
|
77
78
|
@alias_tracker = alias_tracker
|
78
79
|
|
79
80
|
construct_tables!(join_root)
|
@@ -82,9 +83,9 @@ module ActiveRecord
|
|
82
83
|
joins.concat joins_to_add.flat_map { |oj|
|
83
84
|
construct_tables!(oj.join_root)
|
84
85
|
if join_root.match? oj.join_root
|
85
|
-
walk join_root, oj.
|
86
|
+
walk(join_root, oj.join_root, oj.join_type)
|
86
87
|
else
|
87
|
-
make_join_constraints(oj.join_root, join_type)
|
88
|
+
make_join_constraints(oj.join_root, oj.join_type)
|
88
89
|
end
|
89
90
|
}
|
90
91
|
end
|
@@ -125,7 +126,7 @@ module ActiveRecord
|
|
125
126
|
end
|
126
127
|
|
127
128
|
protected
|
128
|
-
attr_reader :join_root
|
129
|
+
attr_reader :join_root, :join_type
|
129
130
|
|
130
131
|
private
|
131
132
|
attr_reader :alias_tracker
|
@@ -151,7 +152,7 @@ module ActiveRecord
|
|
151
152
|
end
|
152
153
|
end
|
153
154
|
|
154
|
-
def make_constraints(parent, child, join_type
|
155
|
+
def make_constraints(parent, child, join_type)
|
155
156
|
foreign_table = parent.table
|
156
157
|
foreign_klass = parent.base_klass
|
157
158
|
joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
|
@@ -173,13 +174,13 @@ module ActiveRecord
|
|
173
174
|
join ? "#{name}_join" : name
|
174
175
|
end
|
175
176
|
|
176
|
-
def walk(left, right)
|
177
|
+
def walk(left, right, join_type)
|
177
178
|
intersection, missing = right.children.map { |node1|
|
178
179
|
[left.children.find { |node2| node1.match? node2 }, node1]
|
179
180
|
}.partition(&:first)
|
180
181
|
|
181
|
-
joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r) }
|
182
|
-
joins.concat missing.flat_map { |_, n| make_constraints(left, n) }
|
182
|
+
joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r, join_type) }
|
183
|
+
joins.concat missing.flat_map { |_, n| make_constraints(left, n, join_type) }
|
183
184
|
end
|
184
185
|
|
185
186
|
def find_reflection(klass, name)
|
@@ -4,29 +4,43 @@ module ActiveRecord
|
|
4
4
|
module Associations
|
5
5
|
class Preloader
|
6
6
|
class Association #:nodoc:
|
7
|
-
attr_reader :preloaded_records
|
8
|
-
|
9
7
|
def initialize(klass, owners, reflection, preload_scope)
|
10
8
|
@klass = klass
|
11
9
|
@owners = owners
|
12
10
|
@reflection = reflection
|
13
11
|
@preload_scope = preload_scope
|
14
12
|
@model = owners.first && owners.first.class
|
15
|
-
@preloaded_records = []
|
16
13
|
end
|
17
14
|
|
18
|
-
def run
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
15
|
+
def run
|
16
|
+
if !preload_scope || preload_scope.empty_scope?
|
17
|
+
owners.each do |owner|
|
18
|
+
associate_records_to_owner(owner, records_by_owner[owner] || [])
|
19
|
+
end
|
20
|
+
else
|
21
|
+
# Custom preload scope is used and
|
22
|
+
# the association can not be marked as loaded
|
23
|
+
# Loading into a Hash instead
|
24
|
+
records_by_owner
|
23
25
|
end
|
26
|
+
self
|
27
|
+
end
|
24
28
|
|
25
|
-
|
26
|
-
|
29
|
+
def records_by_owner
|
30
|
+
# owners can be duplicated when a relation has a collection association join
|
31
|
+
# #compare_by_identity makes such owners different hash keys
|
32
|
+
@records_by_owner ||= preloaded_records.each_with_object({}.compare_by_identity) do |record, result|
|
33
|
+
owners_by_key[convert_key(record[association_key_name])].each do |owner|
|
34
|
+
(result[owner] ||= []) << record
|
35
|
+
end
|
27
36
|
end
|
28
37
|
end
|
29
38
|
|
39
|
+
def preloaded_records
|
40
|
+
return @preloaded_records if defined?(@preloaded_records)
|
41
|
+
@preloaded_records = owner_keys.empty? ? [] : records_for(owner_keys)
|
42
|
+
end
|
43
|
+
|
30
44
|
private
|
31
45
|
attr_reader :owners, :reflection, :preload_scope, :model, :klass
|
32
46
|
|
@@ -42,11 +56,10 @@ module ActiveRecord
|
|
42
56
|
|
43
57
|
def associate_records_to_owner(owner, records)
|
44
58
|
association = owner.association(reflection.name)
|
45
|
-
association.loaded!
|
46
59
|
if reflection.collection?
|
47
|
-
association.target
|
60
|
+
association.target = records
|
48
61
|
else
|
49
|
-
association.target = records.first
|
62
|
+
association.target = records.first
|
50
63
|
end
|
51
64
|
end
|
52
65
|
|
@@ -55,13 +68,10 @@ module ActiveRecord
|
|
55
68
|
end
|
56
69
|
|
57
70
|
def owners_by_key
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
h[key] = owner if key
|
62
|
-
end
|
71
|
+
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
|
72
|
+
key = convert_key(owner[owner_key_name])
|
73
|
+
(result[key] ||= []) << owner if key
|
63
74
|
end
|
64
|
-
@owners_by_key
|
65
75
|
end
|
66
76
|
|
67
77
|
def key_conversion_required?
|
@@ -88,23 +98,16 @@ module ActiveRecord
|
|
88
98
|
@model.type_for_attribute(owner_key_name).type
|
89
99
|
end
|
90
100
|
|
91
|
-
def
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
end
|
99
|
-
@preloaded_records.group_by do |record|
|
100
|
-
convert_key(record[association_key_name])
|
101
|
+
def records_for(ids)
|
102
|
+
scope.where(association_key_name => ids).load do |record|
|
103
|
+
# Processing only the first owner
|
104
|
+
# because the record is modified but not an owner
|
105
|
+
owner = owners_by_key[convert_key(record[association_key_name])].first
|
106
|
+
association = owner.association(reflection.name)
|
107
|
+
association.set_inverse_instance(record)
|
101
108
|
end
|
102
109
|
end
|
103
110
|
|
104
|
-
def records_for(ids, &block)
|
105
|
-
scope.where(association_key_name => ids).load(&block)
|
106
|
-
end
|
107
|
-
|
108
111
|
def scope
|
109
112
|
@scope ||= build_scope
|
110
113
|
end
|
@@ -116,7 +119,7 @@ module ActiveRecord
|
|
116
119
|
def build_scope
|
117
120
|
scope = klass.scope_for_association
|
118
121
|
|
119
|
-
if reflection.type
|
122
|
+
if reflection.type && !reflection.through_reflection?
|
120
123
|
scope.where!(reflection.type => model.polymorphic_name)
|
121
124
|
end
|
122
125
|
|
@@ -4,42 +4,57 @@ module ActiveRecord
|
|
4
4
|
module Associations
|
5
5
|
class Preloader
|
6
6
|
class ThroughAssociation < Association # :nodoc:
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
7
|
+
PRELOADER = ActiveRecord::Associations::Preloader.new
|
8
|
+
|
9
|
+
def initialize(*)
|
10
|
+
super
|
11
|
+
@already_loaded = owners.first.association(through_reflection.name).loaded?
|
12
|
+
end
|
13
|
+
|
14
|
+
def preloaded_records
|
15
|
+
@preloaded_records ||= source_preloaders.flat_map(&:preloaded_records)
|
16
|
+
end
|
17
|
+
|
18
|
+
def records_by_owner
|
19
|
+
return @records_by_owner if defined?(@records_by_owner)
|
20
|
+
source_records_by_owner = source_preloaders.map(&:records_by_owner).reduce(:merge)
|
21
|
+
through_records_by_owner = through_preloaders.map(&:records_by_owner).reduce(:merge)
|
22
|
+
|
23
|
+
@records_by_owner = owners.each_with_object({}) do |owner, result|
|
24
|
+
through_records = through_records_by_owner[owner] || []
|
25
|
+
|
26
|
+
if @already_loaded
|
19
27
|
if source_type = reflection.options[:source_type]
|
20
28
|
through_records = through_records.select do |record|
|
21
29
|
record[reflection.foreign_type] == source_type
|
22
30
|
end
|
23
31
|
end
|
24
|
-
else
|
25
|
-
owner.association(through_reflection.name).reset if through_scope
|
26
|
-
end
|
27
|
-
result = through_records.flat_map do |record|
|
28
|
-
association = record.association(source_reflection.name)
|
29
|
-
target = association.target
|
30
|
-
association.reset if preload_scope
|
31
|
-
target
|
32
32
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
result.uniq! if reflection_scope.distinct_value
|
33
|
+
|
34
|
+
records = through_records.flat_map do |record|
|
35
|
+
source_records_by_owner[record]
|
37
36
|
end
|
38
|
-
|
37
|
+
|
38
|
+
records.compact!
|
39
|
+
records.sort_by! { |rhs| preload_index[rhs] } if scope.order_values.any?
|
40
|
+
records.uniq! if scope.distinct_value
|
41
|
+
result[owner] = records
|
39
42
|
end
|
40
43
|
end
|
41
44
|
|
42
45
|
private
|
46
|
+
def source_preloaders
|
47
|
+
@source_preloaders ||= PRELOADER.preload(middle_records, source_reflection.name, scope)
|
48
|
+
end
|
49
|
+
|
50
|
+
def middle_records
|
51
|
+
through_preloaders.flat_map(&:preloaded_records)
|
52
|
+
end
|
53
|
+
|
54
|
+
def through_preloaders
|
55
|
+
@through_preloaders ||= PRELOADER.preload(owners, through_reflection.name, through_scope)
|
56
|
+
end
|
57
|
+
|
43
58
|
def through_reflection
|
44
59
|
reflection.through_reflection
|
45
60
|
end
|
@@ -49,8 +64,8 @@ module ActiveRecord
|
|
49
64
|
end
|
50
65
|
|
51
66
|
def preload_index
|
52
|
-
@preload_index ||=
|
53
|
-
result[
|
67
|
+
@preload_index ||= preloaded_records.each_with_object({}).with_index do |(record, result), index|
|
68
|
+
result[record] = index
|
54
69
|
end
|
55
70
|
end
|
56
71
|
|
@@ -58,11 +73,15 @@ module ActiveRecord
|
|
58
73
|
scope = through_reflection.klass.unscoped
|
59
74
|
options = reflection.options
|
60
75
|
|
76
|
+
values = reflection_scope.values
|
77
|
+
if annotations = values[:annotate]
|
78
|
+
scope.annotate!(*annotations)
|
79
|
+
end
|
80
|
+
|
61
81
|
if options[:source_type]
|
62
82
|
scope.where! reflection.foreign_type => options[:source_type]
|
63
83
|
elsif !reflection_scope.where_clause.empty?
|
64
84
|
scope.where_clause = reflection_scope.where_clause
|
65
|
-
values = reflection_scope.values
|
66
85
|
|
67
86
|
if includes = values[:includes]
|
68
87
|
scope.includes!(source_reflection.name => includes)
|
@@ -89,17 +108,7 @@ module ActiveRecord
|
|
89
108
|
end
|
90
109
|
end
|
91
110
|
|
92
|
-
scope
|
93
|
-
end
|
94
|
-
|
95
|
-
def target_reflection_scope
|
96
|
-
if preload_scope
|
97
|
-
reflection_scope.merge(preload_scope)
|
98
|
-
elsif reflection.scope
|
99
|
-
reflection_scope
|
100
|
-
else
|
101
|
-
nil
|
102
|
-
end
|
111
|
+
scope
|
103
112
|
end
|
104
113
|
end
|
105
114
|
end
|