activerecord 4.2.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 +7 -0
- data/CHANGELOG.md +1372 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +218 -0
- data/examples/performance.rb +184 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +173 -0
- data/lib/active_record/aggregations.rb +266 -0
- data/lib/active_record/association_relation.rb +22 -0
- data/lib/active_record/associations.rb +1724 -0
- data/lib/active_record/associations/alias_tracker.rb +87 -0
- data/lib/active_record/associations/association.rb +253 -0
- data/lib/active_record/associations/association_scope.rb +194 -0
- data/lib/active_record/associations/belongs_to_association.rb +111 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
- data/lib/active_record/associations/builder/association.rb +149 -0
- data/lib/active_record/associations/builder/belongs_to.rb +116 -0
- data/lib/active_record/associations/builder/collection_association.rb +91 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +124 -0
- data/lib/active_record/associations/builder/has_many.rb +15 -0
- data/lib/active_record/associations/builder/has_one.rb +23 -0
- data/lib/active_record/associations/builder/singular_association.rb +38 -0
- data/lib/active_record/associations/collection_association.rb +634 -0
- data/lib/active_record/associations/collection_proxy.rb +1027 -0
- data/lib/active_record/associations/has_many_association.rb +184 -0
- data/lib/active_record/associations/has_many_through_association.rb +238 -0
- data/lib/active_record/associations/has_one_association.rb +105 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- data/lib/active_record/associations/join_dependency.rb +282 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +122 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +22 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
- data/lib/active_record/associations/preloader.rb +203 -0
- data/lib/active_record/associations/preloader/association.rb +162 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +96 -0
- data/lib/active_record/associations/singular_association.rb +86 -0
- data/lib/active_record/associations/through_association.rb +96 -0
- data/lib/active_record/attribute.rb +149 -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.rb +439 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +71 -0
- data/lib/active_record/attribute_methods/dirty.rb +181 -0
- data/lib/active_record/attribute_methods/primary_key.rb +128 -0
- data/lib/active_record/attribute_methods/query.rb +40 -0
- data/lib/active_record/attribute_methods/read.rb +103 -0
- data/lib/active_record/attribute_methods/serialization.rb +70 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
- data/lib/active_record/attribute_methods/write.rb +83 -0
- data/lib/active_record/attribute_set.rb +77 -0
- data/lib/active_record/attribute_set/builder.rb +86 -0
- data/lib/active_record/attributes.rb +139 -0
- data/lib/active_record/autosave_association.rb +439 -0
- data/lib/active_record/base.rb +317 -0
- data/lib/active_record/callbacks.rb +313 -0
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +659 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +373 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +133 -0
- 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 +574 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +991 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +219 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +487 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +883 -0
- data/lib/active_record/connection_adapters/column.rb +82 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +282 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +491 -0
- 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.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -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 +14 -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 +27 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -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 +15 -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 +97 -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/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 +588 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +754 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +628 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +566 -0
- data/lib/active_record/counter_cache.rb +175 -0
- data/lib/active_record/dynamic_matchers.rb +140 -0
- data/lib/active_record/enum.rb +198 -0
- data/lib/active_record/errors.rb +252 -0
- 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 +1007 -0
- 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/locale/en.yml +47 -0
- data/lib/active_record/locking/optimistic.rb +204 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +75 -0
- data/lib/active_record/migration.rb +1051 -0
- data/lib/active_record/migration/command_recorder.rb +197 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/model_schema.rb +340 -0
- data/lib/active_record/nested_attributes.rb +548 -0
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +532 -0
- data/lib/active_record/query_cache.rb +56 -0
- data/lib/active_record/querying.rb +68 -0
- data/lib/active_record/railtie.rb +162 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +50 -0
- data/lib/active_record/railties/databases.rake +391 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +23 -0
- data/lib/active_record/reflection.rb +881 -0
- data/lib/active_record/relation.rb +681 -0
- data/lib/active_record/relation/batches.rb +138 -0
- data/lib/active_record/relation/calculations.rb +403 -0
- data/lib/active_record/relation/delegation.rb +140 -0
- data/lib/active_record/relation/finder_methods.rb +528 -0
- data/lib/active_record/relation/merger.rb +170 -0
- data/lib/active_record/relation/predicate_builder.rb +126 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +47 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/query_methods.rb +1176 -0
- data/lib/active_record/relation/spawn_methods.rb +75 -0
- data/lib/active_record/result.rb +131 -0
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +191 -0
- data/lib/active_record/schema.rb +64 -0
- data/lib/active_record/schema_dumper.rb +251 -0
- data/lib/active_record/schema_migration.rb +56 -0
- data/lib/active_record/scoping.rb +87 -0
- data/lib/active_record/scoping/default.rb +134 -0
- data/lib/active_record/scoping/named.rb +164 -0
- data/lib/active_record/serialization.rb +22 -0
- data/lib/active_record/serializers/xml_serializer.rb +193 -0
- 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 +296 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +145 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +121 -0
- data/lib/active_record/transactions.rb +417 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/type.rb +23 -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 +30 -0
- data/lib/active_record/type/date.rb +46 -0
- data/lib/active_record/type/date_time.rb +43 -0
- data/lib/active_record/type/decimal.rb +40 -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 +17 -0
- data/lib/active_record/type/integer.rb +55 -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 +56 -0
- data/lib/active_record/type/string.rb +36 -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 +101 -0
- data/lib/active_record/validations.rb +90 -0
- data/lib/active_record/validations/associated.rb +51 -0
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +229 -0
- data/lib/active_record/version.rb +8 -0
- data/lib/rails/generators/active_record.rb +17 -0
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +70 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +22 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +45 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +52 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- metadata +309 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class JoinDependency # :nodoc:
|
4
|
+
# A JoinPart represents a part of a JoinDependency. It is inherited
|
5
|
+
# by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
|
6
|
+
# everything else is being joined onto. A JoinAssociation represents an association which
|
7
|
+
# is joining to the base. A JoinAssociation may result in more than one actual join
|
8
|
+
# operations (for example a has_and_belongs_to_many JoinAssociation would result in
|
9
|
+
# two; one for the join table and one for the target table).
|
10
|
+
class JoinPart # :nodoc:
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
# The Active Record class which this join part is associated 'about'; for a JoinBase
|
14
|
+
# this is the actual base model, for a JoinAssociation this is the target model of the
|
15
|
+
# association.
|
16
|
+
attr_reader :base_klass, :children
|
17
|
+
|
18
|
+
delegate :table_name, :column_names, :primary_key, :to => :base_klass
|
19
|
+
|
20
|
+
def initialize(base_klass, children)
|
21
|
+
@base_klass = base_klass
|
22
|
+
@children = children
|
23
|
+
end
|
24
|
+
|
25
|
+
def name
|
26
|
+
reflection.name
|
27
|
+
end
|
28
|
+
|
29
|
+
def match?(other)
|
30
|
+
self.class == other.class
|
31
|
+
end
|
32
|
+
|
33
|
+
def each(&block)
|
34
|
+
yield self
|
35
|
+
children.each { |child| child.each(&block) }
|
36
|
+
end
|
37
|
+
|
38
|
+
# An Arel::Table for the active_record
|
39
|
+
def table
|
40
|
+
raise NotImplementedError
|
41
|
+
end
|
42
|
+
|
43
|
+
# The alias for the active_record's table
|
44
|
+
def aliased_table_name
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
48
|
+
def extract_record(row, column_names_with_alias)
|
49
|
+
# This code is performance critical as it is called per row.
|
50
|
+
# see: https://github.com/rails/rails/pull/12185
|
51
|
+
hash = {}
|
52
|
+
|
53
|
+
index = 0
|
54
|
+
length = column_names_with_alias.length
|
55
|
+
|
56
|
+
while index < length
|
57
|
+
column_name, alias_name = column_names_with_alias[index]
|
58
|
+
hash[column_name] = row[alias_name]
|
59
|
+
index += 1
|
60
|
+
end
|
61
|
+
|
62
|
+
hash
|
63
|
+
end
|
64
|
+
|
65
|
+
def instantiate(row, aliases)
|
66
|
+
base_klass.instantiate(extract_record(row, aliases))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
# Implements the details of eager loading of Active Record associations.
|
4
|
+
#
|
5
|
+
# Suppose that you have the following two Active Record models:
|
6
|
+
#
|
7
|
+
# class Author < ActiveRecord::Base
|
8
|
+
# # columns: name, age
|
9
|
+
# has_many :books
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# class Book < ActiveRecord::Base
|
13
|
+
# # columns: title, sales
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# When you load an author with all associated books Active Record will make
|
17
|
+
# multiple queries like this:
|
18
|
+
#
|
19
|
+
# Author.includes(:books).where(:name => ['bell hooks', 'Homer').to_a
|
20
|
+
#
|
21
|
+
# => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
|
22
|
+
# => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
|
23
|
+
#
|
24
|
+
# Active Record saves the ids of the records from the first query to use in
|
25
|
+
# the second. Depending on the number of associations involved there can be
|
26
|
+
# arbitrarily many SQL queries made.
|
27
|
+
#
|
28
|
+
# However, if there is a WHERE clause that spans across tables Active
|
29
|
+
# Record will fall back to a slightly more resource-intensive single query:
|
30
|
+
#
|
31
|
+
# Author.includes(:books).where(books: {title: 'Illiad'}).to_a
|
32
|
+
# => SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
|
33
|
+
# `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2
|
34
|
+
# FROM `authors`
|
35
|
+
# LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id`
|
36
|
+
# WHERE `books`.`title` = 'Illiad'
|
37
|
+
#
|
38
|
+
# This could result in many rows that contain redundant data and it performs poorly at scale
|
39
|
+
# and is therefore only used when necessary.
|
40
|
+
#
|
41
|
+
class Preloader #:nodoc:
|
42
|
+
extend ActiveSupport::Autoload
|
43
|
+
|
44
|
+
eager_autoload do
|
45
|
+
autoload :Association, 'active_record/associations/preloader/association'
|
46
|
+
autoload :SingularAssociation, 'active_record/associations/preloader/singular_association'
|
47
|
+
autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association'
|
48
|
+
autoload :ThroughAssociation, 'active_record/associations/preloader/through_association'
|
49
|
+
|
50
|
+
autoload :HasMany, 'active_record/associations/preloader/has_many'
|
51
|
+
autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through'
|
52
|
+
autoload :HasOne, 'active_record/associations/preloader/has_one'
|
53
|
+
autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through'
|
54
|
+
autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
|
55
|
+
end
|
56
|
+
|
57
|
+
# Eager loads the named associations for the given Active Record record(s).
|
58
|
+
#
|
59
|
+
# In this description, 'association name' shall refer to the name passed
|
60
|
+
# to an association creation method. For example, a model that specifies
|
61
|
+
# <tt>belongs_to :author</tt>, <tt>has_many :buyers</tt> has association
|
62
|
+
# names +:author+ and +:buyers+.
|
63
|
+
#
|
64
|
+
# == Parameters
|
65
|
+
# +records+ is an array of ActiveRecord::Base. This array needs not be flat,
|
66
|
+
# i.e. +records+ itself may also contain arrays of records. In any case,
|
67
|
+
# +preload_associations+ will preload the all associations records by
|
68
|
+
# flattening +records+.
|
69
|
+
#
|
70
|
+
# +associations+ specifies one or more associations that you want to
|
71
|
+
# preload. It may be:
|
72
|
+
# - a Symbol or a String which specifies a single association name. For
|
73
|
+
# example, specifying +:books+ allows this method to preload all books
|
74
|
+
# for an Author.
|
75
|
+
# - an Array which specifies multiple association names. This array
|
76
|
+
# is processed recursively. For example, specifying <tt>[:avatar, :books]</tt>
|
77
|
+
# allows this method to preload an author's avatar as well as all of his
|
78
|
+
# books.
|
79
|
+
# - a Hash which specifies multiple association names, as well as
|
80
|
+
# association names for the to-be-preloaded association objects. For
|
81
|
+
# example, specifying <tt>{ author: :avatar }</tt> will preload a
|
82
|
+
# book's author, as well as that author's avatar.
|
83
|
+
#
|
84
|
+
# +:associations+ has the same format as the +:include+ option for
|
85
|
+
# <tt>ActiveRecord::Base.find</tt>. So +associations+ could look like this:
|
86
|
+
#
|
87
|
+
# :books
|
88
|
+
# [ :books, :author ]
|
89
|
+
# { author: :avatar }
|
90
|
+
# [ :books, { author: :avatar } ]
|
91
|
+
|
92
|
+
NULL_RELATION = Struct.new(:values, :bind_values).new({}, [])
|
93
|
+
|
94
|
+
def preload(records, associations, preload_scope = nil)
|
95
|
+
records = Array.wrap(records).compact.uniq
|
96
|
+
associations = Array.wrap(associations)
|
97
|
+
preload_scope = preload_scope || NULL_RELATION
|
98
|
+
|
99
|
+
if records.empty?
|
100
|
+
[]
|
101
|
+
else
|
102
|
+
associations.flat_map { |association|
|
103
|
+
preloaders_on association, records, preload_scope
|
104
|
+
}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def preloaders_on(association, records, scope)
|
111
|
+
case association
|
112
|
+
when Hash
|
113
|
+
preloaders_for_hash(association, records, scope)
|
114
|
+
when Symbol
|
115
|
+
preloaders_for_one(association, records, scope)
|
116
|
+
when String
|
117
|
+
preloaders_for_one(association.to_sym, records, scope)
|
118
|
+
else
|
119
|
+
raise ArgumentError, "#{association.inspect} was not recognised for preload"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def preloaders_for_hash(association, records, scope)
|
124
|
+
association.flat_map { |parent, child|
|
125
|
+
loaders = preloaders_for_one parent, records, scope
|
126
|
+
|
127
|
+
recs = loaders.flat_map(&:preloaded_records).uniq
|
128
|
+
loaders.concat Array.wrap(child).flat_map { |assoc|
|
129
|
+
preloaders_on assoc, recs, scope
|
130
|
+
}
|
131
|
+
loaders
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
# Not all records have the same class, so group then preload group on the reflection
|
136
|
+
# itself so that if various subclass share the same association then we do not split
|
137
|
+
# them unnecessarily
|
138
|
+
#
|
139
|
+
# Additionally, polymorphic belongs_to associations can have multiple associated
|
140
|
+
# classes, depending on the polymorphic_type field. So we group by the classes as
|
141
|
+
# well.
|
142
|
+
def preloaders_for_one(association, records, scope)
|
143
|
+
grouped_records(association, records).flat_map do |reflection, klasses|
|
144
|
+
klasses.map do |rhs_klass, rs|
|
145
|
+
loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope)
|
146
|
+
loader.run self
|
147
|
+
loader
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def grouped_records(association, records)
|
153
|
+
h = {}
|
154
|
+
records.each do |record|
|
155
|
+
next unless record
|
156
|
+
assoc = record.association(association)
|
157
|
+
klasses = h[assoc.reflection] ||= {}
|
158
|
+
(klasses[assoc.klass] ||= []) << record
|
159
|
+
end
|
160
|
+
h
|
161
|
+
end
|
162
|
+
|
163
|
+
class AlreadyLoaded
|
164
|
+
attr_reader :owners, :reflection
|
165
|
+
|
166
|
+
def initialize(klass, owners, reflection, preload_scope)
|
167
|
+
@owners = owners
|
168
|
+
@reflection = reflection
|
169
|
+
end
|
170
|
+
|
171
|
+
def run(preloader); end
|
172
|
+
|
173
|
+
def preloaded_records
|
174
|
+
owners.flat_map { |owner| owner.association(reflection.name).target }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
class NullPreloader
|
179
|
+
def self.new(klass, owners, reflection, preload_scope); self; end
|
180
|
+
def self.run(preloader); end
|
181
|
+
def self.preloaded_records; []; end
|
182
|
+
end
|
183
|
+
|
184
|
+
def preloader_for(reflection, owners, rhs_klass)
|
185
|
+
return NullPreloader unless rhs_klass
|
186
|
+
|
187
|
+
if owners.first.association(reflection.name).loaded?
|
188
|
+
return AlreadyLoaded
|
189
|
+
end
|
190
|
+
reflection.check_preloadable!
|
191
|
+
|
192
|
+
case reflection.macro
|
193
|
+
when :has_many
|
194
|
+
reflection.options[:through] ? HasManyThrough : HasMany
|
195
|
+
when :has_one
|
196
|
+
reflection.options[:through] ? HasOneThrough : HasOne
|
197
|
+
when :belongs_to
|
198
|
+
BelongsTo
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class Preloader
|
4
|
+
class Association #:nodoc:
|
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 = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def run(preloader)
|
20
|
+
preload(preloader)
|
21
|
+
end
|
22
|
+
|
23
|
+
def preload(preloader)
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def scope
|
28
|
+
@scope ||= build_scope
|
29
|
+
end
|
30
|
+
|
31
|
+
def records_for(ids)
|
32
|
+
query_scope(ids)
|
33
|
+
end
|
34
|
+
|
35
|
+
def query_scope(ids)
|
36
|
+
scope.where(association_key.in(ids))
|
37
|
+
end
|
38
|
+
|
39
|
+
def table
|
40
|
+
klass.arel_table
|
41
|
+
end
|
42
|
+
|
43
|
+
# The name of the key on the associated records
|
44
|
+
def association_key_name
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
48
|
+
# This is overridden by HABTM as the condition should be on the foreign_key column in
|
49
|
+
# the join table
|
50
|
+
def association_key
|
51
|
+
table[association_key_name]
|
52
|
+
end
|
53
|
+
|
54
|
+
# The name of the key on the model which declares the association
|
55
|
+
def owner_key_name
|
56
|
+
raise NotImplementedError
|
57
|
+
end
|
58
|
+
|
59
|
+
def owners_by_key
|
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
|
69
|
+
end
|
70
|
+
|
71
|
+
def options
|
72
|
+
reflection.options
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def associated_records_by_owner(preloader)
|
78
|
+
owners_map = owners_by_key
|
79
|
+
owner_keys = owners_map.keys.compact
|
80
|
+
|
81
|
+
# Each record may have multiple owners, and vice-versa
|
82
|
+
records_by_owner = owners.each_with_object({}) do |owner,h|
|
83
|
+
h[owner] = []
|
84
|
+
end
|
85
|
+
|
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)
|
90
|
+
|
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
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
records_by_owner
|
100
|
+
end
|
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
|
+
|
131
|
+
def build_scope
|
132
|
+
scope = klass.unscoped
|
133
|
+
|
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
|
138
|
+
|
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)
|
142
|
+
|
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[:readonly] || values[:readonly]
|
149
|
+
scope.readonly!
|
150
|
+
end
|
151
|
+
|
152
|
+
if options[:as]
|
153
|
+
scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
|
154
|
+
end
|
155
|
+
|
156
|
+
scope.unscope_values = Array(values[:unscope])
|
157
|
+
klass.default_scoped.merge(scope)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class Preloader
|
4
|
+
class BelongsTo < SingularAssociation #:nodoc:
|
5
|
+
|
6
|
+
def association_key_name
|
7
|
+
reflection.options[:primary_key] || klass && klass.primary_key
|
8
|
+
end
|
9
|
+
|
10
|
+
def owner_key_name
|
11
|
+
reflection.foreign_key
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|