sskirby-activerecord 3.2.1

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.
Files changed (150) hide show
  1. data/CHANGELOG.md +6749 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +222 -0
  4. data/examples/associations.png +0 -0
  5. data/examples/performance.rb +177 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +147 -0
  8. data/lib/active_record/aggregations.rb +255 -0
  9. data/lib/active_record/associations.rb +1604 -0
  10. data/lib/active_record/associations/alias_tracker.rb +79 -0
  11. data/lib/active_record/associations/association.rb +239 -0
  12. data/lib/active_record/associations/association_scope.rb +119 -0
  13. data/lib/active_record/associations/belongs_to_association.rb +79 -0
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
  15. data/lib/active_record/associations/builder/association.rb +55 -0
  16. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  17. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  19. data/lib/active_record/associations/builder/has_many.rb +71 -0
  20. data/lib/active_record/associations/builder/has_one.rb +62 -0
  21. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  22. data/lib/active_record/associations/collection_association.rb +574 -0
  23. data/lib/active_record/associations/collection_proxy.rb +132 -0
  24. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
  25. data/lib/active_record/associations/has_many_association.rb +108 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +180 -0
  27. data/lib/active_record/associations/has_one_association.rb +73 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +214 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  33. data/lib/active_record/associations/join_helper.rb +55 -0
  34. data/lib/active_record/associations/preloader.rb +177 -0
  35. data/lib/active_record/associations/preloader/association.rb +127 -0
  36. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  37. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  38. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  39. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  40. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  41. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  42. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  43. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  44. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  45. data/lib/active_record/associations/singular_association.rb +64 -0
  46. data/lib/active_record/associations/through_association.rb +83 -0
  47. data/lib/active_record/attribute_assignment.rb +221 -0
  48. data/lib/active_record/attribute_methods.rb +272 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  50. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  51. data/lib/active_record/attribute_methods/dirty.rb +101 -0
  52. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  53. data/lib/active_record/attribute_methods/query.rb +39 -0
  54. data/lib/active_record/attribute_methods/read.rb +135 -0
  55. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  56. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
  57. data/lib/active_record/attribute_methods/write.rb +69 -0
  58. data/lib/active_record/autosave_association.rb +422 -0
  59. data/lib/active_record/base.rb +716 -0
  60. data/lib/active_record/callbacks.rb +275 -0
  61. data/lib/active_record/coders/yaml_column.rb +41 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
  70. data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
  71. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  72. data/lib/active_record/connection_adapters/column.rb +270 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
  75. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
  76. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
  78. data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
  79. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  80. data/lib/active_record/counter_cache.rb +119 -0
  81. data/lib/active_record/dynamic_finder_match.rb +56 -0
  82. data/lib/active_record/dynamic_matchers.rb +79 -0
  83. data/lib/active_record/dynamic_scope_match.rb +23 -0
  84. data/lib/active_record/errors.rb +195 -0
  85. data/lib/active_record/explain.rb +85 -0
  86. data/lib/active_record/explain_subscriber.rb +21 -0
  87. data/lib/active_record/fixtures.rb +906 -0
  88. data/lib/active_record/fixtures/file.rb +65 -0
  89. data/lib/active_record/identity_map.rb +156 -0
  90. data/lib/active_record/inheritance.rb +167 -0
  91. data/lib/active_record/integration.rb +49 -0
  92. data/lib/active_record/locale/en.yml +40 -0
  93. data/lib/active_record/locking/optimistic.rb +183 -0
  94. data/lib/active_record/locking/pessimistic.rb +77 -0
  95. data/lib/active_record/log_subscriber.rb +68 -0
  96. data/lib/active_record/migration.rb +765 -0
  97. data/lib/active_record/migration/command_recorder.rb +105 -0
  98. data/lib/active_record/model_schema.rb +366 -0
  99. data/lib/active_record/nested_attributes.rb +469 -0
  100. data/lib/active_record/observer.rb +121 -0
  101. data/lib/active_record/persistence.rb +372 -0
  102. data/lib/active_record/query_cache.rb +74 -0
  103. data/lib/active_record/querying.rb +58 -0
  104. data/lib/active_record/railtie.rb +119 -0
  105. data/lib/active_record/railties/console_sandbox.rb +6 -0
  106. data/lib/active_record/railties/controller_runtime.rb +49 -0
  107. data/lib/active_record/railties/databases.rake +620 -0
  108. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  109. data/lib/active_record/readonly_attributes.rb +26 -0
  110. data/lib/active_record/reflection.rb +534 -0
  111. data/lib/active_record/relation.rb +534 -0
  112. data/lib/active_record/relation/batches.rb +90 -0
  113. data/lib/active_record/relation/calculations.rb +354 -0
  114. data/lib/active_record/relation/delegation.rb +49 -0
  115. data/lib/active_record/relation/finder_methods.rb +398 -0
  116. data/lib/active_record/relation/predicate_builder.rb +58 -0
  117. data/lib/active_record/relation/query_methods.rb +417 -0
  118. data/lib/active_record/relation/spawn_methods.rb +148 -0
  119. data/lib/active_record/result.rb +34 -0
  120. data/lib/active_record/sanitization.rb +194 -0
  121. data/lib/active_record/schema.rb +58 -0
  122. data/lib/active_record/schema_dumper.rb +204 -0
  123. data/lib/active_record/scoping.rb +152 -0
  124. data/lib/active_record/scoping/default.rb +142 -0
  125. data/lib/active_record/scoping/named.rb +202 -0
  126. data/lib/active_record/serialization.rb +18 -0
  127. data/lib/active_record/serializers/xml_serializer.rb +202 -0
  128. data/lib/active_record/session_store.rb +358 -0
  129. data/lib/active_record/store.rb +50 -0
  130. data/lib/active_record/test_case.rb +73 -0
  131. data/lib/active_record/timestamp.rb +113 -0
  132. data/lib/active_record/transactions.rb +360 -0
  133. data/lib/active_record/translation.rb +22 -0
  134. data/lib/active_record/validations.rb +83 -0
  135. data/lib/active_record/validations/associated.rb +43 -0
  136. data/lib/active_record/validations/uniqueness.rb +180 -0
  137. data/lib/active_record/version.rb +10 -0
  138. data/lib/rails/generators/active_record.rb +25 -0
  139. data/lib/rails/generators/active_record/migration.rb +15 -0
  140. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  141. data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
  142. data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
  143. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  144. data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
  145. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  146. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  147. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  148. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  149. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  150. 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