sskirby-activerecord 3.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +6749 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +222 -0
- data/examples/associations.png +0 -0
- data/examples/performance.rb +177 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +147 -0
- data/lib/active_record/aggregations.rb +255 -0
- data/lib/active_record/associations.rb +1604 -0
- data/lib/active_record/associations/alias_tracker.rb +79 -0
- data/lib/active_record/associations/association.rb +239 -0
- data/lib/active_record/associations/association_scope.rb +119 -0
- data/lib/active_record/associations/belongs_to_association.rb +79 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
- data/lib/active_record/associations/builder/association.rb +55 -0
- data/lib/active_record/associations/builder/belongs_to.rb +85 -0
- data/lib/active_record/associations/builder/collection_association.rb +75 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
- data/lib/active_record/associations/builder/has_many.rb +71 -0
- data/lib/active_record/associations/builder/has_one.rb +62 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +574 -0
- data/lib/active_record/associations/collection_proxy.rb +132 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
- data/lib/active_record/associations/has_many_association.rb +108 -0
- data/lib/active_record/associations/has_many_through_association.rb +180 -0
- data/lib/active_record/associations/has_one_association.rb +73 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- data/lib/active_record/associations/join_dependency.rb +214 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_helper.rb +55 -0
- data/lib/active_record/associations/preloader.rb +177 -0
- data/lib/active_record/associations/preloader/association.rb +127 -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_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +15 -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 +67 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +83 -0
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods.rb +272 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +101 -0
- data/lib/active_record/attribute_methods/primary_key.rb +114 -0
- data/lib/active_record/attribute_methods/query.rb +39 -0
- data/lib/active_record/attribute_methods/read.rb +135 -0
- data/lib/active_record/attribute_methods/serialization.rb +93 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
- data/lib/active_record/attribute_methods/write.rb +69 -0
- data/lib/active_record/autosave_association.rb +422 -0
- data/lib/active_record/base.rb +716 -0
- data/lib/active_record/callbacks.rb +275 -0
- data/lib/active_record/coders/yaml_column.rb +41 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
- data/lib/active_record/connection_adapters/column.rb +270 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/counter_cache.rb +119 -0
- data/lib/active_record/dynamic_finder_match.rb +56 -0
- data/lib/active_record/dynamic_matchers.rb +79 -0
- data/lib/active_record/dynamic_scope_match.rb +23 -0
- data/lib/active_record/errors.rb +195 -0
- data/lib/active_record/explain.rb +85 -0
- data/lib/active_record/explain_subscriber.rb +21 -0
- data/lib/active_record/fixtures.rb +906 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/identity_map.rb +156 -0
- data/lib/active_record/inheritance.rb +167 -0
- data/lib/active_record/integration.rb +49 -0
- data/lib/active_record/locale/en.yml +40 -0
- data/lib/active_record/locking/optimistic.rb +183 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +68 -0
- data/lib/active_record/migration.rb +765 -0
- data/lib/active_record/migration/command_recorder.rb +105 -0
- data/lib/active_record/model_schema.rb +366 -0
- data/lib/active_record/nested_attributes.rb +469 -0
- data/lib/active_record/observer.rb +121 -0
- data/lib/active_record/persistence.rb +372 -0
- data/lib/active_record/query_cache.rb +74 -0
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +119 -0
- data/lib/active_record/railties/console_sandbox.rb +6 -0
- data/lib/active_record/railties/controller_runtime.rb +49 -0
- data/lib/active_record/railties/databases.rake +620 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +534 -0
- data/lib/active_record/relation.rb +534 -0
- data/lib/active_record/relation/batches.rb +90 -0
- data/lib/active_record/relation/calculations.rb +354 -0
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +398 -0
- data/lib/active_record/relation/predicate_builder.rb +58 -0
- data/lib/active_record/relation/query_methods.rb +417 -0
- data/lib/active_record/relation/spawn_methods.rb +148 -0
- data/lib/active_record/result.rb +34 -0
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema.rb +58 -0
- data/lib/active_record/schema_dumper.rb +204 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/serialization.rb +18 -0
- data/lib/active_record/serializers/xml_serializer.rb +202 -0
- data/lib/active_record/session_store.rb +358 -0
- data/lib/active_record/store.rb +50 -0
- data/lib/active_record/test_case.rb +73 -0
- data/lib/active_record/timestamp.rb +113 -0
- data/lib/active_record/transactions.rb +360 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations.rb +83 -0
- data/lib/active_record/validations/associated.rb +43 -0
- data/lib/active_record/validations/uniqueness.rb +180 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/rails/generators/active_record.rb +25 -0
- data/lib/rails/generators/active_record/migration.rb +15 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
- data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
- metadata +242 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class JoinDependency # :nodoc:
|
4
|
+
class JoinBase < JoinPart # :nodoc:
|
5
|
+
def ==(other)
|
6
|
+
other.class == self.class &&
|
7
|
+
other.active_record == active_record
|
8
|
+
end
|
9
|
+
|
10
|
+
def aliased_prefix
|
11
|
+
"t0"
|
12
|
+
end
|
13
|
+
|
14
|
+
def table
|
15
|
+
Arel::Table.new(table_name, arel_engine)
|
16
|
+
end
|
17
|
+
|
18
|
+
def aliased_table_name
|
19
|
+
active_record.table_name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class JoinDependency # :nodoc:
|
4
|
+
# A JoinPart represents a part of a JoinDependency. It is an abstract class, 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
|
+
# The Active Record class which this join part is associated 'about'; for a JoinBase
|
12
|
+
# this is the actual base model, for a JoinAssociation this is the target model of the
|
13
|
+
# association.
|
14
|
+
attr_reader :active_record
|
15
|
+
|
16
|
+
delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :active_record
|
17
|
+
|
18
|
+
def initialize(active_record)
|
19
|
+
@active_record = active_record
|
20
|
+
@cached_record = {}
|
21
|
+
@column_names_with_alias = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def aliased_table
|
25
|
+
Arel::Nodes::TableAlias.new table, aliased_table_name
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(other)
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
# An Arel::Table for the active_record
|
33
|
+
def table
|
34
|
+
raise NotImplementedError
|
35
|
+
end
|
36
|
+
|
37
|
+
# The prefix to be used when aliasing columns in the active_record's table
|
38
|
+
def aliased_prefix
|
39
|
+
raise NotImplementedError
|
40
|
+
end
|
41
|
+
|
42
|
+
# The alias for the active_record's table
|
43
|
+
def aliased_table_name
|
44
|
+
raise NotImplementedError
|
45
|
+
end
|
46
|
+
|
47
|
+
# The alias for the primary key of the active_record's table
|
48
|
+
def aliased_primary_key
|
49
|
+
"#{aliased_prefix}_r0"
|
50
|
+
end
|
51
|
+
|
52
|
+
# An array of [column_name, alias] pairs for the table
|
53
|
+
def column_names_with_alias
|
54
|
+
unless @column_names_with_alias
|
55
|
+
@column_names_with_alias = []
|
56
|
+
|
57
|
+
([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
|
58
|
+
@column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
@column_names_with_alias
|
62
|
+
end
|
63
|
+
|
64
|
+
def extract_record(row)
|
65
|
+
Hash[column_names_with_alias.map{|cn, an| [cn, row[an]]}]
|
66
|
+
end
|
67
|
+
|
68
|
+
def record_id(row)
|
69
|
+
row[aliased_primary_key]
|
70
|
+
end
|
71
|
+
|
72
|
+
def instantiate(row)
|
73
|
+
@cached_record[record_id(row)] ||= active_record.send(:instantiate, extract_record(row))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
# Helper class module which gets mixed into JoinDependency::JoinAssociation and AssociationScope
|
4
|
+
module JoinHelper #:nodoc:
|
5
|
+
|
6
|
+
def join_type
|
7
|
+
Arel::InnerJoin
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def construct_tables
|
13
|
+
tables = []
|
14
|
+
chain.each do |reflection|
|
15
|
+
tables << alias_tracker.aliased_table_for(
|
16
|
+
table_name_for(reflection),
|
17
|
+
table_alias_for(reflection, reflection != self.reflection)
|
18
|
+
)
|
19
|
+
|
20
|
+
if reflection.source_macro == :has_and_belongs_to_many
|
21
|
+
tables << alias_tracker.aliased_table_for(
|
22
|
+
(reflection.source_reflection || reflection).options[:join_table],
|
23
|
+
table_alias_for(reflection, true)
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
tables
|
28
|
+
end
|
29
|
+
|
30
|
+
def table_name_for(reflection)
|
31
|
+
reflection.table_name
|
32
|
+
end
|
33
|
+
|
34
|
+
def table_alias_for(reflection, join = false)
|
35
|
+
name = "#{reflection.plural_name}_#{alias_suffix}"
|
36
|
+
name << "_join" if join
|
37
|
+
name
|
38
|
+
end
|
39
|
+
|
40
|
+
def join(table, constraint)
|
41
|
+
table.create_join(table, table.create_on(constraint), join_type)
|
42
|
+
end
|
43
|
+
|
44
|
+
def sanitize(conditions, table)
|
45
|
+
conditions = conditions.map do |condition|
|
46
|
+
condition = active_record.send(:sanitize_sql, interpolate(condition), table.table_alias || table.name)
|
47
|
+
condition = Arel.sql(condition) unless condition.is_a?(Arel::Node)
|
48
|
+
condition
|
49
|
+
end
|
50
|
+
|
51
|
+
conditions.length == 1 ? conditions.first : Arel::Nodes::And.new(conditions)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
# Implements the details of eager loading of Active Record associations.
|
4
|
+
#
|
5
|
+
# Note that 'eager loading' and 'preloading' are actually the same thing.
|
6
|
+
# However, there are two different eager loading strategies.
|
7
|
+
#
|
8
|
+
# The first one is by using table joins. This was only strategy available
|
9
|
+
# prior to Rails 2.1. Suppose that you have an Author model with columns
|
10
|
+
# 'name' and 'age', and a Book model with columns 'name' and 'sales'. Using
|
11
|
+
# this strategy, Active Record would try to retrieve all data for an author
|
12
|
+
# and all of its books via a single query:
|
13
|
+
#
|
14
|
+
# SELECT * FROM authors
|
15
|
+
# LEFT OUTER JOIN books ON authors.id = books.id
|
16
|
+
# WHERE authors.name = 'Ken Akamatsu'
|
17
|
+
#
|
18
|
+
# However, this could result in many rows that contain redundant data. After
|
19
|
+
# having received the first row, we already have enough data to instantiate
|
20
|
+
# the Author object. In all subsequent rows, only the data for the joined
|
21
|
+
# 'books' table is useful; the joined 'authors' data is just redundant, and
|
22
|
+
# processing this redundant data takes memory and CPU time. The problem
|
23
|
+
# quickly becomes worse and worse as the level of eager loading increases
|
24
|
+
# (i.e. if Active Record is to eager load the associations' associations as
|
25
|
+
# well).
|
26
|
+
#
|
27
|
+
# The second strategy is to use multiple database queries, one for each
|
28
|
+
# level of association. Since Rails 2.1, this is the default strategy. In
|
29
|
+
# situations where a table join is necessary (e.g. when the +:conditions+
|
30
|
+
# option references an association's column), it will fallback to the table
|
31
|
+
# join strategy.
|
32
|
+
class Preloader #:nodoc:
|
33
|
+
autoload :Association, 'active_record/associations/preloader/association'
|
34
|
+
autoload :SingularAssociation, 'active_record/associations/preloader/singular_association'
|
35
|
+
autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association'
|
36
|
+
autoload :ThroughAssociation, 'active_record/associations/preloader/through_association'
|
37
|
+
|
38
|
+
autoload :HasMany, 'active_record/associations/preloader/has_many'
|
39
|
+
autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through'
|
40
|
+
autoload :HasOne, 'active_record/associations/preloader/has_one'
|
41
|
+
autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through'
|
42
|
+
autoload :HasAndBelongsToMany, 'active_record/associations/preloader/has_and_belongs_to_many'
|
43
|
+
autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
|
44
|
+
|
45
|
+
attr_reader :records, :associations, :options, :model
|
46
|
+
|
47
|
+
# Eager loads the named associations for the given Active Record record(s).
|
48
|
+
#
|
49
|
+
# In this description, 'association name' shall refer to the name passed
|
50
|
+
# to an association creation method. For example, a model that specifies
|
51
|
+
# <tt>belongs_to :author</tt>, <tt>has_many :buyers</tt> has association
|
52
|
+
# names +:author+ and +:buyers+.
|
53
|
+
#
|
54
|
+
# == Parameters
|
55
|
+
# +records+ is an array of ActiveRecord::Base. This array needs not be flat,
|
56
|
+
# i.e. +records+ itself may also contain arrays of records. In any case,
|
57
|
+
# +preload_associations+ will preload the all associations records by
|
58
|
+
# flattening +records+.
|
59
|
+
#
|
60
|
+
# +associations+ specifies one or more associations that you want to
|
61
|
+
# preload. It may be:
|
62
|
+
# - a Symbol or a String which specifies a single association name. For
|
63
|
+
# example, specifying +:books+ allows this method to preload all books
|
64
|
+
# for an Author.
|
65
|
+
# - an Array which specifies multiple association names. This array
|
66
|
+
# is processed recursively. For example, specifying <tt>[:avatar, :books]</tt>
|
67
|
+
# allows this method to preload an author's avatar as well as all of his
|
68
|
+
# books.
|
69
|
+
# - a Hash which specifies multiple association names, as well as
|
70
|
+
# association names for the to-be-preloaded association objects. For
|
71
|
+
# example, specifying <tt>{ :author => :avatar }</tt> will preload a
|
72
|
+
# book's author, as well as that author's avatar.
|
73
|
+
#
|
74
|
+
# +:associations+ has the same format as the +:include+ option for
|
75
|
+
# <tt>ActiveRecord::Base.find</tt>. So +associations+ could look like this:
|
76
|
+
#
|
77
|
+
# :books
|
78
|
+
# [ :books, :author ]
|
79
|
+
# { :author => :avatar }
|
80
|
+
# [ :books, { :author => :avatar } ]
|
81
|
+
#
|
82
|
+
# +options+ contains options that will be passed to ActiveRecord::Base#find
|
83
|
+
# (which is called under the hood for preloading records). But it is passed
|
84
|
+
# only one level deep in the +associations+ argument, i.e. it's not passed
|
85
|
+
# to the child associations when +associations+ is a Hash.
|
86
|
+
def initialize(records, associations, options = {})
|
87
|
+
@records = Array.wrap(records).compact.uniq
|
88
|
+
@associations = Array.wrap(associations)
|
89
|
+
@options = options
|
90
|
+
end
|
91
|
+
|
92
|
+
def run
|
93
|
+
unless records.empty?
|
94
|
+
associations.each { |association| preload(association) }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def preload(association)
|
101
|
+
case association
|
102
|
+
when Hash
|
103
|
+
preload_hash(association)
|
104
|
+
when String, Symbol
|
105
|
+
preload_one(association.to_sym)
|
106
|
+
else
|
107
|
+
raise ArgumentError, "#{association.inspect} was not recognised for preload"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def preload_hash(association)
|
112
|
+
association.each do |parent, child|
|
113
|
+
Preloader.new(records, parent, options).run
|
114
|
+
Preloader.new(records.map { |record| record.send(parent) }.flatten, child).run
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Not all records have the same class, so group then preload group on the reflection
|
119
|
+
# itself so that if various subclass share the same association then we do not split
|
120
|
+
# them unnecessarily
|
121
|
+
#
|
122
|
+
# Additionally, polymorphic belongs_to associations can have multiple associated
|
123
|
+
# classes, depending on the polymorphic_type field. So we group by the classes as
|
124
|
+
# well.
|
125
|
+
def preload_one(association)
|
126
|
+
grouped_records(association).each do |reflection, klasses|
|
127
|
+
klasses.each do |klass, records|
|
128
|
+
preloader_for(reflection).new(klass, records, reflection, options).run
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def grouped_records(association)
|
134
|
+
Hash[
|
135
|
+
records_by_reflection(association).map do |reflection, records|
|
136
|
+
[reflection, records.group_by { |record| association_klass(reflection, record) }]
|
137
|
+
end
|
138
|
+
]
|
139
|
+
end
|
140
|
+
|
141
|
+
def records_by_reflection(association)
|
142
|
+
records.group_by do |record|
|
143
|
+
reflection = record.class.reflections[association]
|
144
|
+
|
145
|
+
unless reflection
|
146
|
+
raise ActiveRecord::ConfigurationError, "Association named '#{association}' was not found; " \
|
147
|
+
"perhaps you misspelled it?"
|
148
|
+
end
|
149
|
+
|
150
|
+
reflection
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def association_klass(reflection, record)
|
155
|
+
if reflection.macro == :belongs_to && reflection.options[:polymorphic]
|
156
|
+
klass = record.send(reflection.foreign_type)
|
157
|
+
klass && klass.constantize
|
158
|
+
else
|
159
|
+
reflection.klass
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def preloader_for(reflection)
|
164
|
+
case reflection.macro
|
165
|
+
when :has_many
|
166
|
+
reflection.options[:through] ? HasManyThrough : HasMany
|
167
|
+
when :has_one
|
168
|
+
reflection.options[:through] ? HasOneThrough : HasOne
|
169
|
+
when :has_and_belongs_to_many
|
170
|
+
HasAndBelongsToMany
|
171
|
+
when :belongs_to
|
172
|
+
BelongsTo
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class Preloader
|
4
|
+
class Association #:nodoc:
|
5
|
+
attr_reader :owners, :reflection, :preload_options, :model, :klass
|
6
|
+
|
7
|
+
def initialize(klass, owners, reflection, preload_options)
|
8
|
+
@klass = klass
|
9
|
+
@owners = owners
|
10
|
+
@reflection = reflection
|
11
|
+
@preload_options = preload_options || {}
|
12
|
+
@model = owners.first && owners.first.class
|
13
|
+
@scoped = nil
|
14
|
+
@owners_by_key = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
unless owners.first.association(reflection.name).loaded?
|
19
|
+
preload
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def preload
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def scoped
|
28
|
+
@scoped ||= build_scope
|
29
|
+
end
|
30
|
+
|
31
|
+
def records_for(ids)
|
32
|
+
scoped.where(association_key.in(ids))
|
33
|
+
end
|
34
|
+
|
35
|
+
def table
|
36
|
+
klass.arel_table
|
37
|
+
end
|
38
|
+
|
39
|
+
# The name of the key on the associated records
|
40
|
+
def association_key_name
|
41
|
+
raise NotImplementedError
|
42
|
+
end
|
43
|
+
|
44
|
+
# This is overridden by HABTM as the condition should be on the foreign_key column in
|
45
|
+
# the join table
|
46
|
+
def association_key
|
47
|
+
table[association_key_name]
|
48
|
+
end
|
49
|
+
|
50
|
+
# The name of the key on the model which declares the association
|
51
|
+
def owner_key_name
|
52
|
+
raise NotImplementedError
|
53
|
+
end
|
54
|
+
|
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
|
+
def owners_by_key
|
58
|
+
@owners_by_key ||= owners.group_by do |owner|
|
59
|
+
key = owner[owner_key_name]
|
60
|
+
key && key.to_s
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def options
|
65
|
+
reflection.options
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def associated_records_by_owner
|
71
|
+
owners_map = owners_by_key
|
72
|
+
owner_keys = owners_map.keys.compact
|
73
|
+
|
74
|
+
if klass.nil? || owner_keys.empty?
|
75
|
+
records = []
|
76
|
+
else
|
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
|
+
end
|
82
|
+
|
83
|
+
# Each record may have multiple owners, and vice-versa
|
84
|
+
records_by_owner = Hash[owners.map { |owner| [owner, []] }]
|
85
|
+
records.each do |record|
|
86
|
+
owner_key = record[association_key_name].to_s
|
87
|
+
|
88
|
+
owners_map[owner_key].each do |owner|
|
89
|
+
records_by_owner[owner] << record
|
90
|
+
end
|
91
|
+
end
|
92
|
+
records_by_owner
|
93
|
+
end
|
94
|
+
|
95
|
+
def build_scope
|
96
|
+
scope = klass.scoped
|
97
|
+
|
98
|
+
scope = scope.where(process_conditions(options[:conditions]))
|
99
|
+
scope = scope.where(process_conditions(preload_options[:conditions]))
|
100
|
+
|
101
|
+
scope = scope.select(preload_options[:select] || options[:select] || table[Arel.star])
|
102
|
+
scope = scope.includes(preload_options[:include] || options[:include])
|
103
|
+
|
104
|
+
if options[:as]
|
105
|
+
scope = scope.where(
|
106
|
+
klass.table_name => {
|
107
|
+
reflection.type => model.base_class.sti_name
|
108
|
+
}
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
scope
|
113
|
+
end
|
114
|
+
|
115
|
+
def process_conditions(conditions)
|
116
|
+
if conditions.respond_to?(:to_proc)
|
117
|
+
conditions = klass.send(:instance_eval, &conditions)
|
118
|
+
end
|
119
|
+
|
120
|
+
if conditions
|
121
|
+
klass.send(:sanitize_sql, conditions)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|