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
@@ -33,7 +33,6 @@ module ActiveRecord
|
|
33
33
|
def initialize(klass, association) #:nodoc:
|
34
34
|
@association = association
|
35
35
|
super klass, klass.arel_table
|
36
|
-
self.default_scoped = true
|
37
36
|
merge! association.scope(nullify: false)
|
38
37
|
end
|
39
38
|
|
@@ -418,13 +417,13 @@ module ActiveRecord
|
|
418
417
|
#
|
419
418
|
# Pet.find(1, 2, 3)
|
420
419
|
# # => ActiveRecord::RecordNotFound
|
421
|
-
def delete_all
|
422
|
-
@association.delete_all
|
420
|
+
def delete_all(dependent = nil)
|
421
|
+
@association.delete_all(dependent)
|
423
422
|
end
|
424
423
|
|
425
|
-
# Deletes the records of the collection directly from the database
|
426
|
-
#
|
427
|
-
#
|
424
|
+
# Deletes the records of the collection directly from the database
|
425
|
+
# ignoring the +:dependent+ option. It invokes +before_remove+,
|
426
|
+
# +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
|
428
427
|
#
|
429
428
|
# class Person < ActiveRecord::Base
|
430
429
|
# has_many :pets
|
@@ -671,6 +670,8 @@ module ActiveRecord
|
|
671
670
|
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
672
671
|
# # ]
|
673
672
|
def count(column_name = nil, options = {})
|
673
|
+
# TODO: Remove options argument as soon we remove support to
|
674
|
+
# activerecord-deprecated_finders.
|
674
675
|
@association.count(column_name, options)
|
675
676
|
end
|
676
677
|
|
@@ -727,7 +728,7 @@ module ActiveRecord
|
|
727
728
|
end
|
728
729
|
|
729
730
|
# Returns +true+ if the collection is empty. If the collection has been
|
730
|
-
# loaded
|
731
|
+
# loaded it is equivalent
|
731
732
|
# to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
|
732
733
|
# it is equivalent to <tt>collection.exists?</tt>. If the collection has
|
733
734
|
# not already been loaded and you are going to fetch the records anyway it
|
@@ -788,12 +789,12 @@ module ActiveRecord
|
|
788
789
|
# has_many :pets
|
789
790
|
# end
|
790
791
|
#
|
791
|
-
# person.pets.count
|
792
|
-
# person.pets.many?
|
792
|
+
# person.pets.count # => 1
|
793
|
+
# person.pets.many? # => false
|
793
794
|
#
|
794
795
|
# person.pets << Pet.new(name: 'Snoopy')
|
795
|
-
# person.pets.count
|
796
|
-
# person.pets.many?
|
796
|
+
# person.pets.count # => 2
|
797
|
+
# person.pets.many? # => true
|
797
798
|
#
|
798
799
|
# You can also pass a block to define criteria. The
|
799
800
|
# behavior is the same, it returns true if the collection
|
@@ -833,10 +834,6 @@ module ActiveRecord
|
|
833
834
|
!!@association.include?(record)
|
834
835
|
end
|
835
836
|
|
836
|
-
def arel
|
837
|
-
scope.arel
|
838
|
-
end
|
839
|
-
|
840
837
|
def proxy_association
|
841
838
|
@association
|
842
839
|
end
|
@@ -9,7 +9,7 @@ module ActiveRecord
|
|
9
9
|
|
10
10
|
def handle_dependency
|
11
11
|
case options[:dependent]
|
12
|
-
when :
|
12
|
+
when :restrict_with_exception
|
13
13
|
raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
|
14
14
|
|
15
15
|
when :restrict_with_error
|
@@ -59,8 +59,6 @@ module ActiveRecord
|
|
59
59
|
def count_records
|
60
60
|
count = if has_cached_counter?
|
61
61
|
owner.send(:read_attribute, cached_counter_attribute_name)
|
62
|
-
elsif options[:counter_sql] || options[:finder_sql]
|
63
|
-
reflection.klass.count_by_sql(custom_counter_sql)
|
64
62
|
else
|
65
63
|
scope.count
|
66
64
|
end
|
@@ -73,15 +71,15 @@ module ActiveRecord
|
|
73
71
|
[association_scope.limit_value, count].compact.min
|
74
72
|
end
|
75
73
|
|
76
|
-
def has_cached_counter?(reflection = reflection
|
74
|
+
def has_cached_counter?(reflection = reflection)
|
77
75
|
owner.attribute_present?(cached_counter_attribute_name(reflection))
|
78
76
|
end
|
79
77
|
|
80
|
-
def cached_counter_attribute_name(reflection = reflection
|
78
|
+
def cached_counter_attribute_name(reflection = reflection)
|
81
79
|
options[:counter_cache] || "#{reflection.name}_count"
|
82
80
|
end
|
83
81
|
|
84
|
-
def update_counter(difference, reflection = reflection
|
82
|
+
def update_counter(difference, reflection = reflection)
|
85
83
|
if has_cached_counter?(reflection)
|
86
84
|
counter = cached_counter_attribute_name(reflection)
|
87
85
|
owner.class.update_counters(owner.id, counter => difference)
|
@@ -100,7 +98,7 @@ module ActiveRecord
|
|
100
98
|
# it will be decremented twice.
|
101
99
|
#
|
102
100
|
# Hence this method.
|
103
|
-
def inverse_updates_counter_cache?(reflection = reflection
|
101
|
+
def inverse_updates_counter_cache?(reflection = reflection)
|
104
102
|
counter_name = cached_counter_attribute_name(reflection)
|
105
103
|
reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
|
106
104
|
inverse_reflection.counter_cache_column == counter_name
|
@@ -110,7 +108,7 @@ module ActiveRecord
|
|
110
108
|
# Deletes the records according to the <tt>:dependent</tt> option.
|
111
109
|
def delete_records(records, method)
|
112
110
|
if method == :destroy
|
113
|
-
records.each
|
111
|
+
records.each(&:destroy!)
|
114
112
|
update_counter(-records.length) unless inverse_updates_counter_cache?
|
115
113
|
else
|
116
114
|
if records == :all
|
@@ -128,7 +126,11 @@ module ActiveRecord
|
|
128
126
|
end
|
129
127
|
|
130
128
|
def foreign_key_present?
|
131
|
-
|
129
|
+
if reflection.klass.primary_key
|
130
|
+
owner.attribute_present?(reflection.association_primary_key)
|
131
|
+
else
|
132
|
+
false
|
133
|
+
end
|
132
134
|
end
|
133
135
|
end
|
134
136
|
end
|
@@ -84,22 +84,12 @@ module ActiveRecord
|
|
84
84
|
@through_records[record.object_id] ||= begin
|
85
85
|
ensure_mutable
|
86
86
|
|
87
|
-
through_record = through_association.build
|
87
|
+
through_record = through_association.build
|
88
88
|
through_record.send("#{source_reflection.name}=", record)
|
89
89
|
through_record
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
|
-
def options_for_through_record
|
94
|
-
[through_scope_attributes]
|
95
|
-
end
|
96
|
-
|
97
|
-
def through_scope_attributes
|
98
|
-
scope.where_values_hash(through_association.reflection.name.to_s).
|
99
|
-
except!(through_association.reflection.foreign_key,
|
100
|
-
through_association.reflection.klass.inheritance_column.to_sym)
|
101
|
-
end
|
102
|
-
|
103
93
|
def save_through_record(record)
|
104
94
|
build_through_record(record).save!
|
105
95
|
ensure
|
@@ -150,7 +140,21 @@ module ActiveRecord
|
|
150
140
|
|
151
141
|
case method
|
152
142
|
when :destroy
|
153
|
-
|
143
|
+
if scope.klass.primary_key
|
144
|
+
count = scope.destroy_all.length
|
145
|
+
else
|
146
|
+
scope.to_a.each do |record|
|
147
|
+
record.run_callbacks :destroy
|
148
|
+
end
|
149
|
+
|
150
|
+
arel = scope.arel
|
151
|
+
|
152
|
+
stmt = Arel::DeleteManager.new arel.engine
|
153
|
+
stmt.from scope.klass.arel_table
|
154
|
+
stmt.wheres = arel.constraints
|
155
|
+
|
156
|
+
count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values)
|
157
|
+
end
|
154
158
|
when :nullify
|
155
159
|
count = scope.update_all(source_reflection.foreign_key => nil)
|
156
160
|
else
|
@@ -1,15 +1,79 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Associations
|
3
3
|
class JoinDependency # :nodoc:
|
4
|
-
autoload :JoinPart, 'active_record/associations/join_dependency/join_part'
|
5
4
|
autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
|
6
5
|
autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
|
7
6
|
|
8
|
-
|
7
|
+
class Aliases # :nodoc:
|
8
|
+
def initialize(tables)
|
9
|
+
@tables = tables
|
10
|
+
@alias_cache = tables.each_with_object({}) { |table,h|
|
11
|
+
h[table.node] = table.columns.each_with_object({}) { |column,i|
|
12
|
+
i[column.name] = column.alias
|
13
|
+
}
|
14
|
+
}
|
15
|
+
@name_and_alias_cache = tables.each_with_object({}) { |table,h|
|
16
|
+
h[table.node] = table.columns.map { |column|
|
17
|
+
[column.name, column.alias]
|
18
|
+
}
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def columns
|
23
|
+
@tables.flat_map { |t| t.column_aliases }
|
24
|
+
end
|
25
|
+
|
26
|
+
# An array of [column_name, alias] pairs for the table
|
27
|
+
def column_aliases(node)
|
28
|
+
@name_and_alias_cache[node]
|
29
|
+
end
|
30
|
+
|
31
|
+
def column_alias(node, column)
|
32
|
+
@alias_cache[node][column]
|
33
|
+
end
|
34
|
+
|
35
|
+
class Table < Struct.new(:node, :columns)
|
36
|
+
def table
|
37
|
+
Arel::Nodes::TableAlias.new node.table, node.aliased_table_name
|
38
|
+
end
|
39
|
+
|
40
|
+
def column_aliases
|
41
|
+
t = table
|
42
|
+
columns.map { |column| t[column.name].as Arel.sql column.alias }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
Column = Struct.new(:name, :alias)
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :alias_tracker, :base_klass, :join_root
|
49
|
+
|
50
|
+
def self.make_tree(associations)
|
51
|
+
hash = {}
|
52
|
+
walk_tree associations, hash
|
53
|
+
hash
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.walk_tree(associations, hash)
|
57
|
+
case associations
|
58
|
+
when Symbol, String
|
59
|
+
hash[associations.to_sym] ||= {}
|
60
|
+
when Array
|
61
|
+
associations.each do |assoc|
|
62
|
+
walk_tree assoc, hash
|
63
|
+
end
|
64
|
+
when Hash
|
65
|
+
associations.each do |k,v|
|
66
|
+
cache = hash[k] ||= {}
|
67
|
+
walk_tree v, cache
|
68
|
+
end
|
69
|
+
else
|
70
|
+
raise ConfigurationError, associations.inspect
|
71
|
+
end
|
72
|
+
end
|
9
73
|
|
10
74
|
# base is the base class on which operation is taking place.
|
11
75
|
# associations is the list of associations which are joined using hash, symbol or array.
|
12
|
-
# joins is the list of all string join
|
76
|
+
# joins is the list of all string join commands and arel nodes.
|
13
77
|
#
|
14
78
|
# Example :
|
15
79
|
#
|
@@ -19,216 +83,191 @@ module ActiveRecord
|
|
19
83
|
# end
|
20
84
|
#
|
21
85
|
# If I execute `@physician.patients.to_a` then
|
22
|
-
# base
|
23
|
-
# associations
|
24
|
-
# joins
|
86
|
+
# base # => Physician
|
87
|
+
# associations # => []
|
88
|
+
# joins # => [#<Arel::Nodes::InnerJoin: ...]
|
25
89
|
#
|
26
90
|
# However if I execute `Physician.joins(:appointments).to_a` then
|
27
|
-
# base
|
28
|
-
# associations
|
29
|
-
# joins
|
91
|
+
# base # => Physician
|
92
|
+
# associations # => [:appointments]
|
93
|
+
# joins # => []
|
30
94
|
#
|
31
95
|
def initialize(base, associations, joins)
|
32
|
-
@base_klass = base
|
33
|
-
@table_joins = joins
|
34
|
-
@join_parts = [JoinBase.new(base)]
|
35
|
-
@associations = {}
|
36
|
-
@reflections = []
|
37
96
|
@alias_tracker = AliasTracker.new(base.connection, joins)
|
38
97
|
@alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
|
39
|
-
|
98
|
+
tree = self.class.make_tree associations
|
99
|
+
@join_root = JoinBase.new base, build(tree, base)
|
100
|
+
@join_root.children.each { |child| construct_tables! @join_root, child }
|
40
101
|
end
|
41
102
|
|
42
|
-
def
|
43
|
-
|
44
|
-
join_associations.detect {|a| association == a} ||
|
45
|
-
build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
|
46
|
-
end
|
47
|
-
self
|
103
|
+
def reflections
|
104
|
+
join_root.drop(1).map!(&:reflection)
|
48
105
|
end
|
49
106
|
|
50
|
-
def
|
51
|
-
|
52
|
-
|
107
|
+
def join_constraints(outer_joins)
|
108
|
+
joins = join_root.children.flat_map { |child|
|
109
|
+
make_inner_joins join_root, child
|
110
|
+
}
|
53
111
|
|
54
|
-
|
55
|
-
|
112
|
+
joins.concat outer_joins.flat_map { |oj|
|
113
|
+
if join_root.match? oj.join_root
|
114
|
+
walk join_root, oj.join_root
|
115
|
+
else
|
116
|
+
oj.join_root.children.flat_map { |child|
|
117
|
+
make_outer_joins join_root, child
|
118
|
+
}
|
119
|
+
end
|
120
|
+
}
|
56
121
|
end
|
57
122
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
table[column_name].as Arel.sql(aliased_name)
|
123
|
+
def aliases
|
124
|
+
Aliases.new join_root.each_with_index.map { |join_part,i|
|
125
|
+
columns = join_part.column_names.each_with_index.map { |column_name,j|
|
126
|
+
Aliases::Column.new column_name, "t#{i}_r#{j}"
|
63
127
|
}
|
64
|
-
|
128
|
+
Aliases::Table.new(join_part, columns)
|
129
|
+
}
|
65
130
|
end
|
66
131
|
|
67
|
-
def instantiate(
|
68
|
-
primary_key =
|
69
|
-
|
70
|
-
|
71
|
-
records = rows.map { |model|
|
72
|
-
primary_id = model[primary_key]
|
73
|
-
parent = parents[primary_id] ||= join_base.instantiate(model)
|
74
|
-
construct(parent, @associations, join_associations, model)
|
75
|
-
parent
|
76
|
-
}.uniq
|
132
|
+
def instantiate(result_set, aliases)
|
133
|
+
primary_key = aliases.column_alias(join_root, join_root.primary_key)
|
134
|
+
type_caster = result_set.column_type primary_key
|
77
135
|
|
78
|
-
|
79
|
-
|
80
|
-
|
136
|
+
seen = Hash.new { |h,parent_klass|
|
137
|
+
h[parent_klass] = Hash.new { |i,parent_id|
|
138
|
+
i[parent_id] = Hash.new { |j,child_klass| j[child_klass] = {} }
|
139
|
+
}
|
140
|
+
}
|
81
141
|
|
82
|
-
|
142
|
+
model_cache = Hash.new { |h,klass| h[klass] = {} }
|
143
|
+
parents = model_cache[join_root]
|
144
|
+
column_aliases = aliases.column_aliases join_root
|
83
145
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
when Array
|
90
|
-
associations.each do |association|
|
91
|
-
remove_duplicate_results!(base, records, association)
|
92
|
-
end
|
93
|
-
when Hash
|
94
|
-
associations.each_key do |name|
|
95
|
-
reflection = base.reflections[name]
|
96
|
-
remove_uniq_by_reflection(reflection, records)
|
97
|
-
|
98
|
-
parent_records = []
|
99
|
-
records.each do |record|
|
100
|
-
if descendant = record.send(reflection.name)
|
101
|
-
if reflection.collection?
|
102
|
-
parent_records.concat descendant.target.uniq
|
103
|
-
else
|
104
|
-
parent_records << descendant
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
146
|
+
result_set.each { |row_hash|
|
147
|
+
primary_id = type_caster.type_cast row_hash[primary_key]
|
148
|
+
parent = parents[primary_id] ||= join_root.instantiate(row_hash, column_aliases)
|
149
|
+
construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
|
150
|
+
}
|
108
151
|
|
109
|
-
|
110
|
-
end
|
111
|
-
end
|
152
|
+
parents.values
|
112
153
|
end
|
113
154
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
ref = @associations
|
122
|
-
associations.each do |key|
|
123
|
-
ref = ref[key]
|
124
|
-
end
|
125
|
-
ref[association.reflection.name] ||= {}
|
155
|
+
private
|
156
|
+
|
157
|
+
def make_constraints(parent, child, tables, join_type)
|
158
|
+
chain = child.reflection.chain
|
159
|
+
foreign_table = parent.table
|
160
|
+
foreign_klass = parent.base_klass
|
161
|
+
child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, child.reflection.scope_chain, chain)
|
126
162
|
end
|
127
163
|
|
128
|
-
def
|
129
|
-
parent
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
unless join_association = find_join_association(reflection, parent)
|
135
|
-
@reflections << reflection
|
136
|
-
join_association = build_join_association(reflection, parent)
|
137
|
-
join_association.join_type = join_type
|
138
|
-
@join_parts << join_association
|
139
|
-
cache_joined_association(join_association)
|
140
|
-
end
|
141
|
-
join_association
|
142
|
-
when Array
|
143
|
-
associations.each do |association|
|
144
|
-
build(association, parent, join_type)
|
145
|
-
end
|
146
|
-
when Hash
|
147
|
-
associations.keys.sort_by { |a| a.to_s }.each do |name|
|
148
|
-
join_association = build(name, parent, join_type)
|
149
|
-
build(associations[name], join_association, join_type)
|
150
|
-
end
|
151
|
-
else
|
152
|
-
raise ConfigurationError, associations.inspect
|
153
|
-
end
|
164
|
+
def make_outer_joins(parent, child)
|
165
|
+
tables = table_aliases_for(parent, child)
|
166
|
+
join_type = Arel::OuterJoin
|
167
|
+
joins = make_constraints parent, child, tables, join_type
|
168
|
+
|
169
|
+
joins.concat child.children.flat_map { |c| make_outer_joins(child, c) }
|
154
170
|
end
|
155
171
|
|
156
|
-
def
|
157
|
-
|
158
|
-
|
159
|
-
|
172
|
+
def make_inner_joins(parent, child)
|
173
|
+
tables = child.tables
|
174
|
+
join_type = Arel::InnerJoin
|
175
|
+
joins = make_constraints parent, child, tables, join_type
|
160
176
|
|
161
|
-
|
162
|
-
|
177
|
+
joins.concat child.children.flat_map { |c| make_inner_joins(child, c) }
|
178
|
+
end
|
179
|
+
|
180
|
+
def table_aliases_for(parent, node)
|
181
|
+
node.reflection.chain.map { |reflection|
|
182
|
+
alias_tracker.aliased_table_for(
|
183
|
+
reflection.table_name,
|
184
|
+
table_alias_for(reflection, parent, reflection != node.reflection)
|
185
|
+
)
|
163
186
|
}
|
164
187
|
end
|
165
188
|
|
166
|
-
def
|
167
|
-
|
168
|
-
|
169
|
-
end
|
189
|
+
def construct_tables!(parent, node)
|
190
|
+
node.tables = table_aliases_for(parent, node)
|
191
|
+
node.children.each { |child| construct_tables! node, child }
|
170
192
|
end
|
171
193
|
|
172
|
-
def
|
173
|
-
|
194
|
+
def table_alias_for(reflection, parent, join)
|
195
|
+
name = "#{reflection.plural_name}_#{parent.table_name}"
|
196
|
+
name << "_join" if join
|
197
|
+
name
|
174
198
|
end
|
175
199
|
|
176
|
-
def
|
177
|
-
|
178
|
-
|
179
|
-
|
200
|
+
def walk(left, right)
|
201
|
+
intersection, missing = right.children.map { |node1|
|
202
|
+
[left.children.find { |node2| node1.match? node2 }, node1]
|
203
|
+
}.partition(&:first)
|
180
204
|
|
181
|
-
|
182
|
-
|
183
|
-
|
205
|
+
ojs = missing.flat_map { |_,n| make_outer_joins left, n }
|
206
|
+
intersection.flat_map { |l,r| walk l, r }.concat ojs
|
207
|
+
end
|
184
208
|
|
185
|
-
|
209
|
+
def find_reflection(klass, name)
|
210
|
+
klass.reflect_on_association(name) or
|
211
|
+
raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
|
212
|
+
end
|
186
213
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
associations.sort_by { |k,_| k.to_s }.each do |association_name, assoc|
|
195
|
-
association = construct(parent, association_name, join_parts, row)
|
196
|
-
construct(association, assoc, join_parts, row) if association
|
214
|
+
def build(associations, base_klass)
|
215
|
+
associations.map do |name, right|
|
216
|
+
reflection = find_reflection base_klass, name
|
217
|
+
reflection.check_validity!
|
218
|
+
|
219
|
+
if reflection.options[:polymorphic]
|
220
|
+
raise EagerLoadPolymorphicError.new(reflection)
|
197
221
|
end
|
198
|
-
|
199
|
-
|
222
|
+
|
223
|
+
JoinAssociation.new reflection, build(right, reflection.klass)
|
200
224
|
end
|
201
225
|
end
|
202
226
|
|
203
|
-
def
|
204
|
-
|
227
|
+
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
|
228
|
+
primary_id = ar_parent.id
|
205
229
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
|
210
|
-
set_target_and_inverse(join_part, association, record)
|
211
|
-
else
|
212
|
-
association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
|
213
|
-
case macro
|
214
|
-
when :has_many, :has_and_belongs_to_many
|
215
|
-
other = record.association(join_part.reflection.name)
|
230
|
+
parent.children.each do |node|
|
231
|
+
if node.reflection.collection?
|
232
|
+
other = ar_parent.association(node.reflection.name)
|
216
233
|
other.loaded!
|
217
|
-
other.target.push(association) if association
|
218
|
-
other.set_inverse_instance(association)
|
219
|
-
when :belongs_to
|
220
|
-
set_target_and_inverse(join_part, association, record)
|
221
234
|
else
|
222
|
-
|
235
|
+
if ar_parent.association_cache.key?(node.reflection.name)
|
236
|
+
model = ar_parent.association(node.reflection.name).target
|
237
|
+
construct(model, node, row, rs, seen, model_cache, aliases)
|
238
|
+
next
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
key = aliases.column_alias(node, node.primary_key)
|
243
|
+
id = row[key]
|
244
|
+
next if id.nil?
|
245
|
+
|
246
|
+
model = seen[parent.base_klass][primary_id][node.base_klass][id]
|
247
|
+
|
248
|
+
if model
|
249
|
+
construct(model, node, row, rs, seen, model_cache, aliases)
|
250
|
+
else
|
251
|
+
model = construct_model(ar_parent, node, row, model_cache, id, aliases)
|
252
|
+
seen[parent.base_klass][primary_id][node.base_klass][id] = model
|
253
|
+
construct(model, node, row, rs, seen, model_cache, aliases)
|
223
254
|
end
|
224
255
|
end
|
225
|
-
association
|
226
256
|
end
|
227
257
|
|
228
|
-
def
|
229
|
-
|
230
|
-
|
231
|
-
other.
|
258
|
+
def construct_model(record, node, row, model_cache, id, aliases)
|
259
|
+
model = model_cache[node][id] ||= node.instantiate(row,
|
260
|
+
aliases.column_aliases(node))
|
261
|
+
other = record.association(node.reflection.name)
|
262
|
+
|
263
|
+
if node.reflection.collection?
|
264
|
+
other.target.push(model)
|
265
|
+
else
|
266
|
+
other.target = model
|
267
|
+
end
|
268
|
+
|
269
|
+
other.set_inverse_instance(model)
|
270
|
+
model
|
232
271
|
end
|
233
272
|
end
|
234
273
|
end
|