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.

Files changed (221) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1372 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +218 -0
  5. data/examples/performance.rb +184 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +173 -0
  8. data/lib/active_record/aggregations.rb +266 -0
  9. data/lib/active_record/association_relation.rb +22 -0
  10. data/lib/active_record/associations.rb +1724 -0
  11. data/lib/active_record/associations/alias_tracker.rb +87 -0
  12. data/lib/active_record/associations/association.rb +253 -0
  13. data/lib/active_record/associations/association_scope.rb +194 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +111 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +149 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +116 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +91 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +124 -0
  20. data/lib/active_record/associations/builder/has_many.rb +15 -0
  21. data/lib/active_record/associations/builder/has_one.rb +23 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +38 -0
  23. data/lib/active_record/associations/collection_association.rb +634 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1027 -0
  25. data/lib/active_record/associations/has_many_association.rb +184 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +238 -0
  27. data/lib/active_record/associations/has_one_association.rb +105 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +282 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +122 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +22 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  33. data/lib/active_record/associations/preloader.rb +203 -0
  34. data/lib/active_record/associations/preloader/association.rb +162 -0
  35. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  36. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  37. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  38. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  39. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  40. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  41. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  42. data/lib/active_record/associations/preloader/through_association.rb +96 -0
  43. data/lib/active_record/associations/singular_association.rb +86 -0
  44. data/lib/active_record/associations/through_association.rb +96 -0
  45. data/lib/active_record/attribute.rb +149 -0
  46. data/lib/active_record/attribute_assignment.rb +212 -0
  47. data/lib/active_record/attribute_decorators.rb +66 -0
  48. data/lib/active_record/attribute_methods.rb +439 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +71 -0
  50. data/lib/active_record/attribute_methods/dirty.rb +181 -0
  51. data/lib/active_record/attribute_methods/primary_key.rb +128 -0
  52. data/lib/active_record/attribute_methods/query.rb +40 -0
  53. data/lib/active_record/attribute_methods/read.rb +103 -0
  54. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  55. data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
  56. data/lib/active_record/attribute_methods/write.rb +83 -0
  57. data/lib/active_record/attribute_set.rb +77 -0
  58. data/lib/active_record/attribute_set/builder.rb +86 -0
  59. data/lib/active_record/attributes.rb +139 -0
  60. data/lib/active_record/autosave_association.rb +439 -0
  61. data/lib/active_record/base.rb +317 -0
  62. data/lib/active_record/callbacks.rb +313 -0
  63. data/lib/active_record/coders/json.rb +13 -0
  64. data/lib/active_record/coders/yaml_column.rb +38 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +659 -0
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +373 -0
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +133 -0
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +574 -0
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +991 -0
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +219 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +487 -0
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +883 -0
  78. data/lib/active_record/connection_adapters/column.rb +82 -0
  79. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  80. data/lib/active_record/connection_adapters/mysql2_adapter.rb +282 -0
  81. data/lib/active_record/connection_adapters/mysql_adapter.rb +491 -0
  82. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  111. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  112. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +588 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +754 -0
  117. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +628 -0
  119. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  120. data/lib/active_record/connection_handling.rb +132 -0
  121. data/lib/active_record/core.rb +566 -0
  122. data/lib/active_record/counter_cache.rb +175 -0
  123. data/lib/active_record/dynamic_matchers.rb +140 -0
  124. data/lib/active_record/enum.rb +198 -0
  125. data/lib/active_record/errors.rb +252 -0
  126. data/lib/active_record/explain.rb +38 -0
  127. data/lib/active_record/explain_registry.rb +30 -0
  128. data/lib/active_record/explain_subscriber.rb +29 -0
  129. data/lib/active_record/fixture_set/file.rb +56 -0
  130. data/lib/active_record/fixtures.rb +1007 -0
  131. data/lib/active_record/gem_version.rb +15 -0
  132. data/lib/active_record/inheritance.rb +247 -0
  133. data/lib/active_record/integration.rb +113 -0
  134. data/lib/active_record/locale/en.yml +47 -0
  135. data/lib/active_record/locking/optimistic.rb +204 -0
  136. data/lib/active_record/locking/pessimistic.rb +77 -0
  137. data/lib/active_record/log_subscriber.rb +75 -0
  138. data/lib/active_record/migration.rb +1051 -0
  139. data/lib/active_record/migration/command_recorder.rb +197 -0
  140. data/lib/active_record/migration/join_table.rb +15 -0
  141. data/lib/active_record/model_schema.rb +340 -0
  142. data/lib/active_record/nested_attributes.rb +548 -0
  143. data/lib/active_record/no_touching.rb +52 -0
  144. data/lib/active_record/null_relation.rb +81 -0
  145. data/lib/active_record/persistence.rb +532 -0
  146. data/lib/active_record/query_cache.rb +56 -0
  147. data/lib/active_record/querying.rb +68 -0
  148. data/lib/active_record/railtie.rb +162 -0
  149. data/lib/active_record/railties/console_sandbox.rb +5 -0
  150. data/lib/active_record/railties/controller_runtime.rb +50 -0
  151. data/lib/active_record/railties/databases.rake +391 -0
  152. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  153. data/lib/active_record/readonly_attributes.rb +23 -0
  154. data/lib/active_record/reflection.rb +881 -0
  155. data/lib/active_record/relation.rb +681 -0
  156. data/lib/active_record/relation/batches.rb +138 -0
  157. data/lib/active_record/relation/calculations.rb +403 -0
  158. data/lib/active_record/relation/delegation.rb +140 -0
  159. data/lib/active_record/relation/finder_methods.rb +528 -0
  160. data/lib/active_record/relation/merger.rb +170 -0
  161. data/lib/active_record/relation/predicate_builder.rb +126 -0
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +47 -0
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  164. data/lib/active_record/relation/query_methods.rb +1176 -0
  165. data/lib/active_record/relation/spawn_methods.rb +75 -0
  166. data/lib/active_record/result.rb +131 -0
  167. data/lib/active_record/runtime_registry.rb +22 -0
  168. data/lib/active_record/sanitization.rb +191 -0
  169. data/lib/active_record/schema.rb +64 -0
  170. data/lib/active_record/schema_dumper.rb +251 -0
  171. data/lib/active_record/schema_migration.rb +56 -0
  172. data/lib/active_record/scoping.rb +87 -0
  173. data/lib/active_record/scoping/default.rb +134 -0
  174. data/lib/active_record/scoping/named.rb +164 -0
  175. data/lib/active_record/serialization.rb +22 -0
  176. data/lib/active_record/serializers/xml_serializer.rb +193 -0
  177. data/lib/active_record/statement_cache.rb +111 -0
  178. data/lib/active_record/store.rb +205 -0
  179. data/lib/active_record/tasks/database_tasks.rb +296 -0
  180. data/lib/active_record/tasks/mysql_database_tasks.rb +145 -0
  181. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  182. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  183. data/lib/active_record/timestamp.rb +121 -0
  184. data/lib/active_record/transactions.rb +417 -0
  185. data/lib/active_record/translation.rb +22 -0
  186. data/lib/active_record/type.rb +23 -0
  187. data/lib/active_record/type/big_integer.rb +13 -0
  188. data/lib/active_record/type/binary.rb +50 -0
  189. data/lib/active_record/type/boolean.rb +30 -0
  190. data/lib/active_record/type/date.rb +46 -0
  191. data/lib/active_record/type/date_time.rb +43 -0
  192. data/lib/active_record/type/decimal.rb +40 -0
  193. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  194. data/lib/active_record/type/decorator.rb +14 -0
  195. data/lib/active_record/type/float.rb +19 -0
  196. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  197. data/lib/active_record/type/integer.rb +55 -0
  198. data/lib/active_record/type/mutable.rb +16 -0
  199. data/lib/active_record/type/numeric.rb +36 -0
  200. data/lib/active_record/type/serialized.rb +56 -0
  201. data/lib/active_record/type/string.rb +36 -0
  202. data/lib/active_record/type/text.rb +11 -0
  203. data/lib/active_record/type/time.rb +26 -0
  204. data/lib/active_record/type/time_value.rb +38 -0
  205. data/lib/active_record/type/type_map.rb +64 -0
  206. data/lib/active_record/type/unsigned_integer.rb +15 -0
  207. data/lib/active_record/type/value.rb +101 -0
  208. data/lib/active_record/validations.rb +90 -0
  209. data/lib/active_record/validations/associated.rb +51 -0
  210. data/lib/active_record/validations/presence.rb +67 -0
  211. data/lib/active_record/validations/uniqueness.rb +229 -0
  212. data/lib/active_record/version.rb +8 -0
  213. data/lib/rails/generators/active_record.rb +17 -0
  214. data/lib/rails/generators/active_record/migration.rb +18 -0
  215. data/lib/rails/generators/active_record/migration/migration_generator.rb +70 -0
  216. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +22 -0
  217. data/lib/rails/generators/active_record/migration/templates/migration.rb +45 -0
  218. data/lib/rails/generators/active_record/model/model_generator.rb +52 -0
  219. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  220. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  221. 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