activerecord 3.1.10 → 4.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +6 -6
- data/CHANGELOG.md +1837 -338
- data/MIT-LICENSE +1 -1
- data/README.rdoc +39 -43
- data/examples/performance.rb +51 -20
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +57 -43
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -39
- data/lib/active_record/associations/association.rb +71 -85
- data/lib/active_record/associations/association_scope.rb +138 -89
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
- data/lib/active_record/associations/builder/association.rb +125 -29
- data/lib/active_record/associations/builder/belongs_to.rb +91 -60
- data/lib/active_record/associations/builder/collection_association.rb +69 -49
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +12 -52
- data/lib/active_record/associations/builder/singular_association.rb +22 -29
- data/lib/active_record/associations/collection_association.rb +294 -187
- data/lib/active_record/associations/collection_proxy.rb +961 -94
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +118 -23
- data/lib/active_record/associations/has_many_through_association.rb +115 -45
- data/lib/active_record/associations/has_one_association.rb +57 -24
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -102
- data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
- data/lib/active_record/associations/join_dependency.rb +230 -156
- data/lib/active_record/associations/preloader/association.rb +96 -55
- data/lib/active_record/associations/preloader/collection_association.rb +3 -3
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +61 -32
- data/lib/active_record/associations/preloader.rb +113 -87
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +37 -19
- data/lib/active_record/associations.rb +505 -371
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +212 -0
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +141 -51
- data/lib/active_record/attribute_methods/primary_key.rb +87 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +74 -117
- data/lib/active_record/attribute_methods/serialization.rb +70 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
- data/lib/active_record/attribute_methods/write.rb +60 -21
- data/lib/active_record/attribute_methods.rb +409 -48
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +279 -232
- data/lib/active_record/base.rb +84 -1969
- data/lib/active_record/callbacks.rb +66 -28
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +422 -243
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +273 -170
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
- data/lib/active_record/connection_adapters/column.rb +33 -221
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
- data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +445 -902
- data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +159 -102
- data/lib/active_record/dynamic_matchers.rb +140 -0
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +102 -34
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +318 -260
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +247 -0
- data/lib/active_record/integration.rb +113 -0
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +80 -52
- data/lib/active_record/locking/pessimistic.rb +27 -5
- data/lib/active_record/log_subscriber.rb +25 -18
- data/lib/active_record/migration/command_recorder.rb +130 -38
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +532 -201
- data/lib/active_record/model_schema.rb +342 -0
- data/lib/active_record/nested_attributes.rb +229 -139
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +304 -99
- data/lib/active_record/query_cache.rb +25 -43
- data/lib/active_record/querying.rb +68 -0
- data/lib/active_record/railtie.rb +86 -45
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +7 -4
- data/lib/active_record/railties/databases.rake +198 -377
- data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
- data/lib/active_record/readonly_attributes.rb +23 -0
- data/lib/active_record/reflection.rb +516 -165
- data/lib/active_record/relation/batches.rb +96 -45
- data/lib/active_record/relation/calculations.rb +221 -144
- data/lib/active_record/relation/delegation.rb +140 -0
- data/lib/active_record/relation/finder_methods.rb +362 -243
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +135 -41
- data/lib/active_record/relation/query_methods.rb +982 -155
- data/lib/active_record/relation/spawn_methods.rb +50 -110
- data/lib/active_record/relation.rb +371 -180
- data/lib/active_record/result.rb +109 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +191 -0
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +111 -61
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +135 -0
- data/lib/active_record/scoping/named.rb +164 -0
- data/lib/active_record/scoping.rb +87 -0
- data/lib/active_record/serialization.rb +7 -45
- data/lib/active_record/serializers/xml_serializer.rb +14 -65
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +205 -0
- data/lib/active_record/tasks/database_tasks.rb +299 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +35 -14
- data/lib/active_record/transactions.rb +141 -74
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/integer.rb +59 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +62 -0
- data/lib/active_record/type/string.rb +40 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +110 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +27 -18
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +125 -66
- data/lib/active_record/validations.rb +37 -30
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +80 -25
- data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +132 -53
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
- data/lib/active_record/dynamic_finder_match.rb +0 -56
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/identity_map.rb +0 -163
- data/lib/active_record/named_scope.rb +0 -200
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -358
- data/lib/active_record/test_case.rb +0 -69
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,213 +1,287 @@
|
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
@reflections = []
|
16
|
-
@alias_tracker = AliasTracker.new(joins)
|
17
|
-
@alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
|
18
|
-
build(associations)
|
19
|
-
end
|
20
|
-
|
21
|
-
def graft(*associations)
|
22
|
-
associations.each do |association|
|
23
|
-
join_associations.detect {|a| association == a} ||
|
24
|
-
build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
|
25
|
-
end
|
26
|
-
self
|
27
|
-
end
|
28
|
-
|
29
|
-
def join_associations
|
30
|
-
join_parts.last(join_parts.length - 1)
|
31
|
-
end
|
32
|
-
|
33
|
-
def join_base
|
34
|
-
join_parts.first
|
35
|
-
end
|
36
|
-
|
37
|
-
def columns
|
38
|
-
join_parts.collect { |join_part|
|
39
|
-
table = join_part.aliased_table
|
40
|
-
join_part.column_names_with_alias.collect{ |column_name, aliased_name|
|
41
|
-
table[column_name].as Arel.sql(aliased_name)
|
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
|
+
}
|
42
14
|
}
|
43
|
-
|
44
|
-
|
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
|
45
21
|
|
46
|
-
|
47
|
-
|
48
|
-
|
22
|
+
def columns
|
23
|
+
@tables.flat_map { |t| t.column_aliases }
|
24
|
+
end
|
49
25
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
parent
|
55
|
-
}.uniq
|
26
|
+
# An array of [column_name, alias] pairs for the table
|
27
|
+
def column_aliases(node)
|
28
|
+
@name_and_alias_cache[node]
|
29
|
+
end
|
56
30
|
|
57
|
-
|
58
|
-
|
59
|
-
|
31
|
+
def column_alias(node, column)
|
32
|
+
@alias_cache[node][column]
|
33
|
+
end
|
60
34
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
reflection = base.reflections[associations]
|
65
|
-
remove_uniq_by_reflection(reflection, records)
|
66
|
-
when Array
|
67
|
-
associations.each do |association|
|
68
|
-
remove_duplicate_results!(base, records, association)
|
35
|
+
class Table < Struct.new(:node, :columns)
|
36
|
+
def table
|
37
|
+
Arel::Nodes::TableAlias.new node.table, node.aliased_table_name
|
69
38
|
end
|
70
|
-
when Hash
|
71
|
-
associations.keys.each do |name|
|
72
|
-
reflection = base.reflections[name]
|
73
|
-
remove_uniq_by_reflection(reflection, records)
|
74
|
-
|
75
|
-
parent_records = []
|
76
|
-
records.each do |record|
|
77
|
-
if descendant = record.send(reflection.name)
|
78
|
-
if reflection.collection?
|
79
|
-
parent_records.concat descendant.target.uniq
|
80
|
-
else
|
81
|
-
parent_records << descendant
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
39
|
|
86
|
-
|
40
|
+
def column_aliases
|
41
|
+
t = table
|
42
|
+
columns.map { |column| t[column.name].as Arel.sql column.alias }
|
87
43
|
end
|
88
44
|
end
|
45
|
+
Column = Struct.new(:name, :alias)
|
89
46
|
end
|
90
47
|
|
91
|
-
|
48
|
+
attr_reader :alias_tracker, :base_klass, :join_root
|
92
49
|
|
93
|
-
def
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
associations.unshift(parent.reflection.name)
|
98
|
-
parent = parent.parent
|
99
|
-
end
|
100
|
-
ref = @associations
|
101
|
-
associations.each do |key|
|
102
|
-
ref = ref[key]
|
103
|
-
end
|
104
|
-
ref[association.reflection.name] ||= {}
|
50
|
+
def self.make_tree(associations)
|
51
|
+
hash = {}
|
52
|
+
walk_tree associations, hash
|
53
|
+
hash
|
105
54
|
end
|
106
55
|
|
107
|
-
def
|
108
|
-
parent ||= join_parts.last
|
56
|
+
def self.walk_tree(associations, hash)
|
109
57
|
case associations
|
110
58
|
when Symbol, String
|
111
|
-
|
112
|
-
raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
|
113
|
-
unless join_association = find_join_association(reflection, parent)
|
114
|
-
@reflections << reflection
|
115
|
-
join_association = build_join_association(reflection, parent)
|
116
|
-
join_association.join_type = join_type
|
117
|
-
@join_parts << join_association
|
118
|
-
cache_joined_association(join_association)
|
119
|
-
end
|
120
|
-
join_association
|
59
|
+
hash[associations.to_sym] ||= {}
|
121
60
|
when Array
|
122
|
-
associations.each do |
|
123
|
-
|
61
|
+
associations.each do |assoc|
|
62
|
+
walk_tree assoc, hash
|
124
63
|
end
|
125
64
|
when Hash
|
126
|
-
associations.
|
127
|
-
|
128
|
-
|
65
|
+
associations.each do |k,v|
|
66
|
+
cache = hash[k] ||= {}
|
67
|
+
walk_tree v, cache
|
129
68
|
end
|
130
69
|
else
|
131
70
|
raise ConfigurationError, associations.inspect
|
132
71
|
end
|
133
72
|
end
|
134
73
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
74
|
+
# base is the base class on which operation is taking place.
|
75
|
+
# associations is the list of associations which are joined using hash, symbol or array.
|
76
|
+
# joins is the list of all string join commands and arel nodes.
|
77
|
+
#
|
78
|
+
# Example :
|
79
|
+
#
|
80
|
+
# class Physician < ActiveRecord::Base
|
81
|
+
# has_many :appointments
|
82
|
+
# has_many :patients, through: :appointments
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# If I execute `@physician.patients.to_a` then
|
86
|
+
# base # => Physician
|
87
|
+
# associations # => []
|
88
|
+
# joins # => [#<Arel::Nodes::InnerJoin: ...]
|
89
|
+
#
|
90
|
+
# However if I execute `Physician.joins(:appointments).to_a` then
|
91
|
+
# base # => Physician
|
92
|
+
# associations # => [:appointments]
|
93
|
+
# joins # => []
|
94
|
+
#
|
95
|
+
def initialize(base, associations, joins)
|
96
|
+
@alias_tracker = AliasTracker.create(base.connection, joins)
|
97
|
+
@alias_tracker.aliased_table_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
|
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 }
|
101
|
+
end
|
139
102
|
|
140
|
-
|
141
|
-
|
103
|
+
def reflections
|
104
|
+
join_root.drop(1).map!(&:reflection)
|
105
|
+
end
|
106
|
+
|
107
|
+
def join_constraints(outer_joins)
|
108
|
+
joins = join_root.children.flat_map { |child|
|
109
|
+
make_inner_joins join_root, child
|
110
|
+
}
|
111
|
+
|
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 oj.join_root, child
|
118
|
+
}
|
119
|
+
end
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
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}"
|
127
|
+
}
|
128
|
+
Aliases::Table.new(join_part, columns)
|
142
129
|
}
|
143
130
|
end
|
144
131
|
|
145
|
-
def
|
146
|
-
|
147
|
-
|
132
|
+
def instantiate(result_set, aliases)
|
133
|
+
primary_key = aliases.column_alias(join_root, join_root.primary_key)
|
134
|
+
|
135
|
+
seen = Hash.new { |h,parent_klass|
|
136
|
+
h[parent_klass] = Hash.new { |i,parent_id|
|
137
|
+
i[parent_id] = Hash.new { |j,child_klass| j[child_klass] = {} }
|
138
|
+
}
|
139
|
+
}
|
140
|
+
|
141
|
+
model_cache = Hash.new { |h,klass| h[klass] = {} }
|
142
|
+
parents = model_cache[join_root]
|
143
|
+
column_aliases = aliases.column_aliases join_root
|
144
|
+
|
145
|
+
message_bus = ActiveSupport::Notifications.instrumenter
|
146
|
+
|
147
|
+
payload = {
|
148
|
+
record_count: result_set.length,
|
149
|
+
class_name: join_root.base_klass.name
|
150
|
+
}
|
151
|
+
|
152
|
+
message_bus.instrument('instantiation.active_record', payload) do
|
153
|
+
result_set.each { |row_hash|
|
154
|
+
parent_key = primary_key ? row_hash[primary_key] : row_hash
|
155
|
+
parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases)
|
156
|
+
construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
|
157
|
+
}
|
148
158
|
end
|
159
|
+
|
160
|
+
parents.values
|
149
161
|
end
|
150
162
|
|
151
|
-
|
152
|
-
|
163
|
+
private
|
164
|
+
|
165
|
+
def make_constraints(parent, child, tables, join_type)
|
166
|
+
chain = child.reflection.chain
|
167
|
+
foreign_table = parent.table
|
168
|
+
foreign_klass = parent.base_klass
|
169
|
+
child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, child.reflection.scope_chain, chain)
|
153
170
|
end
|
154
171
|
|
155
|
-
def
|
156
|
-
|
157
|
-
|
158
|
-
|
172
|
+
def make_outer_joins(parent, child)
|
173
|
+
tables = table_aliases_for(parent, child)
|
174
|
+
join_type = Arel::Nodes::OuterJoin
|
175
|
+
info = make_constraints parent, child, tables, join_type
|
159
176
|
|
160
|
-
|
161
|
-
|
162
|
-
j.parent_table_name == parent.class.table_name }
|
177
|
+
[info] + child.children.flat_map { |c| make_outer_joins(child, c) }
|
178
|
+
end
|
163
179
|
|
164
|
-
|
180
|
+
def make_inner_joins(parent, child)
|
181
|
+
tables = child.tables
|
182
|
+
join_type = Arel::Nodes::InnerJoin
|
183
|
+
info = make_constraints parent, child, tables, join_type
|
165
184
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
185
|
+
[info] + child.children.flat_map { |c| make_inner_joins(child, c) }
|
186
|
+
end
|
187
|
+
|
188
|
+
def table_aliases_for(parent, node)
|
189
|
+
node.reflection.chain.map { |reflection|
|
190
|
+
alias_tracker.aliased_table_for(
|
191
|
+
reflection.table_name,
|
192
|
+
table_alias_for(reflection, parent, reflection != node.reflection)
|
193
|
+
)
|
194
|
+
}
|
195
|
+
end
|
196
|
+
|
197
|
+
def construct_tables!(parent, node)
|
198
|
+
node.tables = table_aliases_for(parent, node)
|
199
|
+
node.children.each { |child| construct_tables! node, child }
|
200
|
+
end
|
201
|
+
|
202
|
+
def table_alias_for(reflection, parent, join)
|
203
|
+
name = "#{reflection.plural_name}_#{parent.table_name}"
|
204
|
+
name << "_join" if join
|
205
|
+
name
|
206
|
+
end
|
207
|
+
|
208
|
+
def walk(left, right)
|
209
|
+
intersection, missing = right.children.map { |node1|
|
210
|
+
[left.children.find { |node2| node1.match? node2 }, node1]
|
211
|
+
}.partition(&:first)
|
212
|
+
|
213
|
+
ojs = missing.flat_map { |_,n| make_outer_joins left, n }
|
214
|
+
intersection.flat_map { |l,r| walk l, r }.concat ojs
|
215
|
+
end
|
216
|
+
|
217
|
+
def find_reflection(klass, name)
|
218
|
+
klass._reflect_on_association(name) or
|
219
|
+
raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
|
220
|
+
end
|
221
|
+
|
222
|
+
def build(associations, base_klass)
|
223
|
+
associations.map do |name, right|
|
224
|
+
reflection = find_reflection base_klass, name
|
225
|
+
reflection.check_validity!
|
226
|
+
reflection.check_eager_loadable!
|
227
|
+
|
228
|
+
if reflection.polymorphic?
|
229
|
+
raise EagerLoadPolymorphicError.new(reflection)
|
176
230
|
end
|
177
|
-
|
178
|
-
|
231
|
+
|
232
|
+
JoinAssociation.new reflection, build(right, reflection.klass)
|
179
233
|
end
|
180
234
|
end
|
181
235
|
|
182
|
-
def
|
183
|
-
return if
|
236
|
+
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
|
237
|
+
return if ar_parent.nil?
|
238
|
+
primary_id = ar_parent.id
|
184
239
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
|
189
|
-
set_target_and_inverse(join_part, association, record)
|
190
|
-
else
|
191
|
-
association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
|
192
|
-
case macro
|
193
|
-
when :has_many, :has_and_belongs_to_many
|
194
|
-
other = record.association(join_part.reflection.name)
|
240
|
+
parent.children.each do |node|
|
241
|
+
if node.reflection.collection?
|
242
|
+
other = ar_parent.association(node.reflection.name)
|
195
243
|
other.loaded!
|
196
|
-
other.target.push(association) if association
|
197
|
-
other.set_inverse_instance(association)
|
198
|
-
when :belongs_to
|
199
|
-
set_target_and_inverse(join_part, association, record)
|
200
244
|
else
|
201
|
-
|
245
|
+
if ar_parent.association_cache.key?(node.reflection.name)
|
246
|
+
model = ar_parent.association(node.reflection.name).target
|
247
|
+
construct(model, node, row, rs, seen, model_cache, aliases)
|
248
|
+
next
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
key = aliases.column_alias(node, node.primary_key)
|
253
|
+
id = row[key]
|
254
|
+
if id.nil?
|
255
|
+
nil_association = ar_parent.association(node.reflection.name)
|
256
|
+
nil_association.loaded!
|
257
|
+
next
|
258
|
+
end
|
259
|
+
|
260
|
+
model = seen[parent.base_klass][primary_id][node.base_klass][id]
|
261
|
+
|
262
|
+
if model
|
263
|
+
construct(model, node, row, rs, seen, model_cache, aliases)
|
264
|
+
else
|
265
|
+
model = construct_model(ar_parent, node, row, model_cache, id, aliases)
|
266
|
+
seen[parent.base_klass][primary_id][node.base_klass][id] = model
|
267
|
+
construct(model, node, row, rs, seen, model_cache, aliases)
|
202
268
|
end
|
203
269
|
end
|
204
|
-
association
|
205
270
|
end
|
206
271
|
|
207
|
-
def
|
208
|
-
|
209
|
-
|
210
|
-
other.
|
272
|
+
def construct_model(record, node, row, model_cache, id, aliases)
|
273
|
+
model = model_cache[node][id] ||= node.instantiate(row,
|
274
|
+
aliases.column_aliases(node))
|
275
|
+
other = record.association(node.reflection.name)
|
276
|
+
|
277
|
+
if node.reflection.collection?
|
278
|
+
other.target.push(model)
|
279
|
+
else
|
280
|
+
other.target = model
|
281
|
+
end
|
282
|
+
|
283
|
+
other.set_inverse_instance(model)
|
284
|
+
model
|
211
285
|
end
|
212
286
|
end
|
213
287
|
end
|
@@ -2,34 +2,38 @@ module ActiveRecord
|
|
2
2
|
module Associations
|
3
3
|
class Preloader
|
4
4
|
class Association #:nodoc:
|
5
|
-
attr_reader :owners, :reflection, :
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
@
|
10
|
-
@
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@
|
14
|
-
@
|
5
|
+
attr_reader :owners, :reflection, :preload_scope, :model, :klass
|
6
|
+
attr_reader :preloaded_records
|
7
|
+
|
8
|
+
def initialize(klass, owners, reflection, preload_scope)
|
9
|
+
@klass = klass
|
10
|
+
@owners = owners
|
11
|
+
@reflection = reflection
|
12
|
+
@preload_scope = preload_scope
|
13
|
+
@model = owners.first && owners.first.class
|
14
|
+
@scope = nil
|
15
|
+
@owners_by_key = nil
|
16
|
+
@preloaded_records = []
|
15
17
|
end
|
16
18
|
|
17
|
-
def run
|
18
|
-
|
19
|
-
preload
|
20
|
-
end
|
19
|
+
def run(preloader)
|
20
|
+
preload(preloader)
|
21
21
|
end
|
22
22
|
|
23
|
-
def preload
|
23
|
+
def preload(preloader)
|
24
24
|
raise NotImplementedError
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
28
|
-
@
|
27
|
+
def scope
|
28
|
+
@scope ||= build_scope
|
29
29
|
end
|
30
30
|
|
31
31
|
def records_for(ids)
|
32
|
-
|
32
|
+
query_scope(ids)
|
33
|
+
end
|
34
|
+
|
35
|
+
def query_scope(ids)
|
36
|
+
scope.where(association_key.in(ids))
|
33
37
|
end
|
34
38
|
|
35
39
|
def table
|
@@ -52,13 +56,16 @@ module ActiveRecord
|
|
52
56
|
raise NotImplementedError
|
53
57
|
end
|
54
58
|
|
55
|
-
# We're converting to a string here because postgres will return the aliased association
|
56
|
-
# key in a habtm as a string (for whatever reason)
|
57
59
|
def owners_by_key
|
58
|
-
@owners_by_key ||=
|
59
|
-
|
60
|
-
|
61
|
-
|
60
|
+
@owners_by_key ||= if key_conversion_required?
|
61
|
+
owners.group_by do |owner|
|
62
|
+
owner[owner_key_name].to_s
|
63
|
+
end
|
64
|
+
else
|
65
|
+
owners.group_by do |owner|
|
66
|
+
owner[owner_key_name]
|
67
|
+
end
|
68
|
+
end
|
62
69
|
end
|
63
70
|
|
64
71
|
def options
|
@@ -67,57 +74,91 @@ module ActiveRecord
|
|
67
74
|
|
68
75
|
private
|
69
76
|
|
70
|
-
def associated_records_by_owner
|
77
|
+
def associated_records_by_owner(preloader)
|
71
78
|
owners_map = owners_by_key
|
72
79
|
owner_keys = owners_map.keys.compact
|
73
80
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
# Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
|
78
|
-
# Make several smaller queries if necessary or make one query if the adapter supports it
|
79
|
-
sliced = owner_keys.each_slice(model.connection.in_clause_length || owner_keys.size)
|
80
|
-
records = sliced.map { |slice| records_for(slice) }.flatten
|
81
|
+
# Each record may have multiple owners, and vice-versa
|
82
|
+
records_by_owner = owners.each_with_object({}) do |owner,h|
|
83
|
+
h[owner] = []
|
81
84
|
end
|
82
85
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
86
|
+
if owner_keys.any?
|
87
|
+
# Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
|
88
|
+
# Make several smaller queries if necessary or make one query if the adapter supports it
|
89
|
+
sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
|
87
90
|
|
88
|
-
|
89
|
-
|
91
|
+
records = load_slices sliced
|
92
|
+
records.each do |record, owner_key|
|
93
|
+
owners_map[owner_key].each do |owner|
|
94
|
+
records_by_owner[owner] << record
|
95
|
+
end
|
90
96
|
end
|
91
97
|
end
|
98
|
+
|
92
99
|
records_by_owner
|
93
100
|
end
|
94
101
|
|
102
|
+
def key_conversion_required?
|
103
|
+
association_key_type != owner_key_type
|
104
|
+
end
|
105
|
+
|
106
|
+
def association_key_type
|
107
|
+
@klass.type_for_attribute(association_key_name.to_s).type
|
108
|
+
end
|
109
|
+
|
110
|
+
def owner_key_type
|
111
|
+
@model.type_for_attribute(owner_key_name.to_s).type
|
112
|
+
end
|
113
|
+
|
114
|
+
def load_slices(slices)
|
115
|
+
@preloaded_records = slices.flat_map { |slice|
|
116
|
+
records_for(slice)
|
117
|
+
}
|
118
|
+
|
119
|
+
@preloaded_records.map { |record|
|
120
|
+
key = record[association_key_name]
|
121
|
+
key = key.to_s if key_conversion_required?
|
122
|
+
|
123
|
+
[record, key]
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
def reflection_scope
|
128
|
+
@reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped
|
129
|
+
end
|
130
|
+
|
95
131
|
def build_scope
|
96
|
-
scope = klass.
|
132
|
+
scope = klass.unscoped
|
97
133
|
|
98
|
-
|
99
|
-
|
134
|
+
values = reflection_scope.values
|
135
|
+
reflection_binds = reflection_scope.bind_values
|
136
|
+
preload_values = preload_scope.values
|
137
|
+
preload_binds = preload_scope.bind_values
|
100
138
|
|
101
|
-
scope
|
102
|
-
scope =
|
139
|
+
scope.where_values = Array(values[:where]) + Array(preload_values[:where])
|
140
|
+
scope.references_values = Array(values[:references]) + Array(preload_values[:references])
|
141
|
+
scope.bind_values = (reflection_binds + preload_binds)
|
103
142
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
143
|
+
scope._select! preload_values[:select] || values[:select] || table[Arel.star]
|
144
|
+
scope.includes! preload_values[:includes] || values[:includes]
|
145
|
+
scope.joins! preload_values[:joins] || values[:joins]
|
146
|
+
scope.order! preload_values[:order] || values[:order]
|
147
|
+
|
148
|
+
if preload_values[:reordering] || values[:reordering]
|
149
|
+
scope.reordering_value = true
|
110
150
|
end
|
111
151
|
|
112
|
-
|
113
|
-
|
152
|
+
if preload_values[:readonly] || values[:readonly]
|
153
|
+
scope.readonly!
|
154
|
+
end
|
114
155
|
|
115
|
-
|
116
|
-
|
117
|
-
conditions = klass.send(:instance_eval, &conditions)
|
156
|
+
if options[:as]
|
157
|
+
scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
|
118
158
|
end
|
119
159
|
|
120
|
-
|
160
|
+
scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope])
|
161
|
+
klass.default_scoped.merge(scope)
|
121
162
|
end
|
122
163
|
end
|
123
164
|
end
|
@@ -6,11 +6,11 @@ module ActiveRecord
|
|
6
6
|
private
|
7
7
|
|
8
8
|
def build_scope
|
9
|
-
super.order(
|
9
|
+
super.order(preload_scope.values[:order] || reflection_scope.values[:order])
|
10
10
|
end
|
11
11
|
|
12
|
-
def preload
|
13
|
-
associated_records_by_owner.each do |owner, records|
|
12
|
+
def preload(preloader)
|
13
|
+
associated_records_by_owner(preloader).each do |owner, records|
|
14
14
|
association = owner.association(reflection.name)
|
15
15
|
association.loaded!
|
16
16
|
association.target.concat(records)
|