activerecord 3.2.19 → 5.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 (264) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1715 -604
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +40 -45
  5. data/examples/performance.rb +33 -22
  6. data/examples/simple.rb +3 -4
  7. data/lib/active_record/aggregations.rb +76 -51
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +54 -40
  10. data/lib/active_record/associations/association.rb +76 -56
  11. data/lib/active_record/associations/association_scope.rb +125 -93
  12. data/lib/active_record/associations/belongs_to_association.rb +57 -28
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +120 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +115 -62
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -53
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
  18. data/lib/active_record/associations/builder/has_many.rb +9 -65
  19. data/lib/active_record/associations/builder/has_one.rb +18 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +18 -19
  21. data/lib/active_record/associations/collection_association.rb +268 -186
  22. data/lib/active_record/associations/collection_proxy.rb +1003 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +81 -41
  25. data/lib/active_record/associations/has_many_through_association.rb +76 -55
  26. data/lib/active_record/associations/has_one_association.rb +51 -21
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +239 -155
  32. data/lib/active_record/associations/preloader/association.rb +97 -62
  33. data/lib/active_record/associations/preloader/collection_association.rb +2 -8
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +75 -33
  38. data/lib/active_record/associations/preloader.rb +111 -79
  39. data/lib/active_record/associations/singular_association.rb +35 -13
  40. data/lib/active_record/associations/through_association.rb +41 -19
  41. data/lib/active_record/associations.rb +727 -501
  42. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  43. data/lib/active_record/attribute.rb +213 -0
  44. data/lib/active_record/attribute_assignment.rb +32 -162
  45. data/lib/active_record/attribute_decorators.rb +67 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  47. data/lib/active_record/attribute_methods/dirty.rb +101 -61
  48. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  49. data/lib/active_record/attribute_methods/query.rb +7 -6
  50. data/lib/active_record/attribute_methods/read.rb +56 -117
  51. data/lib/active_record/attribute_methods/serialization.rb +43 -96
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
  53. data/lib/active_record/attribute_methods/write.rb +34 -45
  54. data/lib/active_record/attribute_methods.rb +333 -144
  55. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  56. data/lib/active_record/attribute_set/builder.rb +108 -0
  57. data/lib/active_record/attribute_set.rb +108 -0
  58. data/lib/active_record/attributes.rb +265 -0
  59. data/lib/active_record/autosave_association.rb +285 -223
  60. data/lib/active_record/base.rb +95 -490
  61. data/lib/active_record/callbacks.rb +95 -61
  62. data/lib/active_record/coders/json.rb +13 -0
  63. data/lib/active_record/coders/yaml_column.rb +28 -19
  64. data/lib/active_record/collection_cache_key.rb +40 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
  78. data/lib/active_record/connection_adapters/column.rb +30 -259
  79. data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
  80. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  81. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  82. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  83. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  84. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  89. data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
  90. data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
  91. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
  92. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
  115. data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
  116. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
  117. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
  118. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  119. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
  120. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  121. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  122. data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
  123. data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
  124. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  125. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
  129. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  130. data/lib/active_record/connection_handling.rb +155 -0
  131. data/lib/active_record/core.rb +561 -0
  132. data/lib/active_record/counter_cache.rb +146 -105
  133. data/lib/active_record/dynamic_matchers.rb +101 -64
  134. data/lib/active_record/enum.rb +234 -0
  135. data/lib/active_record/errors.rb +153 -56
  136. data/lib/active_record/explain.rb +15 -63
  137. data/lib/active_record/explain_registry.rb +30 -0
  138. data/lib/active_record/explain_subscriber.rb +10 -6
  139. data/lib/active_record/fixture_set/file.rb +77 -0
  140. data/lib/active_record/fixtures.rb +355 -232
  141. data/lib/active_record/gem_version.rb +15 -0
  142. data/lib/active_record/inheritance.rb +144 -79
  143. data/lib/active_record/integration.rb +66 -13
  144. data/lib/active_record/internal_metadata.rb +56 -0
  145. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  146. data/lib/active_record/locale/en.yml +9 -1
  147. data/lib/active_record/locking/optimistic.rb +77 -56
  148. data/lib/active_record/locking/pessimistic.rb +6 -6
  149. data/lib/active_record/log_subscriber.rb +53 -28
  150. data/lib/active_record/migration/command_recorder.rb +166 -33
  151. data/lib/active_record/migration/compatibility.rb +126 -0
  152. data/lib/active_record/migration/join_table.rb +15 -0
  153. data/lib/active_record/migration.rb +792 -264
  154. data/lib/active_record/model_schema.rb +192 -130
  155. data/lib/active_record/nested_attributes.rb +238 -145
  156. data/lib/active_record/no_touching.rb +52 -0
  157. data/lib/active_record/null_relation.rb +89 -0
  158. data/lib/active_record/persistence.rb +357 -157
  159. data/lib/active_record/query_cache.rb +22 -43
  160. data/lib/active_record/querying.rb +34 -23
  161. data/lib/active_record/railtie.rb +88 -48
  162. data/lib/active_record/railties/console_sandbox.rb +3 -4
  163. data/lib/active_record/railties/controller_runtime.rb +5 -4
  164. data/lib/active_record/railties/databases.rake +170 -422
  165. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  166. data/lib/active_record/readonly_attributes.rb +2 -5
  167. data/lib/active_record/reflection.rb +715 -189
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  169. data/lib/active_record/relation/batches.rb +203 -50
  170. data/lib/active_record/relation/calculations.rb +203 -194
  171. data/lib/active_record/relation/delegation.rb +103 -25
  172. data/lib/active_record/relation/finder_methods.rb +457 -261
  173. data/lib/active_record/relation/from_clause.rb +32 -0
  174. data/lib/active_record/relation/merger.rb +167 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  179. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  180. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  181. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  182. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  183. data/lib/active_record/relation/predicate_builder.rb +153 -48
  184. data/lib/active_record/relation/query_attribute.rb +19 -0
  185. data/lib/active_record/relation/query_methods.rb +1019 -194
  186. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  187. data/lib/active_record/relation/spawn_methods.rb +46 -150
  188. data/lib/active_record/relation/where_clause.rb +174 -0
  189. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  190. data/lib/active_record/relation.rb +450 -245
  191. data/lib/active_record/result.rb +104 -12
  192. data/lib/active_record/runtime_registry.rb +22 -0
  193. data/lib/active_record/sanitization.rb +120 -94
  194. data/lib/active_record/schema.rb +28 -18
  195. data/lib/active_record/schema_dumper.rb +141 -74
  196. data/lib/active_record/schema_migration.rb +50 -0
  197. data/lib/active_record/scoping/default.rb +64 -57
  198. data/lib/active_record/scoping/named.rb +93 -108
  199. data/lib/active_record/scoping.rb +73 -121
  200. data/lib/active_record/secure_token.rb +38 -0
  201. data/lib/active_record/serialization.rb +7 -5
  202. data/lib/active_record/statement_cache.rb +113 -0
  203. data/lib/active_record/store.rb +173 -15
  204. data/lib/active_record/suppressor.rb +58 -0
  205. data/lib/active_record/table_metadata.rb +68 -0
  206. data/lib/active_record/tasks/database_tasks.rb +313 -0
  207. data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
  208. data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
  209. data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
  210. data/lib/active_record/timestamp.rb +42 -24
  211. data/lib/active_record/touch_later.rb +58 -0
  212. data/lib/active_record/transactions.rb +233 -105
  213. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  214. data/lib/active_record/type/date.rb +7 -0
  215. data/lib/active_record/type/date_time.rb +7 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  217. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  218. data/lib/active_record/type/internal/timezone.rb +15 -0
  219. data/lib/active_record/type/serialized.rb +63 -0
  220. data/lib/active_record/type/time.rb +20 -0
  221. data/lib/active_record/type/type_map.rb +64 -0
  222. data/lib/active_record/type.rb +72 -0
  223. data/lib/active_record/type_caster/connection.rb +29 -0
  224. data/lib/active_record/type_caster/map.rb +19 -0
  225. data/lib/active_record/type_caster.rb +7 -0
  226. data/lib/active_record/validations/absence.rb +23 -0
  227. data/lib/active_record/validations/associated.rb +33 -18
  228. data/lib/active_record/validations/length.rb +24 -0
  229. data/lib/active_record/validations/presence.rb +66 -0
  230. data/lib/active_record/validations/uniqueness.rb +128 -68
  231. data/lib/active_record/validations.rb +48 -40
  232. data/lib/active_record/version.rb +5 -7
  233. data/lib/active_record.rb +71 -47
  234. data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
  235. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
  236. data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
  237. data/lib/rails/generators/active_record/migration.rb +18 -8
  238. data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
  239. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  240. data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
  241. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  242. data/lib/rails/generators/active_record.rb +3 -11
  243. metadata +188 -134
  244. data/examples/associations.png +0 -0
  245. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  246. data/lib/active_record/associations/join_helper.rb +0 -55
  247. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  248. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  249. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  250. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  251. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  252. data/lib/active_record/dynamic_finder_match.rb +0 -68
  253. data/lib/active_record/dynamic_scope_match.rb +0 -23
  254. data/lib/active_record/fixtures/file.rb +0 -65
  255. data/lib/active_record/identity_map.rb +0 -162
  256. data/lib/active_record/observer.rb +0 -121
  257. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  258. data/lib/active_record/session_store.rb +0 -360
  259. data/lib/active_record/test_case.rb +0 -73
  260. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  261. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  262. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  263. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  264. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,5 +1,5 @@
1
- require 'active_support/core_ext/class/attribute'
2
- require 'active_support/core_ext/object/inclusion'
1
+ require 'thread'
2
+ require 'active_support/core_ext/string/filters'
3
3
 
4
4
  module ActiveRecord
5
5
  # = Active Record Reflection
@@ -7,35 +7,51 @@ module ActiveRecord
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
10
- class_attribute :reflections
11
- self.reflections = {}
10
+ class_attribute :_reflections, instance_writer: false
11
+ class_attribute :aggregate_reflections, instance_writer: false
12
+ self._reflections = {}
13
+ self.aggregate_reflections = {}
12
14
  end
13
15
 
14
- # Reflection enables to interrogate Active Record classes and objects
15
- # about their associations and aggregations. This information can,
16
- # for example, be used in a form builder that takes an Active Record object
16
+ def self.create(macro, name, scope, options, ar)
17
+ klass = case macro
18
+ when :composed_of
19
+ AggregateReflection
20
+ when :has_many
21
+ HasManyReflection
22
+ when :has_one
23
+ HasOneReflection
24
+ when :belongs_to
25
+ BelongsToReflection
26
+ else
27
+ raise "Unsupported Macro: #{macro}"
28
+ end
29
+
30
+ reflection = klass.new(name, scope, options, ar)
31
+ options[:through] ? ThroughReflection.new(reflection) : reflection
32
+ end
33
+
34
+ def self.add_reflection(ar, name, reflection)
35
+ ar.clear_reflections_cache
36
+ ar._reflections = ar._reflections.merge(name.to_s => reflection)
37
+ end
38
+
39
+ def self.add_aggregate_reflection(ar, name, reflection)
40
+ ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
41
+ end
42
+
43
+ # \Reflection enables the ability to examine the associations and aggregations of
44
+ # Active Record classes and objects. This information, for example,
45
+ # can be used in a form builder that takes an Active Record object
17
46
  # and creates input fields for all of the attributes depending on their type
18
47
  # and displays the associations to other objects.
19
48
  #
20
49
  # MacroReflection class has info for AggregateReflection and AssociationReflection
21
50
  # classes.
22
51
  module ClassMethods
23
- def create_reflection(macro, name, options, active_record)
24
- case macro
25
- when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
26
- klass = options[:through] ? ThroughReflection : AssociationReflection
27
- reflection = klass.new(macro, name, options, active_record)
28
- when :composed_of
29
- reflection = AggregateReflection.new(macro, name, options, active_record)
30
- end
31
-
32
- self.reflections = self.reflections.merge(name => reflection)
33
- reflection
34
- end
35
-
36
52
  # Returns an array of AggregateReflection objects for all the aggregations in the class.
37
53
  def reflect_on_all_aggregations
38
- reflections.values.grep(AggregateReflection)
54
+ aggregate_reflections.values
39
55
  end
40
56
 
41
57
  # Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
@@ -43,7 +59,30 @@ module ActiveRecord
43
59
  # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
44
60
  #
45
61
  def reflect_on_aggregation(aggregation)
46
- reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
62
+ aggregate_reflections[aggregation.to_s]
63
+ end
64
+
65
+ # Returns a Hash of name of the reflection as the key and an AssociationReflection as the value.
66
+ #
67
+ # Account.reflections # => {"balance" => AggregateReflection}
68
+ #
69
+ def reflections
70
+ @__reflections ||= begin
71
+ ref = {}
72
+
73
+ _reflections.each do |name, reflection|
74
+ parent_reflection = reflection.parent_reflection
75
+
76
+ if parent_reflection
77
+ parent_name = parent_reflection.name
78
+ ref[parent_name.to_s] = parent_reflection
79
+ else
80
+ ref[name] = reflection
81
+ end
82
+ end
83
+
84
+ ref
85
+ end
47
86
  end
48
87
 
49
88
  # Returns an array of AssociationReflection objects for all the
@@ -57,8 +96,9 @@ module ActiveRecord
57
96
  # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
58
97
  #
59
98
  def reflect_on_all_associations(macro = nil)
60
- association_reflections = reflections.values.grep(AssociationReflection)
61
- macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
99
+ association_reflections = reflections.values
100
+ association_reflections.select! { |reflection| reflection.macro == macro } if macro
101
+ association_reflections
62
102
  end
63
103
 
64
104
  # Returns the AssociationReflection object for the +association+ (use the symbol).
@@ -67,64 +107,198 @@ module ActiveRecord
67
107
  # Invoice.reflect_on_association(:line_items).macro # returns :has_many
68
108
  #
69
109
  def reflect_on_association(association)
70
- reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
110
+ reflections[association.to_s]
111
+ end
112
+
113
+ def _reflect_on_association(association) #:nodoc:
114
+ _reflections[association.to_s]
71
115
  end
72
116
 
73
117
  # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
74
118
  def reflect_on_all_autosave_associations
75
119
  reflections.values.select { |reflection| reflection.options[:autosave] }
76
120
  end
121
+
122
+ def clear_reflections_cache # :nodoc:
123
+ @__reflections = nil
124
+ end
77
125
  end
78
126
 
127
+ # Holds all the methods that are shared between MacroReflection and ThroughReflection.
128
+ #
129
+ # AbstractReflection
130
+ # MacroReflection
131
+ # AggregateReflection
132
+ # AssociationReflection
133
+ # HasManyReflection
134
+ # HasOneReflection
135
+ # BelongsToReflection
136
+ # HasAndBelongsToManyReflection
137
+ # ThroughReflection
138
+ # PolymorphicReflection
139
+ # RuntimeReflection
140
+ class AbstractReflection # :nodoc:
141
+ def through_reflection?
142
+ false
143
+ end
144
+
145
+ def table_name
146
+ klass.table_name
147
+ end
148
+
149
+ # Returns a new, unsaved instance of the associated class. +attributes+ will
150
+ # be passed to the class's constructor.
151
+ def build_association(attributes, &block)
152
+ klass.new(attributes, &block)
153
+ end
154
+
155
+ def quoted_table_name
156
+ klass.quoted_table_name
157
+ end
158
+
159
+ def primary_key_type
160
+ klass.type_for_attribute(klass.primary_key)
161
+ end
79
162
 
80
- # Abstract base class for AggregateReflection and AssociationReflection. Objects of
163
+ # Returns the class name for the macro.
164
+ #
165
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
166
+ # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
167
+ def class_name
168
+ @class_name ||= (options[:class_name] || derive_class_name).to_s
169
+ end
170
+
171
+ JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
172
+
173
+ def join_keys(association_klass)
174
+ JoinKeys.new(foreign_key, active_record_primary_key)
175
+ end
176
+
177
+ def constraints
178
+ scope_chain.flatten
179
+ end
180
+
181
+ def counter_cache_column
182
+ if belongs_to?
183
+ if options[:counter_cache] == true
184
+ "#{active_record.name.demodulize.underscore.pluralize}_count"
185
+ elsif options[:counter_cache]
186
+ options[:counter_cache].to_s
187
+ end
188
+ else
189
+ options[:counter_cache] ? options[:counter_cache].to_s : "#{name}_count"
190
+ end
191
+ end
192
+
193
+ def inverse_of
194
+ return unless inverse_name
195
+
196
+ @inverse_of ||= klass._reflect_on_association inverse_name
197
+ end
198
+
199
+ def check_validity_of_inverse!
200
+ unless polymorphic?
201
+ if has_inverse? && inverse_of.nil?
202
+ raise InverseOfAssociationNotFoundError.new(self)
203
+ end
204
+ end
205
+ end
206
+
207
+ # This shit is nasty. We need to avoid the following situation:
208
+ #
209
+ # * An associated record is deleted via record.destroy
210
+ # * Hence the callbacks run, and they find a belongs_to on the record with a
211
+ # :counter_cache options which points back at our owner. So they update the
212
+ # counter cache.
213
+ # * In which case, we must make sure to *not* update the counter cache, or else
214
+ # it will be decremented twice.
215
+ #
216
+ # Hence this method.
217
+ def inverse_which_updates_counter_cache
218
+ return @inverse_which_updates_counter_cache if defined?(@inverse_which_updates_counter_cache)
219
+ @inverse_which_updates_counter_cache = klass.reflect_on_all_associations(:belongs_to).find do |inverse|
220
+ inverse.counter_cache_column == counter_cache_column
221
+ end
222
+ end
223
+ alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
224
+
225
+ def inverse_updates_counter_in_memory?
226
+ inverse_of && inverse_which_updates_counter_cache == inverse_of
227
+ end
228
+
229
+ # Returns whether a counter cache should be used for this association.
230
+ #
231
+ # The counter_cache option must be given on either the owner or inverse
232
+ # association, and the column must be present on the owner.
233
+ def has_cached_counter?
234
+ options[:counter_cache] ||
235
+ inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache] &&
236
+ !!active_record.columns_hash[counter_cache_column]
237
+ end
238
+
239
+ def counter_must_be_updated_by_has_many?
240
+ !inverse_updates_counter_in_memory? && has_cached_counter?
241
+ end
242
+
243
+ def alias_candidate(name)
244
+ "#{plural_name}_#{name}"
245
+ end
246
+
247
+ def chain
248
+ collect_join_chain
249
+ end
250
+ end
251
+
252
+ # Base class for AggregateReflection and AssociationReflection. Objects of
81
253
  # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
82
- class MacroReflection
254
+ class MacroReflection < AbstractReflection
83
255
  # Returns the name of the macro.
84
256
  #
85
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
257
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
86
258
  # <tt>has_many :clients</tt> returns <tt>:clients</tt>
87
259
  attr_reader :name
88
260
 
89
- # Returns the macro type.
90
- #
91
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
92
- # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
93
- attr_reader :macro
261
+ attr_reader :scope
94
262
 
95
263
  # Returns the hash of options used for the macro.
96
264
  #
97
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
98
- # <tt>has_many :clients</tt> returns +{}+
265
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
266
+ # <tt>has_many :clients</tt> returns <tt>{}</tt>
99
267
  attr_reader :options
100
268
 
101
269
  attr_reader :active_record
102
270
 
103
271
  attr_reader :plural_name # :nodoc:
104
272
 
105
- def initialize(macro, name, options, active_record)
106
- @macro = macro
273
+ def initialize(name, scope, options, active_record)
107
274
  @name = name
275
+ @scope = scope
108
276
  @options = options
109
277
  @active_record = active_record
278
+ @klass = options[:anonymous_class]
110
279
  @plural_name = active_record.pluralize_table_names ?
111
280
  name.to_s.pluralize : name.to_s
112
281
  end
113
282
 
283
+ def autosave=(autosave)
284
+ @automatic_inverse_of = false
285
+ @options[:autosave] = autosave
286
+ parent_reflection = self.parent_reflection
287
+ if parent_reflection
288
+ parent_reflection.autosave = autosave
289
+ end
290
+ end
291
+
114
292
  # Returns the class for the macro.
115
293
  #
116
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
294
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
117
295
  # <tt>has_many :clients</tt> returns the Client class
118
296
  def klass
119
- @klass ||= class_name.constantize
297
+ @klass ||= compute_class(class_name)
120
298
  end
121
299
 
122
- # Returns the class name for the macro.
123
- #
124
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
125
- # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
126
- def class_name
127
- @class_name ||= (options[:class_name] || derive_class_name).to_s
300
+ def compute_class(name)
301
+ name.constantize
128
302
  end
129
303
 
130
304
  # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
@@ -133,14 +307,10 @@ module ActiveRecord
133
307
  super ||
134
308
  other_aggregation.kind_of?(self.class) &&
135
309
  name == other_aggregation.name &&
136
- other_aggregation.options &&
310
+ !other_aggregation.options.nil? &&
137
311
  active_record == other_aggregation.active_record
138
312
  end
139
313
 
140
- def sanitized_conditions #:nodoc:
141
- @sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
142
- end
143
-
144
314
  private
145
315
  def derive_class_name
146
316
  name.to_s.camelize
@@ -151,6 +321,10 @@ module ActiveRecord
151
321
  # Holds all the meta-data about an aggregation as it was specified in the
152
322
  # Active Record class.
153
323
  class AggregateReflection < MacroReflection #:nodoc:
324
+ def mapping
325
+ mapping = options[:mapping] || [name, name]
326
+ mapping.first.is_a?(Array) ? mapping : [mapping]
327
+ end
154
328
  end
155
329
 
156
330
  # Holds all the meta-data about an association as it was specified in the
@@ -169,42 +343,46 @@ module ActiveRecord
169
343
  # a new association object. Use +build_association+ or +create_association+
170
344
  # instead. This allows plugins to hook into association object creation.
171
345
  def klass
172
- @klass ||= active_record.send(:compute_type, class_name)
346
+ @klass ||= compute_class(class_name)
173
347
  end
174
348
 
175
- def initialize(macro, name, options, active_record)
176
- super
177
- @collection = macro.in?([:has_many, :has_and_belongs_to_many])
349
+ def compute_class(name)
350
+ active_record.send(:compute_type, name)
178
351
  end
179
352
 
180
- # Returns a new, unsaved instance of the associated class. +options+ will
181
- # be passed to the class's constructor.
182
- def build_association(*options, &block)
183
- klass.new(*options, &block)
184
- end
353
+ attr_reader :type, :foreign_type
354
+ attr_accessor :parent_reflection # Reflection
185
355
 
186
- def table_name
187
- @table_name ||= klass.table_name
188
- end
189
-
190
- def quoted_table_name
191
- @quoted_table_name ||= klass.quoted_table_name
356
+ def initialize(name, scope, options, active_record)
357
+ super
358
+ @automatic_inverse_of = nil
359
+ @type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
360
+ @foreign_type = options[:foreign_type] || "#{name}_type"
361
+ @constructable = calculate_constructable(macro, options)
362
+ @association_scope_cache = {}
363
+ @scope_lock = Mutex.new
192
364
  end
193
365
 
194
- def foreign_key
195
- @foreign_key ||= options[:foreign_key] || derive_foreign_key
366
+ def association_scope_cache(conn, owner)
367
+ key = conn.prepared_statements
368
+ if polymorphic?
369
+ key = [key, owner._read_attribute(@foreign_type)]
370
+ end
371
+ @association_scope_cache[key] ||= @scope_lock.synchronize {
372
+ @association_scope_cache[key] ||= yield
373
+ }
196
374
  end
197
375
 
198
- def foreign_type
199
- @foreign_type ||= options[:foreign_type] || "#{name}_type"
376
+ def constructable? # :nodoc:
377
+ @constructable
200
378
  end
201
379
 
202
- def type
203
- @type ||= options[:as] && "#{options[:as]}_type"
380
+ def join_table
381
+ @join_table ||= options[:join_table] || derive_join_table
204
382
  end
205
383
 
206
- def primary_key_column
207
- @primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
384
+ def foreign_key
385
+ @foreign_key ||= options[:foreign_key] || derive_foreign_key.freeze
208
386
  end
209
387
 
210
388
  def association_foreign_key
@@ -220,74 +398,68 @@ module ActiveRecord
220
398
  @active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
221
399
  end
222
400
 
223
- def counter_cache_column
224
- if options[:counter_cache] == true
225
- "#{active_record.name.demodulize.underscore.pluralize}_count"
226
- elsif options[:counter_cache]
227
- options[:counter_cache].to_s
228
- end
229
- end
230
-
231
- def columns(tbl_name, log_msg)
232
- @columns ||= klass.connection.columns(tbl_name, log_msg)
233
- end
234
-
235
- def reset_column_information
236
- @columns = nil
237
- end
238
-
239
401
  def check_validity!
240
402
  check_validity_of_inverse!
241
403
  end
242
404
 
243
- def check_validity_of_inverse!
244
- unless options[:polymorphic]
245
- if has_inverse? && inverse_of.nil?
246
- raise InverseOfAssociationNotFoundError.new(self)
247
- end
405
+ def check_preloadable!
406
+ return unless scope
407
+
408
+ if scope.arity > 0
409
+ raise ArgumentError, <<-MSG.squish
410
+ The association scope '#{name}' is instance dependent (the scope
411
+ block takes an argument). Preloading instance dependent scopes is
412
+ not supported.
413
+ MSG
248
414
  end
249
415
  end
416
+ alias :check_eager_loadable! :check_preloadable!
417
+
418
+ def join_id_for(owner) # :nodoc:
419
+ owner[active_record_primary_key]
420
+ end
250
421
 
251
422
  def through_reflection
252
423
  nil
253
424
  end
254
425
 
255
426
  def source_reflection
256
- nil
427
+ self
257
428
  end
258
429
 
259
430
  # A chain of reflections from this one back to the owner. For more see the explanation in
260
431
  # ThroughReflection.
261
- def chain
432
+ def collect_join_chain
262
433
  [self]
263
434
  end
264
435
 
436
+ # This is for clearing cache on the reflection. Useful for tests that need to compare
437
+ # SQL queries on associations.
438
+ def clear_association_scope_cache # :nodoc:
439
+ @association_scope_cache.clear
440
+ end
441
+
265
442
  def nested?
266
443
  false
267
444
  end
268
445
 
269
- # An array of arrays of conditions. Each item in the outside array corresponds to a reflection
270
- # in the #chain. The inside arrays are simply conditions (and each condition may itself be
271
- # a hash, array, arel predicate, etc...)
272
- def conditions
273
- [[options[:conditions]].compact]
446
+ # An array of arrays of scopes. Each item in the outside array corresponds to a reflection
447
+ # in the #chain.
448
+ def scope_chain
449
+ scope ? [[scope]] : [[]]
274
450
  end
275
451
 
276
- alias :source_macro :macro
277
-
278
- def has_inverse?
279
- @options[:inverse_of]
452
+ def has_scope?
453
+ scope
280
454
  end
281
455
 
282
- def inverse_of
283
- if has_inverse?
284
- @inverse_of ||= klass.reflect_on_association(options[:inverse_of])
285
- end
456
+ def has_inverse?
457
+ inverse_name
286
458
  end
287
459
 
288
460
  def polymorphic_inverse_of(associated_class)
289
461
  if has_inverse?
290
- if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of])
462
+ if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
291
463
  inverse_relationship
292
464
  else
293
465
  raise InverseOfAssociationNotFoundError.new(self, associated_class)
@@ -295,61 +467,138 @@ module ActiveRecord
295
467
  end
296
468
  end
297
469
 
470
+ # Returns the macro type.
471
+ #
472
+ # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
473
+ def macro; raise NotImplementedError; end
474
+
298
475
  # Returns whether or not this association reflection is for a collection
299
476
  # association. Returns +true+ if the +macro+ is either +has_many+ or
300
477
  # +has_and_belongs_to_many+, +false+ otherwise.
301
478
  def collection?
302
- @collection
479
+ false
303
480
  end
304
481
 
305
482
  # Returns whether or not the association should be validated as part of
306
483
  # the parent's validation.
307
484
  #
308
485
  # Unless you explicitly disable validation with
309
- # <tt>:validate => false</tt>, validation will take place when:
486
+ # <tt>validate: false</tt>, validation will take place when:
310
487
  #
311
- # * you explicitly enable validation; <tt>:validate => true</tt>
312
- # * you use autosave; <tt>:autosave => true</tt>
488
+ # * you explicitly enable validation; <tt>validate: true</tt>
489
+ # * you use autosave; <tt>autosave: true</tt>
313
490
  # * the association is a +has_many+ association
314
491
  def validate?
315
- !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
492
+ !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?)
316
493
  end
317
494
 
318
495
  # Returns +true+ if +self+ is a +belongs_to+ reflection.
319
- def belongs_to?
320
- macro == :belongs_to
496
+ def belongs_to?; false; end
497
+
498
+ # Returns +true+ if +self+ is a +has_one+ reflection.
499
+ def has_one?; false; end
500
+
501
+ def association_class; raise NotImplementedError; end
502
+
503
+ def polymorphic?
504
+ options[:polymorphic]
321
505
  end
322
506
 
323
- def association_class
324
- case macro
325
- when :belongs_to
326
- if options[:polymorphic]
327
- Associations::BelongsToPolymorphicAssociation
328
- else
329
- Associations::BelongsToAssociation
330
- end
331
- when :has_and_belongs_to_many
332
- Associations::HasAndBelongsToManyAssociation
333
- when :has_many
334
- if options[:through]
335
- Associations::HasManyThroughAssociation
336
- else
337
- Associations::HasManyAssociation
507
+ VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
508
+ INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
509
+
510
+ def add_as_source(seed)
511
+ seed
512
+ end
513
+
514
+ def add_as_polymorphic_through(reflection, seed)
515
+ seed + [PolymorphicReflection.new(self, reflection)]
516
+ end
517
+
518
+ def add_as_through(seed)
519
+ seed + [self]
520
+ end
521
+
522
+ protected
523
+
524
+ def actual_source_reflection # FIXME: this is a horrible name
525
+ self
526
+ end
527
+
528
+ private
529
+
530
+ def calculate_constructable(macro, options)
531
+ true
532
+ end
533
+
534
+ # Attempts to find the inverse association name automatically.
535
+ # If it cannot find a suitable inverse association name, it returns
536
+ # nil.
537
+ def inverse_name
538
+ options.fetch(:inverse_of) do
539
+ if @automatic_inverse_of == false
540
+ nil
541
+ else
542
+ @automatic_inverse_of ||= automatic_inverse_of
543
+ end
338
544
  end
339
- when :has_one
340
- if options[:through]
341
- Associations::HasOneThroughAssociation
342
- else
343
- Associations::HasOneAssociation
545
+ end
546
+
547
+ # returns either false or the inverse association name that it finds.
548
+ def automatic_inverse_of
549
+ if can_find_inverse_of_automatically?(self)
550
+ inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
551
+
552
+ begin
553
+ reflection = klass._reflect_on_association(inverse_name)
554
+ rescue NameError
555
+ # Give up: we couldn't compute the klass type so we won't be able
556
+ # to find any associations either.
557
+ reflection = false
558
+ end
559
+
560
+ if valid_inverse_reflection?(reflection)
561
+ return inverse_name
562
+ end
344
563
  end
564
+
565
+ false
566
+ end
567
+
568
+ # Checks if the inverse reflection that is returned from the
569
+ # +automatic_inverse_of+ method is a valid reflection. We must
570
+ # make sure that the reflection's active_record name matches up
571
+ # with the current reflection's klass name.
572
+ #
573
+ # Note: klass will always be valid because when there's a NameError
574
+ # from calling +klass+, +reflection+ will already be set to false.
575
+ def valid_inverse_reflection?(reflection)
576
+ reflection &&
577
+ klass.name == reflection.active_record.name &&
578
+ can_find_inverse_of_automatically?(reflection)
579
+ end
580
+
581
+ # Checks to see if the reflection doesn't have any options that prevent
582
+ # us from being able to guess the inverse automatically. First, the
583
+ # <tt>inverse_of</tt> option cannot be set to false. Second, we must
584
+ # have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
585
+ # Third, we must not have options such as <tt>:polymorphic</tt> or
586
+ # <tt>:foreign_key</tt> which prevent us from correctly guessing the
587
+ # inverse association.
588
+ #
589
+ # Anything with a scope can additionally ruin our attempt at finding an
590
+ # inverse, so we exclude reflections with scopes.
591
+ def can_find_inverse_of_automatically?(reflection)
592
+ reflection.options[:inverse_of] != false &&
593
+ VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
594
+ !INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
595
+ !reflection.scope
345
596
  end
346
- end
347
597
 
348
- private
349
598
  def derive_class_name
350
- class_name = name.to_s.camelize
599
+ class_name = name.to_s
351
600
  class_name = class_name.singularize if collection?
352
- class_name
601
+ class_name.camelize
353
602
  end
354
603
 
355
604
  def derive_foreign_key
@@ -362,27 +611,130 @@ module ActiveRecord
362
611
  end
363
612
  end
364
613
 
614
+ def derive_join_table
615
+ ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
616
+ end
617
+
365
618
  def primary_key(klass)
366
619
  klass.primary_key || raise(UnknownPrimaryKey.new(klass))
367
620
  end
368
621
  end
369
622
 
623
+ class HasManyReflection < AssociationReflection # :nodoc:
624
+ def macro; :has_many; end
625
+
626
+ def collection?; true; end
627
+
628
+ def association_class
629
+ if options[:through]
630
+ Associations::HasManyThroughAssociation
631
+ else
632
+ Associations::HasManyAssociation
633
+ end
634
+ end
635
+ end
636
+
637
+ class HasOneReflection < AssociationReflection # :nodoc:
638
+ def macro; :has_one; end
639
+
640
+ def has_one?; true; end
641
+
642
+ def association_class
643
+ if options[:through]
644
+ Associations::HasOneThroughAssociation
645
+ else
646
+ Associations::HasOneAssociation
647
+ end
648
+ end
649
+
650
+ private
651
+
652
+ def calculate_constructable(macro, options)
653
+ !options[:through]
654
+ end
655
+ end
656
+
657
+ class BelongsToReflection < AssociationReflection # :nodoc:
658
+ def macro; :belongs_to; end
659
+
660
+ def belongs_to?; true; end
661
+
662
+ def association_class
663
+ if polymorphic?
664
+ Associations::BelongsToPolymorphicAssociation
665
+ else
666
+ Associations::BelongsToAssociation
667
+ end
668
+ end
669
+
670
+ def join_keys(association_klass)
671
+ key = polymorphic? ? association_primary_key(association_klass) : association_primary_key
672
+ JoinKeys.new(key, foreign_key)
673
+ end
674
+
675
+ def join_id_for(owner) # :nodoc:
676
+ owner[foreign_key]
677
+ end
678
+
679
+ private
680
+
681
+ def calculate_constructable(macro, options)
682
+ !polymorphic?
683
+ end
684
+ end
685
+
686
+ class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
687
+ def initialize(name, scope, options, active_record)
688
+ super
689
+ end
690
+
691
+ def macro; :has_and_belongs_to_many; end
692
+
693
+ def collection?
694
+ true
695
+ end
696
+ end
697
+
370
698
  # Holds all the meta-data about a :through association as it was specified
371
699
  # in the Active Record class.
372
- class ThroughReflection < AssociationReflection #:nodoc:
700
+ class ThroughReflection < AbstractReflection #:nodoc:
701
+ attr_reader :delegate_reflection
373
702
  delegate :foreign_key, :foreign_type, :association_foreign_key,
374
703
  :active_record_primary_key, :type, :to => :source_reflection
375
704
 
376
- # Gets the source of the through reflection. It checks both a singularized
705
+ def initialize(delegate_reflection)
706
+ @delegate_reflection = delegate_reflection
707
+ @klass = delegate_reflection.options[:anonymous_class]
708
+ @source_reflection_name = delegate_reflection.options[:source]
709
+ end
710
+
711
+ def through_reflection?
712
+ true
713
+ end
714
+
715
+ def klass
716
+ @klass ||= delegate_reflection.compute_class(class_name)
717
+ end
718
+
719
+ # Returns the source of the through reflection. It checks both a singularized
377
720
  # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
378
721
  #
379
722
  # class Post < ActiveRecord::Base
380
723
  # has_many :taggings
381
- # has_many :tags, :through => :taggings
724
+ # has_many :tags, through: :taggings
725
+ # end
726
+ #
727
+ # class Tagging < ActiveRecord::Base
728
+ # belongs_to :post
729
+ # belongs_to :tag
382
730
  # end
383
731
  #
732
+ # tags_reflection = Post.reflect_on_association(:tags)
733
+ # tags_reflection.source_reflection
734
+ # # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
735
+ #
384
736
  def source_reflection
385
- @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
737
+ through_reflection.klass._reflect_on_association(source_reflection_name)
386
738
  end
387
739
 
388
740
  # Returns the AssociationReflection object specified in the <tt>:through</tt> option
@@ -390,14 +742,15 @@ module ActiveRecord
390
742
  #
391
743
  # class Post < ActiveRecord::Base
392
744
  # has_many :taggings
393
- # has_many :tags, :through => :taggings
745
+ # has_many :tags, through: :taggings
394
746
  # end
395
747
  #
396
748
  # tags_reflection = Post.reflect_on_association(:tags)
397
- # taggings_reflection = tags_reflection.through_reflection
749
+ # tags_reflection.through_reflection
750
+ # # => <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">
398
751
  #
399
752
  def through_reflection
400
- @through_reflection ||= active_record.reflect_on_association(options[:through])
753
+ active_record._reflect_on_association(options[:through])
401
754
  end
402
755
 
403
756
  # Returns an array of reflections which are involved in this association. Each item in the
@@ -406,64 +759,83 @@ module ActiveRecord
406
759
  # The chain is built by recursively calling #chain on the source reflection and the through
407
760
  # reflection. The base case for the recursion is a normal association, which just returns
408
761
  # [self] as its #chain.
409
- def chain
410
- @chain ||= begin
411
- chain = source_reflection.chain + through_reflection.chain
412
- chain[0] = self # Use self so we don't lose the information from :source_type
413
- chain
414
- end
762
+ #
763
+ # class Post < ActiveRecord::Base
764
+ # has_many :taggings
765
+ # has_many :tags, through: :taggings
766
+ # end
767
+ #
768
+ # tags_reflection = Post.reflect_on_association(:tags)
769
+ # tags_reflection.chain
770
+ # # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
771
+ # <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
772
+ #
773
+ def collect_join_chain
774
+ collect_join_reflections [self]
775
+ end
776
+
777
+ # This is for clearing cache on the reflection. Useful for tests that need to compare
778
+ # SQL queries on associations.
779
+ def clear_association_scope_cache # :nodoc:
780
+ delegate_reflection.clear_association_scope_cache
781
+ source_reflection.clear_association_scope_cache
782
+ through_reflection.clear_association_scope_cache
415
783
  end
416
784
 
417
785
  # Consider the following example:
418
786
  #
419
787
  # class Person
420
788
  # has_many :articles
421
- # has_many :comment_tags, :through => :articles
789
+ # has_many :comment_tags, through: :articles
422
790
  # end
423
791
  #
424
792
  # class Article
425
793
  # has_many :comments
426
- # has_many :comment_tags, :through => :comments, :source => :tags
794
+ # has_many :comment_tags, through: :comments, source: :tags
427
795
  # end
428
796
  #
429
797
  # class Comment
430
798
  # has_many :tags
431
799
  # end
432
800
  #
433
- # There may be conditions on Person.comment_tags, Article.comment_tags and/or Comment.tags,
801
+ # There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
434
802
  # but only Comment.tags will be represented in the #chain. So this method creates an array
435
- # of conditions corresponding to the chain. Each item in the #conditions array corresponds
436
- # to an item in the #chain, and is itself an array of conditions from an arbitrary number
437
- # of relevant reflections, plus any :source_type or polymorphic :as constraints.
438
- def conditions
439
- @conditions ||= begin
440
- conditions = source_reflection.conditions.map { |c| c.dup }
803
+ # of scopes corresponding to the chain.
804
+ def scope_chain
805
+ @scope_chain ||= begin
806
+ scope_chain = source_reflection.scope_chain.map(&:dup)
441
807
 
442
- # Add to it the conditions from this reflection if necessary.
443
- conditions.first << options[:conditions] if options[:conditions]
808
+ # Add to it the scope from this reflection (if any)
809
+ scope_chain.first << scope if scope
444
810
 
445
- through_conditions = through_reflection.conditions
811
+ through_scope_chain = through_reflection.scope_chain.map(&:dup)
446
812
 
447
813
  if options[:source_type]
448
- through_conditions.first << { foreign_type => options[:source_type] }
814
+ type = foreign_type
815
+ source_type = options[:source_type]
816
+ through_scope_chain.first << lambda { |object|
817
+ where(type => source_type)
818
+ }
449
819
  end
450
820
 
451
821
  # Recursively fill out the rest of the array from the through reflection
452
- conditions += through_conditions
453
-
454
- # And return
455
- conditions
822
+ scope_chain + through_scope_chain
456
823
  end
457
824
  end
458
825
 
459
- # The macro used by the source association
460
- def source_macro
461
- source_reflection.source_macro
826
+ def has_scope?
827
+ scope || options[:source_type] ||
828
+ source_reflection.has_scope? ||
829
+ through_reflection.has_scope?
830
+ end
831
+
832
+ def join_keys(association_klass)
833
+ source_reflection.join_keys(association_klass)
462
834
  end
463
835
 
464
836
  # A through association is nested if there would be more than one join table
465
837
  def nested?
466
- chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
838
+ source_reflection.through_reflection? || through_reflection.through_reflection?
467
839
  end
468
840
 
469
841
  # We want to use the klass from this reflection, rather than just delegate straight to
@@ -472,20 +844,45 @@ module ActiveRecord
472
844
  def association_primary_key(klass = nil)
473
845
  # Get the "actual" source reflection if the immediate source reflection has a
474
846
  # source reflection itself
475
- source_reflection = self.source_reflection
476
- while source_reflection.source_reflection
477
- source_reflection = source_reflection.source_reflection
478
- end
479
-
480
- source_reflection.options[:primary_key] || primary_key(klass || self.klass)
847
+ actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
481
848
  end
482
849
 
483
- # Gets an array of possible <tt>:through</tt> source reflection names:
850
+ # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
851
+ #
852
+ # class Post < ActiveRecord::Base
853
+ # has_many :taggings
854
+ # has_many :tags, through: :taggings
855
+ # end
484
856
  #
485
- # [:singularized, :pluralized]
857
+ # tags_reflection = Post.reflect_on_association(:tags)
858
+ # tags_reflection.source_reflection_names
859
+ # # => [:tag, :tags]
486
860
  #
487
861
  def source_reflection_names
488
- @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
862
+ options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq
863
+ end
864
+
865
+ def source_reflection_name # :nodoc:
866
+ return @source_reflection_name if @source_reflection_name
867
+
868
+ names = [name.to_s.singularize, name].collect(&:to_sym).uniq
869
+ names = names.find_all { |n|
870
+ through_reflection.klass._reflect_on_association(n)
871
+ }
872
+
873
+ if names.length > 1
874
+ example_options = options.dup
875
+ example_options[:source] = source_reflection_names.first
876
+ ActiveSupport::Deprecation.warn \
877
+ "Ambiguous source reflection for through association. Please " \
878
+ "specify a :source directive on your declaration like:\n" \
879
+ "\n" \
880
+ " class #{active_record.name} < ActiveRecord::Base\n" \
881
+ " #{macro} :#{name}, #{example_options}\n" \
882
+ " end"
883
+ end
884
+
885
+ @source_reflection_name = names.first
489
886
  end
490
887
 
491
888
  def source_options
@@ -496,39 +893,168 @@ module ActiveRecord
496
893
  through_reflection.options
497
894
  end
498
895
 
896
+ def join_id_for(owner) # :nodoc:
897
+ source_reflection.join_id_for(owner)
898
+ end
899
+
499
900
  def check_validity!
500
901
  if through_reflection.nil?
501
902
  raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
502
903
  end
503
904
 
504
- if through_reflection.options[:polymorphic]
505
- raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
905
+ if through_reflection.polymorphic?
906
+ if has_one?
907
+ raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self)
908
+ else
909
+ raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
910
+ end
506
911
  end
507
912
 
508
913
  if source_reflection.nil?
509
914
  raise HasManyThroughSourceAssociationNotFoundError.new(self)
510
915
  end
511
916
 
512
- if options[:source_type] && source_reflection.options[:polymorphic].nil?
917
+ if options[:source_type] && !source_reflection.polymorphic?
513
918
  raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
514
919
  end
515
920
 
516
- if source_reflection.options[:polymorphic] && options[:source_type].nil?
921
+ if source_reflection.polymorphic? && options[:source_type].nil?
517
922
  raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
518
923
  end
519
924
 
520
- if macro == :has_one && through_reflection.collection?
925
+ if has_one? && through_reflection.collection?
521
926
  raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
522
927
  end
523
928
 
524
929
  check_validity_of_inverse!
525
930
  end
526
931
 
932
+ def constraints
933
+ scope_chain = source_reflection.constraints
934
+ scope_chain << scope if scope
935
+ scope_chain
936
+ end
937
+
938
+ def add_as_source(seed)
939
+ collect_join_reflections seed
940
+ end
941
+
942
+ def add_as_polymorphic_through(reflection, seed)
943
+ collect_join_reflections(seed + [PolymorphicReflection.new(self, reflection)])
944
+ end
945
+
946
+ def add_as_through(seed)
947
+ collect_join_reflections(seed + [self])
948
+ end
949
+
950
+ def collect_join_reflections(seed)
951
+ a = source_reflection.add_as_source seed
952
+ if options[:source_type]
953
+ through_reflection.add_as_polymorphic_through self, a
954
+ else
955
+ through_reflection.add_as_through a
956
+ end
957
+ end
958
+
959
+ protected
960
+
961
+ def actual_source_reflection # FIXME: this is a horrible name
962
+ source_reflection.send(:actual_source_reflection)
963
+ end
964
+
965
+ def primary_key(klass)
966
+ klass.primary_key || raise(UnknownPrimaryKey.new(klass))
967
+ end
968
+
969
+ def inverse_name; delegate_reflection.send(:inverse_name); end
970
+
527
971
  private
528
972
  def derive_class_name
529
973
  # get the class_name of the belongs_to association of the through reflection
530
974
  options[:source_type] || source_reflection.class_name
531
975
  end
976
+
977
+ delegate_methods = AssociationReflection.public_instance_methods -
978
+ public_instance_methods
979
+
980
+ delegate(*delegate_methods, to: :delegate_reflection)
981
+
982
+ end
983
+
984
+ class PolymorphicReflection < ThroughReflection # :nodoc:
985
+ def initialize(reflection, previous_reflection)
986
+ @reflection = reflection
987
+ @previous_reflection = previous_reflection
988
+ end
989
+
990
+ def klass
991
+ @reflection.klass
992
+ end
993
+
994
+ def scope
995
+ @reflection.scope
996
+ end
997
+
998
+ def table_name
999
+ @reflection.table_name
1000
+ end
1001
+
1002
+ def plural_name
1003
+ @reflection.plural_name
1004
+ end
1005
+
1006
+ def join_keys(association_klass)
1007
+ @reflection.join_keys(association_klass)
1008
+ end
1009
+
1010
+ def type
1011
+ @reflection.type
1012
+ end
1013
+
1014
+ def constraints
1015
+ @reflection.constraints + [source_type_info]
1016
+ end
1017
+
1018
+ def source_type_info
1019
+ type = @previous_reflection.foreign_type
1020
+ source_type = @previous_reflection.options[:source_type]
1021
+ lambda { |object| where(type => source_type) }
1022
+ end
1023
+ end
1024
+
1025
+ class RuntimeReflection < PolymorphicReflection # :nodoc:
1026
+ attr_accessor :next
1027
+
1028
+ def initialize(reflection, association)
1029
+ @reflection = reflection
1030
+ @association = association
1031
+ end
1032
+
1033
+ def klass
1034
+ @association.klass
1035
+ end
1036
+
1037
+ def table_name
1038
+ klass.table_name
1039
+ end
1040
+
1041
+ def constraints
1042
+ @reflection.constraints
1043
+ end
1044
+
1045
+ def source_type_info
1046
+ @reflection.source_type_info
1047
+ end
1048
+
1049
+ def alias_candidate(name)
1050
+ "#{plural_name}_#{name}_join"
1051
+ end
1052
+
1053
+ def alias_name
1054
+ Arel::Table.new(table_name)
1055
+ end
1056
+
1057
+ def all_includes; yield; end
532
1058
  end
533
1059
  end
534
1060
  end