activerecord 1.0.0 → 4.0.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 (255) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +213 -0
  5. data/examples/performance.rb +172 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record/aggregations.rb +180 -84
  8. data/lib/active_record/associations/alias_tracker.rb +76 -0
  9. data/lib/active_record/associations/association.rb +248 -0
  10. data/lib/active_record/associations/association_scope.rb +135 -0
  11. data/lib/active_record/associations/belongs_to_association.rb +92 -0
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +35 -0
  13. data/lib/active_record/associations/builder/association.rb +108 -0
  14. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  15. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  17. data/lib/active_record/associations/builder/has_many.rb +15 -0
  18. data/lib/active_record/associations/builder/has_one.rb +25 -0
  19. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  20. data/lib/active_record/associations/collection_association.rb +608 -0
  21. data/lib/active_record/associations/collection_proxy.rb +986 -0
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +58 -39
  23. data/lib/active_record/associations/has_many_association.rb +116 -85
  24. data/lib/active_record/associations/has_many_through_association.rb +197 -0
  25. data/lib/active_record/associations/has_one_association.rb +102 -0
  26. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_dependency.rb +235 -0
  31. data/lib/active_record/associations/join_helper.rb +45 -0
  32. data/lib/active_record/associations/preloader/association.rb +121 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  42. data/lib/active_record/associations/preloader.rb +178 -0
  43. data/lib/active_record/associations/singular_association.rb +64 -0
  44. data/lib/active_record/associations/through_association.rb +87 -0
  45. data/lib/active_record/associations.rb +1437 -431
  46. data/lib/active_record/attribute_assignment.rb +201 -0
  47. data/lib/active_record/attribute_methods/before_type_cast.rb +70 -0
  48. data/lib/active_record/attribute_methods/dirty.rb +118 -0
  49. data/lib/active_record/attribute_methods/primary_key.rb +122 -0
  50. data/lib/active_record/attribute_methods/query.rb +40 -0
  51. data/lib/active_record/attribute_methods/read.rb +107 -0
  52. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  53. data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -0
  54. data/lib/active_record/attribute_methods/write.rb +63 -0
  55. data/lib/active_record/attribute_methods.rb +393 -0
  56. data/lib/active_record/autosave_association.rb +426 -0
  57. data/lib/active_record/base.rb +268 -930
  58. data/lib/active_record/callbacks.rb +203 -230
  59. data/lib/active_record/coders/yaml_column.rb +38 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +638 -0
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +390 -0
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +129 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +501 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +873 -0
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +389 -275
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  71. data/lib/active_record/connection_adapters/column.rb +318 -0
  72. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +517 -90
  75. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  76. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  77. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  81. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  82. data/lib/active_record/connection_adapters/postgresql_adapter.rb +911 -138
  83. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  84. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +624 -0
  85. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  86. data/lib/active_record/connection_handling.rb +98 -0
  87. data/lib/active_record/core.rb +463 -0
  88. data/lib/active_record/counter_cache.rb +122 -0
  89. data/lib/active_record/dynamic_matchers.rb +131 -0
  90. data/lib/active_record/errors.rb +213 -0
  91. data/lib/active_record/explain.rb +38 -0
  92. data/lib/active_record/explain_registry.rb +30 -0
  93. data/lib/active_record/explain_subscriber.rb +29 -0
  94. data/lib/active_record/fixture_set/file.rb +55 -0
  95. data/lib/active_record/fixtures.rb +892 -138
  96. data/lib/active_record/inheritance.rb +200 -0
  97. data/lib/active_record/integration.rb +60 -0
  98. data/lib/active_record/locale/en.yml +47 -0
  99. data/lib/active_record/locking/optimistic.rb +181 -0
  100. data/lib/active_record/locking/pessimistic.rb +77 -0
  101. data/lib/active_record/log_subscriber.rb +82 -0
  102. data/lib/active_record/migration/command_recorder.rb +164 -0
  103. data/lib/active_record/migration/join_table.rb +15 -0
  104. data/lib/active_record/migration.rb +1015 -0
  105. data/lib/active_record/model_schema.rb +345 -0
  106. data/lib/active_record/nested_attributes.rb +546 -0
  107. data/lib/active_record/null_relation.rb +65 -0
  108. data/lib/active_record/persistence.rb +509 -0
  109. data/lib/active_record/query_cache.rb +56 -0
  110. data/lib/active_record/querying.rb +62 -0
  111. data/lib/active_record/railtie.rb +205 -0
  112. data/lib/active_record/railties/console_sandbox.rb +5 -0
  113. data/lib/active_record/railties/controller_runtime.rb +50 -0
  114. data/lib/active_record/railties/databases.rake +402 -0
  115. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  116. data/lib/active_record/readonly_attributes.rb +30 -0
  117. data/lib/active_record/reflection.rb +544 -87
  118. data/lib/active_record/relation/batches.rb +93 -0
  119. data/lib/active_record/relation/calculations.rb +399 -0
  120. data/lib/active_record/relation/delegation.rb +125 -0
  121. data/lib/active_record/relation/finder_methods.rb +349 -0
  122. data/lib/active_record/relation/merger.rb +161 -0
  123. data/lib/active_record/relation/predicate_builder.rb +106 -0
  124. data/lib/active_record/relation/query_methods.rb +1044 -0
  125. data/lib/active_record/relation/spawn_methods.rb +73 -0
  126. data/lib/active_record/relation.rb +655 -0
  127. data/lib/active_record/result.rb +67 -0
  128. data/lib/active_record/runtime_registry.rb +17 -0
  129. data/lib/active_record/sanitization.rb +168 -0
  130. data/lib/active_record/schema.rb +65 -0
  131. data/lib/active_record/schema_dumper.rb +204 -0
  132. data/lib/active_record/schema_migration.rb +39 -0
  133. data/lib/active_record/scoping/default.rb +146 -0
  134. data/lib/active_record/scoping/named.rb +175 -0
  135. data/lib/active_record/scoping.rb +82 -0
  136. data/lib/active_record/serialization.rb +22 -0
  137. data/lib/active_record/serializers/xml_serializer.rb +197 -0
  138. data/lib/active_record/statement_cache.rb +26 -0
  139. data/lib/active_record/store.rb +156 -0
  140. data/lib/active_record/tasks/database_tasks.rb +203 -0
  141. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  142. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  143. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  144. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  145. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  146. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  147. data/lib/active_record/test_case.rb +96 -0
  148. data/lib/active_record/timestamp.rb +119 -0
  149. data/lib/active_record/transactions.rb +366 -69
  150. data/lib/active_record/translation.rb +22 -0
  151. data/lib/active_record/validations/associated.rb +49 -0
  152. data/lib/active_record/validations/presence.rb +65 -0
  153. data/lib/active_record/validations/uniqueness.rb +225 -0
  154. data/lib/active_record/validations.rb +64 -185
  155. data/lib/active_record/version.rb +11 -0
  156. data/lib/active_record.rb +149 -24
  157. data/lib/rails/generators/active_record/migration/migration_generator.rb +62 -0
  158. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  159. data/lib/rails/generators/active_record/migration/templates/migration.rb +39 -0
  160. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  161. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  162. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  163. data/lib/rails/generators/active_record.rb +23 -0
  164. metadata +261 -161
  165. data/CHANGELOG +0 -581
  166. data/README +0 -361
  167. data/RUNNING_UNIT_TESTS +0 -36
  168. data/dev-utils/eval_debugger.rb +0 -9
  169. data/examples/associations.png +0 -0
  170. data/examples/associations.rb +0 -87
  171. data/examples/shared_setup.rb +0 -15
  172. data/examples/validation.rb +0 -88
  173. data/install.rb +0 -60
  174. data/lib/active_record/associations/association_collection.rb +0 -70
  175. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -107
  176. data/lib/active_record/deprecated_associations.rb +0 -70
  177. data/lib/active_record/observer.rb +0 -71
  178. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  179. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  180. data/lib/active_record/support/clean_logger.rb +0 -10
  181. data/lib/active_record/support/inflector.rb +0 -70
  182. data/lib/active_record/vendor/mysql.rb +0 -1117
  183. data/lib/active_record/vendor/simple.rb +0 -702
  184. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  185. data/lib/active_record/wrappings.rb +0 -59
  186. data/rakefile +0 -122
  187. data/test/abstract_unit.rb +0 -16
  188. data/test/aggregations_test.rb +0 -34
  189. data/test/all.sh +0 -8
  190. data/test/associations_test.rb +0 -477
  191. data/test/base_test.rb +0 -513
  192. data/test/class_inheritable_attributes_test.rb +0 -33
  193. data/test/connections/native_mysql/connection.rb +0 -24
  194. data/test/connections/native_postgresql/connection.rb +0 -24
  195. data/test/connections/native_sqlite/connection.rb +0 -24
  196. data/test/deprecated_associations_test.rb +0 -336
  197. data/test/finder_test.rb +0 -67
  198. data/test/fixtures/accounts/signals37 +0 -3
  199. data/test/fixtures/accounts/unknown +0 -2
  200. data/test/fixtures/auto_id.rb +0 -4
  201. data/test/fixtures/column_name.rb +0 -3
  202. data/test/fixtures/companies/first_client +0 -6
  203. data/test/fixtures/companies/first_firm +0 -4
  204. data/test/fixtures/companies/second_client +0 -6
  205. data/test/fixtures/company.rb +0 -37
  206. data/test/fixtures/company_in_module.rb +0 -33
  207. data/test/fixtures/course.rb +0 -3
  208. data/test/fixtures/courses/java +0 -2
  209. data/test/fixtures/courses/ruby +0 -2
  210. data/test/fixtures/customer.rb +0 -30
  211. data/test/fixtures/customers/david +0 -6
  212. data/test/fixtures/db_definitions/mysql.sql +0 -96
  213. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  214. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  215. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  216. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  217. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  218. data/test/fixtures/default.rb +0 -2
  219. data/test/fixtures/developer.rb +0 -8
  220. data/test/fixtures/developers/david +0 -2
  221. data/test/fixtures/developers/jamis +0 -2
  222. data/test/fixtures/developers_projects/david_action_controller +0 -2
  223. data/test/fixtures/developers_projects/david_active_record +0 -2
  224. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  225. data/test/fixtures/entrant.rb +0 -3
  226. data/test/fixtures/entrants/first +0 -3
  227. data/test/fixtures/entrants/second +0 -3
  228. data/test/fixtures/entrants/third +0 -3
  229. data/test/fixtures/fixture_database.sqlite +0 -0
  230. data/test/fixtures/fixture_database_2.sqlite +0 -0
  231. data/test/fixtures/movie.rb +0 -5
  232. data/test/fixtures/movies/first +0 -2
  233. data/test/fixtures/movies/second +0 -2
  234. data/test/fixtures/project.rb +0 -3
  235. data/test/fixtures/projects/action_controller +0 -2
  236. data/test/fixtures/projects/active_record +0 -2
  237. data/test/fixtures/reply.rb +0 -21
  238. data/test/fixtures/subscriber.rb +0 -5
  239. data/test/fixtures/subscribers/first +0 -2
  240. data/test/fixtures/subscribers/second +0 -2
  241. data/test/fixtures/topic.rb +0 -20
  242. data/test/fixtures/topics/first +0 -9
  243. data/test/fixtures/topics/second +0 -8
  244. data/test/fixtures_test.rb +0 -20
  245. data/test/inflector_test.rb +0 -104
  246. data/test/inheritance_test.rb +0 -125
  247. data/test/lifecycle_test.rb +0 -110
  248. data/test/modules_test.rb +0 -21
  249. data/test/multiple_db_test.rb +0 -46
  250. data/test/pk_test.rb +0 -57
  251. data/test/reflection_test.rb +0 -78
  252. data/test/thread_safety_test.rb +0 -33
  253. data/test/transactions_test.rb +0 -83
  254. data/test/unconnected_test.rb +0 -24
  255. data/test/validations_test.rb +0 -126
@@ -0,0 +1,24 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class CollectionAssociation < Association #:nodoc:
5
+
6
+ private
7
+
8
+ def build_scope
9
+ super.order(preload_scope.values[:order] || reflection_scope.values[:order])
10
+ end
11
+
12
+ def preload
13
+ associated_records_by_owner.each do |owner, records|
14
+ association = owner.association(reflection.name)
15
+ association.loaded!
16
+ association.target.concat(records)
17
+ records.each { |record| association.set_inverse_instance(record) }
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,60 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class HasAndBelongsToMany < CollectionAssociation #:nodoc:
5
+ attr_reader :join_table
6
+
7
+ def initialize(klass, records, reflection, preload_options)
8
+ super
9
+ @join_table = Arel::Table.new(reflection.join_table).alias('t0')
10
+ end
11
+
12
+ # Unlike the other associations, we want to get a raw array of rows so that we can
13
+ # access the aliased column on the join table
14
+ def records_for(ids)
15
+ scope = super
16
+ klass.connection.select_all(scope.arel, 'SQL', scope.bind_values)
17
+ end
18
+
19
+ def owner_key_name
20
+ reflection.active_record_primary_key
21
+ end
22
+
23
+ def association_key_name
24
+ 'ar_association_key_name'
25
+ end
26
+
27
+ def association_key
28
+ join_table[reflection.foreign_key]
29
+ end
30
+
31
+ private
32
+
33
+ # Once we have used the join table column (in super), we manually instantiate the
34
+ # actual records, ensuring that we don't create more than one instances of the same
35
+ # record
36
+ def associated_records_by_owner
37
+ records = {}
38
+ super.each_value do |rows|
39
+ rows.map! { |row| records[row[klass.primary_key]] ||= klass.instantiate(row) }
40
+ end
41
+ end
42
+
43
+ def build_scope
44
+ super.joins(join).select(join_select)
45
+ end
46
+
47
+ def join_select
48
+ association_key.as(Arel.sql(association_key_name))
49
+ end
50
+
51
+ def join
52
+ condition = table[reflection.association_primary_key].eq(
53
+ join_table[reflection.association_foreign_key])
54
+
55
+ table.create_join(join_table, table.create_on(condition))
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class HasMany < CollectionAssociation #:nodoc:
5
+
6
+ def association_key_name
7
+ reflection.foreign_key
8
+ end
9
+
10
+ def owner_key_name
11
+ reflection.active_record_primary_key
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class HasManyThrough < CollectionAssociation #:nodoc:
5
+ include ThroughAssociation
6
+
7
+ def associated_records_by_owner
8
+ records_by_owner = super
9
+
10
+ if reflection_scope.distinct_value
11
+ records_by_owner.each_value { |records| records.uniq! }
12
+ end
13
+
14
+ records_by_owner
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class HasOne < SingularAssociation #:nodoc:
5
+
6
+ def association_key_name
7
+ reflection.foreign_key
8
+ end
9
+
10
+ def owner_key_name
11
+ reflection.active_record_primary_key
12
+ end
13
+
14
+ private
15
+
16
+ def build_scope
17
+ super.order(preload_scope.values[:order] || reflection_scope.values[:order])
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class HasOneThrough < SingularAssociation #:nodoc:
5
+ include ThroughAssociation
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class SingularAssociation < Association #:nodoc:
5
+
6
+ private
7
+
8
+ def preload
9
+ associated_records_by_owner.each do |owner, associated_records|
10
+ record = associated_records.first
11
+
12
+ association = owner.association(reflection.name)
13
+ association.target = record
14
+ association.set_inverse_instance(record)
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,63 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ module ThroughAssociation #:nodoc:
5
+
6
+ def through_reflection
7
+ reflection.through_reflection
8
+ end
9
+
10
+ def source_reflection
11
+ reflection.source_reflection
12
+ end
13
+
14
+ def associated_records_by_owner
15
+ through_records = through_records_by_owner
16
+
17
+ Preloader.new(through_records.values.flatten, source_reflection.name, reflection_scope).run
18
+
19
+ through_records.each do |owner, records|
20
+ records.map! { |r| r.send(source_reflection.name) }.flatten!
21
+ records.compact!
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def through_records_by_owner
28
+ Preloader.new(owners, through_reflection.name, through_scope).run
29
+
30
+ Hash[owners.map do |owner|
31
+ through_records = Array.wrap(owner.send(through_reflection.name))
32
+
33
+ # Dont cache the association - we would only be caching a subset
34
+ if (through_scope != through_reflection.klass.unscoped) ||
35
+ (reflection.options[:source_type] && through_reflection.collection?)
36
+ owner.association(through_reflection.name).reset
37
+ end
38
+
39
+ [owner, through_records]
40
+ end]
41
+ end
42
+
43
+ def through_scope
44
+ through_scope = through_reflection.klass.unscoped
45
+
46
+ if options[:source_type]
47
+ through_scope.where! reflection.foreign_type => options[:source_type]
48
+ else
49
+ unless reflection_scope.where_values.empty?
50
+ through_scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
51
+ through_scope.where_values = reflection_scope.values[:where]
52
+ end
53
+
54
+ through_scope.references! reflection_scope.values[:references]
55
+ through_scope.order! reflection_scope.values[:order] if through_scope.eager_loading?
56
+ end
57
+
58
+ through_scope
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,178 @@
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.author_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
+ extend ActiveSupport::Autoload
34
+
35
+ eager_autoload do
36
+ autoload :Association, 'active_record/associations/preloader/association'
37
+ autoload :SingularAssociation, 'active_record/associations/preloader/singular_association'
38
+ autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association'
39
+ autoload :ThroughAssociation, 'active_record/associations/preloader/through_association'
40
+
41
+ autoload :HasMany, 'active_record/associations/preloader/has_many'
42
+ autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through'
43
+ autoload :HasOne, 'active_record/associations/preloader/has_one'
44
+ autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through'
45
+ autoload :HasAndBelongsToMany, 'active_record/associations/preloader/has_and_belongs_to_many'
46
+ autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
47
+ end
48
+
49
+ attr_reader :records, :associations, :preload_scope, :model
50
+
51
+ # Eager loads the named associations for the given Active Record record(s).
52
+ #
53
+ # In this description, 'association name' shall refer to the name passed
54
+ # to an association creation method. For example, a model that specifies
55
+ # <tt>belongs_to :author</tt>, <tt>has_many :buyers</tt> has association
56
+ # names +:author+ and +:buyers+.
57
+ #
58
+ # == Parameters
59
+ # +records+ is an array of ActiveRecord::Base. This array needs not be flat,
60
+ # i.e. +records+ itself may also contain arrays of records. In any case,
61
+ # +preload_associations+ will preload the all associations records by
62
+ # flattening +records+.
63
+ #
64
+ # +associations+ specifies one or more associations that you want to
65
+ # preload. It may be:
66
+ # - a Symbol or a String which specifies a single association name. For
67
+ # example, specifying +:books+ allows this method to preload all books
68
+ # for an Author.
69
+ # - an Array which specifies multiple association names. This array
70
+ # is processed recursively. For example, specifying <tt>[:avatar, :books]</tt>
71
+ # allows this method to preload an author's avatar as well as all of his
72
+ # books.
73
+ # - a Hash which specifies multiple association names, as well as
74
+ # association names for the to-be-preloaded association objects. For
75
+ # example, specifying <tt>{ author: :avatar }</tt> will preload a
76
+ # book's author, as well as that author's avatar.
77
+ #
78
+ # +:associations+ has the same format as the +:include+ option for
79
+ # <tt>ActiveRecord::Base.find</tt>. So +associations+ could look like this:
80
+ #
81
+ # :books
82
+ # [ :books, :author ]
83
+ # { author: :avatar }
84
+ # [ :books, { author: :avatar } ]
85
+ def initialize(records, associations, preload_scope = nil)
86
+ @records = Array.wrap(records).compact.uniq
87
+ @associations = Array.wrap(associations)
88
+ @preload_scope = preload_scope || Relation.new(nil, nil)
89
+ end
90
+
91
+ def run
92
+ unless records.empty?
93
+ associations.each { |association| preload(association) }
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ def preload(association)
100
+ case association
101
+ when Hash
102
+ preload_hash(association)
103
+ when Symbol
104
+ preload_one(association)
105
+ when String
106
+ preload_one(association.to_sym)
107
+ else
108
+ raise ArgumentError, "#{association.inspect} was not recognised for preload"
109
+ end
110
+ end
111
+
112
+ def preload_hash(association)
113
+ association.each do |parent, child|
114
+ Preloader.new(records, parent, preload_scope).run
115
+ Preloader.new(records.map { |record| record.send(parent) }.flatten, child).run
116
+ end
117
+ end
118
+
119
+ # Not all records have the same class, so group then preload group on the reflection
120
+ # itself so that if various subclass share the same association then we do not split
121
+ # them unnecessarily
122
+ #
123
+ # Additionally, polymorphic belongs_to associations can have multiple associated
124
+ # classes, depending on the polymorphic_type field. So we group by the classes as
125
+ # well.
126
+ def preload_one(association)
127
+ grouped_records(association).each do |reflection, klasses|
128
+ klasses.each do |klass, records|
129
+ preloader_for(reflection).new(klass, records, reflection, preload_scope).run
130
+ end
131
+ end
132
+ end
133
+
134
+ def grouped_records(association)
135
+ Hash[
136
+ records_by_reflection(association).map do |reflection, records|
137
+ [reflection, records.group_by { |record| association_klass(reflection, record) }]
138
+ end
139
+ ]
140
+ end
141
+
142
+ def records_by_reflection(association)
143
+ records.group_by do |record|
144
+ reflection = record.class.reflections[association]
145
+
146
+ unless reflection
147
+ raise ActiveRecord::ConfigurationError, "Association named '#{association}' was not found; " \
148
+ "perhaps you misspelled it?"
149
+ end
150
+
151
+ reflection
152
+ end
153
+ end
154
+
155
+ def association_klass(reflection, record)
156
+ if reflection.macro == :belongs_to && reflection.options[:polymorphic]
157
+ klass = record.send(reflection.foreign_type)
158
+ klass && klass.constantize
159
+ else
160
+ reflection.klass
161
+ end
162
+ end
163
+
164
+ def preloader_for(reflection)
165
+ case reflection.macro
166
+ when :has_many
167
+ reflection.options[:through] ? HasManyThrough : HasMany
168
+ when :has_one
169
+ reflection.options[:through] ? HasOneThrough : HasOne
170
+ when :has_and_belongs_to_many
171
+ HasAndBelongsToMany
172
+ when :belongs_to
173
+ BelongsTo
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,64 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class SingularAssociation < Association #:nodoc:
4
+ # Implements the reader method, e.g. foo.bar for Foo.has_one :bar
5
+ def reader(force_reload = false)
6
+ if force_reload
7
+ klass.uncached { reload }
8
+ elsif !loaded? || stale_target?
9
+ reload
10
+ end
11
+
12
+ target
13
+ end
14
+
15
+ # Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
16
+ def writer(record)
17
+ replace(record)
18
+ end
19
+
20
+ def create(attributes = {}, &block)
21
+ create_record(attributes, &block)
22
+ end
23
+
24
+ def create!(attributes = {}, &block)
25
+ create_record(attributes, true, &block)
26
+ end
27
+
28
+ def build(attributes = {})
29
+ record = build_record(attributes)
30
+ yield(record) if block_given?
31
+ set_new_record(record)
32
+ record
33
+ end
34
+
35
+ private
36
+
37
+ def create_scope
38
+ scope.scope_for_create.stringify_keys.except(klass.primary_key)
39
+ end
40
+
41
+ def find_target
42
+ scope.first.tap { |record| set_inverse_instance(record) }
43
+ end
44
+
45
+ # Implemented by subclasses
46
+ def replace(record)
47
+ raise NotImplementedError, "Subclasses must implement a replace(record) method"
48
+ end
49
+
50
+ def set_new_record(record)
51
+ replace(record)
52
+ end
53
+
54
+ def create_record(attributes, raise_error = false)
55
+ record = build_record(attributes)
56
+ yield(record) if block_given?
57
+ saved = record.save
58
+ set_new_record(record)
59
+ raise RecordInvalid.new(record) if !saved && raise_error
60
+ record
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,87 @@
1
+ module ActiveRecord
2
+ # = Active Record Through Association
3
+ module Associations
4
+ module ThroughAssociation #:nodoc:
5
+
6
+ delegate :source_reflection, :through_reflection, :chain, :to => :reflection
7
+
8
+ protected
9
+
10
+ # We merge in these scopes for two reasons:
11
+ #
12
+ # 1. To get the default_scope conditions for any of the other reflections in the chain
13
+ # 2. To get the type conditions for any STI models in the chain
14
+ def target_scope
15
+ scope = super
16
+ chain[1..-1].each do |reflection|
17
+ scope.merge!(
18
+ reflection.klass.all.with_default_scope.
19
+ except(:select, :create_with, :includes, :preload, :joins, :eager_load)
20
+ )
21
+ end
22
+ scope
23
+ end
24
+
25
+ private
26
+
27
+ # Construct attributes for :through pointing to owner and associate. This is used by the
28
+ # methods which create and delete records on the association.
29
+ #
30
+ # We only support indirectly modifying through associations which has a belongs_to source.
31
+ # This is the "has_many :tags, through: :taggings" situation, where the join model
32
+ # typically has a belongs_to on both side. In other words, associations which could also
33
+ # be represented as has_and_belongs_to_many associations.
34
+ #
35
+ # We do not support creating/deleting records on the association where the source has
36
+ # some other type, because this opens up a whole can of worms, and in basically any
37
+ # situation it is more natural for the user to just create or modify their join records
38
+ # directly as required.
39
+ def construct_join_attributes(*records)
40
+ ensure_mutable
41
+
42
+ join_attributes = {
43
+ source_reflection.foreign_key =>
44
+ records.map { |record|
45
+ record.send(source_reflection.association_primary_key(reflection.klass))
46
+ }
47
+ }
48
+
49
+ if options[:source_type]
50
+ join_attributes[source_reflection.foreign_type] =
51
+ records.map { |record| record.class.base_class.name }
52
+ end
53
+
54
+ if records.count == 1
55
+ Hash[join_attributes.map { |k, v| [k, v.first] }]
56
+ else
57
+ join_attributes
58
+ end
59
+ end
60
+
61
+ # Note: this does not capture all cases, for example it would be crazy to try to
62
+ # properly support stale-checking for nested associations.
63
+ def stale_state
64
+ if through_reflection.macro == :belongs_to
65
+ owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
66
+ end
67
+ end
68
+
69
+ def foreign_key_present?
70
+ through_reflection.macro == :belongs_to &&
71
+ !owner[through_reflection.foreign_key].nil?
72
+ end
73
+
74
+ def ensure_mutable
75
+ if source_reflection.macro != :belongs_to
76
+ raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
77
+ end
78
+ end
79
+
80
+ def ensure_not_nested
81
+ if reflection.nested?
82
+ raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end