activerecord 3.2.22.5 → 4.2.11.3

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 (236) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1632 -609
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +37 -41
  5. data/examples/performance.rb +31 -19
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +56 -42
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -36
  10. data/lib/active_record/associations/association.rb +73 -55
  11. data/lib/active_record/associations/association_scope.rb +143 -82
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +125 -31
  15. data/lib/active_record/associations/builder/belongs_to.rb +89 -61
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +23 -17
  21. data/lib/active_record/associations/collection_association.rb +251 -177
  22. data/lib/active_record/associations/collection_proxy.rb +963 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +113 -22
  25. data/lib/active_record/associations/has_many_through_association.rb +99 -39
  26. data/lib/active_record/associations/has_one_association.rb +43 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
  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 +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +62 -33
  38. data/lib/active_record/associations/preloader.rb +101 -79
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +30 -16
  41. data/lib/active_record/associations.rb +463 -345
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +142 -151
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +137 -57
  47. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +73 -106
  50. data/lib/active_record/attribute_methods/serialization.rb +44 -94
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
  52. data/lib/active_record/attribute_methods/write.rb +57 -44
  53. data/lib/active_record/attribute_methods.rb +301 -141
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +246 -217
  58. data/lib/active_record/base.rb +70 -474
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
  75. data/lib/active_record/connection_adapters/column.rb +31 -245
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
  114. data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +157 -105
  119. data/lib/active_record/dynamic_matchers.rb +119 -63
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +94 -36
  122. data/lib/active_record/explain.rb +15 -63
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +9 -5
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +302 -215
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +143 -70
  129. data/lib/active_record/integration.rb +65 -12
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +73 -52
  133. data/lib/active_record/locking/pessimistic.rb +5 -5
  134. data/lib/active_record/log_subscriber.rb +24 -21
  135. data/lib/active_record/migration/command_recorder.rb +124 -32
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +511 -213
  138. data/lib/active_record/model_schema.rb +91 -117
  139. data/lib/active_record/nested_attributes.rb +184 -130
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +276 -117
  143. data/lib/active_record/query_cache.rb +19 -37
  144. data/lib/active_record/querying.rb +28 -18
  145. data/lib/active_record/railtie.rb +73 -40
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +4 -3
  148. data/lib/active_record/railties/databases.rake +141 -416
  149. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  150. data/lib/active_record/readonly_attributes.rb +1 -4
  151. data/lib/active_record/reflection.rb +513 -154
  152. data/lib/active_record/relation/batches.rb +91 -43
  153. data/lib/active_record/relation/calculations.rb +199 -161
  154. data/lib/active_record/relation/delegation.rb +116 -25
  155. data/lib/active_record/relation/finder_methods.rb +362 -248
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -43
  160. data/lib/active_record/relation/query_methods.rb +928 -167
  161. data/lib/active_record/relation/spawn_methods.rb +48 -149
  162. data/lib/active_record/relation.rb +352 -207
  163. data/lib/active_record/result.rb +101 -10
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +56 -59
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +106 -63
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +50 -57
  170. data/lib/active_record/scoping/named.rb +73 -109
  171. data/lib/active_record/scoping.rb +58 -123
  172. data/lib/active_record/serialization.rb +6 -2
  173. data/lib/active_record/serializers/xml_serializer.rb +12 -22
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +168 -15
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +23 -16
  181. data/lib/active_record/transactions.rb +125 -79
  182. data/lib/active_record/type/big_integer.rb +13 -0
  183. data/lib/active_record/type/binary.rb +50 -0
  184. data/lib/active_record/type/boolean.rb +31 -0
  185. data/lib/active_record/type/date.rb +50 -0
  186. data/lib/active_record/type/date_time.rb +54 -0
  187. data/lib/active_record/type/decimal.rb +64 -0
  188. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  189. data/lib/active_record/type/decorator.rb +14 -0
  190. data/lib/active_record/type/float.rb +19 -0
  191. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  192. data/lib/active_record/type/integer.rb +59 -0
  193. data/lib/active_record/type/mutable.rb +16 -0
  194. data/lib/active_record/type/numeric.rb +36 -0
  195. data/lib/active_record/type/serialized.rb +62 -0
  196. data/lib/active_record/type/string.rb +40 -0
  197. data/lib/active_record/type/text.rb +11 -0
  198. data/lib/active_record/type/time.rb +26 -0
  199. data/lib/active_record/type/time_value.rb +38 -0
  200. data/lib/active_record/type/type_map.rb +64 -0
  201. data/lib/active_record/type/unsigned_integer.rb +15 -0
  202. data/lib/active_record/type/value.rb +110 -0
  203. data/lib/active_record/type.rb +23 -0
  204. data/lib/active_record/validations/associated.rb +24 -16
  205. data/lib/active_record/validations/presence.rb +67 -0
  206. data/lib/active_record/validations/uniqueness.rb +123 -64
  207. data/lib/active_record/validations.rb +36 -29
  208. data/lib/active_record/version.rb +5 -7
  209. data/lib/active_record.rb +66 -46
  210. data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
  211. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
  212. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  213. data/lib/rails/generators/active_record/migration.rb +11 -8
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
  215. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  216. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  217. data/lib/rails/generators/active_record.rb +3 -11
  218. metadata +101 -45
  219. data/examples/associations.png +0 -0
  220. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  221. data/lib/active_record/associations/join_helper.rb +0 -55
  222. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  223. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  226. data/lib/active_record/dynamic_finder_match.rb +0 -68
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/fixtures/file.rb +0 -65
  229. data/lib/active_record/identity_map.rb +0 -162
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -360
  232. data/lib/active_record/test_case.rb +0 -73
  233. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  234. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  235. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  236. 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,11 +7,40 @@ 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
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 interrogating of Active Record classes and objects
15
44
  # about their associations and aggregations. This information can,
16
45
  # for example, 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
@@ -20,22 +49,9 @@ module ActiveRecord
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 a AssociationReflection as the value.
66
+ #
67
+ # Account.reflections # => {"balance" => AggregateReflection}
68
+ #
69
+ # @api public
70
+ def reflections
71
+ @__reflections ||= begin
72
+ ref = {}
73
+
74
+ _reflections.each do |name, reflection|
75
+ parent_name, parent_reflection = reflection.parent_reflection
76
+
77
+ if parent_name
78
+ ref[parent_name] = 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
@@ -56,8 +95,9 @@ module ActiveRecord
56
95
  # Account.reflect_on_all_associations # returns an array of all associations
57
96
  # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
58
97
  #
98
+ # @api public
59
99
  def reflect_on_all_associations(macro = nil)
60
- association_reflections = reflections.values.grep(AssociationReflection)
100
+ association_reflections = reflections.values
61
101
  macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
62
102
  end
63
103
 
@@ -66,65 +106,144 @@ module ActiveRecord
66
106
  # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
67
107
  # Invoice.reflect_on_association(:line_items).macro # returns :has_many
68
108
  #
109
+ # @api public
69
110
  def reflect_on_association(association)
70
- reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
111
+ reflections[association.to_s]
112
+ end
113
+
114
+ # @api private
115
+ def _reflect_on_association(association) #:nodoc:
116
+ _reflections[association.to_s]
71
117
  end
72
118
 
73
119
  # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
120
+ #
121
+ # @api public
74
122
  def reflect_on_all_autosave_associations
75
123
  reflections.values.select { |reflection| reflection.options[:autosave] }
76
124
  end
125
+
126
+ def clear_reflections_cache #:nodoc:
127
+ @__reflections = nil
128
+ end
77
129
  end
78
130
 
131
+ # Holds all the methods that are shared between MacroReflection, AssociationReflection
132
+ # and ThroughReflection
133
+ class AbstractReflection # :nodoc:
134
+ def table_name
135
+ klass.table_name
136
+ end
137
+
138
+ # Returns a new, unsaved instance of the associated class. +attributes+ will
139
+ # be passed to the class's constructor.
140
+ def build_association(attributes, &block)
141
+ klass.new(attributes, &block)
142
+ end
143
+
144
+ def quoted_table_name
145
+ klass.quoted_table_name
146
+ end
147
+
148
+ def primary_key_type
149
+ klass.type_for_attribute(klass.primary_key)
150
+ end
151
+
152
+ # Returns the class name for the macro.
153
+ #
154
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
155
+ # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
156
+ def class_name
157
+ @class_name ||= (options[:class_name] || derive_class_name).to_s
158
+ end
159
+
160
+ JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
161
+
162
+ def join_keys(assoc_klass)
163
+ JoinKeys.new(foreign_key, active_record_primary_key)
164
+ end
79
165
 
80
- # Abstract base class for AggregateReflection and AssociationReflection. Objects of
166
+ def source_macro
167
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
168
+ ActiveRecord::Base.source_macro is deprecated and will be removed
169
+ without replacement.
170
+ MSG
171
+
172
+ macro
173
+ end
174
+
175
+ def inverse_of
176
+ return unless inverse_name
177
+
178
+ @inverse_of ||= klass._reflect_on_association inverse_name
179
+ end
180
+
181
+ def check_validity_of_inverse!
182
+ unless polymorphic?
183
+ if has_inverse? && inverse_of.nil?
184
+ raise InverseOfAssociationNotFoundError.new(self)
185
+ end
186
+ end
187
+ end
188
+ end
189
+ # Base class for AggregateReflection and AssociationReflection. Objects of
81
190
  # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
82
- class MacroReflection
191
+ #
192
+ # MacroReflection
193
+ # AssociationReflection
194
+ # AggregateReflection
195
+ # HasManyReflection
196
+ # HasOneReflection
197
+ # BelongsToReflection
198
+ # ThroughReflection
199
+ class MacroReflection < AbstractReflection
83
200
  # Returns the name of the macro.
84
201
  #
85
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
202
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
86
203
  # <tt>has_many :clients</tt> returns <tt>:clients</tt>
87
204
  attr_reader :name
88
205
 
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
206
+ attr_reader :scope
94
207
 
95
208
  # Returns the hash of options used for the macro.
96
209
  #
97
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
98
- # <tt>has_many :clients</tt> returns +{}+
210
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
211
+ # <tt>has_many :clients</tt> returns <tt>{}</tt>
99
212
  attr_reader :options
100
213
 
101
214
  attr_reader :active_record
102
215
 
103
216
  attr_reader :plural_name # :nodoc:
104
217
 
105
- def initialize(macro, name, options, active_record)
106
- @macro = macro
218
+ def initialize(name, scope, options, active_record)
107
219
  @name = name
220
+ @scope = scope
108
221
  @options = options
109
222
  @active_record = active_record
223
+ @klass = options[:anonymous_class]
110
224
  @plural_name = active_record.pluralize_table_names ?
111
225
  name.to_s.pluralize : name.to_s
112
226
  end
113
227
 
228
+ def autosave=(autosave)
229
+ @automatic_inverse_of = false
230
+ @options[:autosave] = autosave
231
+ _, parent_reflection = self.parent_reflection
232
+ if parent_reflection
233
+ parent_reflection.autosave = autosave
234
+ end
235
+ end
236
+
114
237
  # Returns the class for the macro.
115
238
  #
116
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
239
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
117
240
  # <tt>has_many :clients</tt> returns the Client class
118
241
  def klass
119
- @klass ||= class_name.constantize
242
+ @klass ||= compute_class(class_name)
120
243
  end
121
244
 
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
245
+ def compute_class(name)
246
+ name.constantize
128
247
  end
129
248
 
130
249
  # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
@@ -133,14 +252,10 @@ module ActiveRecord
133
252
  super ||
134
253
  other_aggregation.kind_of?(self.class) &&
135
254
  name == other_aggregation.name &&
136
- other_aggregation.options &&
255
+ !other_aggregation.options.nil? &&
137
256
  active_record == other_aggregation.active_record
138
257
  end
139
258
 
140
- def sanitized_conditions #:nodoc:
141
- @sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
142
- end
143
-
144
259
  private
145
260
  def derive_class_name
146
261
  name.to_s.camelize
@@ -151,6 +266,10 @@ module ActiveRecord
151
266
  # Holds all the meta-data about an aggregation as it was specified in the
152
267
  # Active Record class.
153
268
  class AggregateReflection < MacroReflection #:nodoc:
269
+ def mapping
270
+ mapping = options[:mapping] || [name, name]
271
+ mapping.first.is_a?(Array) ? mapping : [mapping]
272
+ end
154
273
  end
155
274
 
156
275
  # Holds all the meta-data about an association as it was specified in the
@@ -169,42 +288,46 @@ module ActiveRecord
169
288
  # a new association object. Use +build_association+ or +create_association+
170
289
  # instead. This allows plugins to hook into association object creation.
171
290
  def klass
172
- @klass ||= active_record.send(:compute_type, class_name)
291
+ @klass ||= compute_class(class_name)
173
292
  end
174
293
 
175
- def initialize(macro, name, options, active_record)
176
- super
177
- @collection = macro.in?([:has_many, :has_and_belongs_to_many])
294
+ def compute_class(name)
295
+ active_record.send(:compute_type, name)
178
296
  end
179
297
 
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
298
+ attr_reader :type, :foreign_type
299
+ attr_accessor :parent_reflection # [:name, Reflection]
185
300
 
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
192
- end
193
-
194
- def foreign_key
195
- @foreign_key ||= options[:foreign_key] || derive_foreign_key
301
+ def initialize(name, scope, options, active_record)
302
+ super
303
+ @automatic_inverse_of = nil
304
+ @type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
305
+ @foreign_type = options[:foreign_type] || "#{name}_type"
306
+ @constructable = calculate_constructable(macro, options)
307
+ @association_scope_cache = {}
308
+ @scope_lock = Mutex.new
309
+ end
310
+
311
+ def association_scope_cache(conn, owner)
312
+ key = conn.prepared_statements
313
+ if polymorphic?
314
+ key = [key, owner._read_attribute(@foreign_type)]
315
+ end
316
+ @association_scope_cache[key] ||= @scope_lock.synchronize {
317
+ @association_scope_cache[key] ||= yield
318
+ }
196
319
  end
197
320
 
198
- def foreign_type
199
- @foreign_type ||= options[:foreign_type] || "#{name}_type"
321
+ def constructable? # :nodoc:
322
+ @constructable
200
323
  end
201
324
 
202
- def type
203
- @type ||= options[:as] && "#{options[:as]}_type"
325
+ def join_table
326
+ @join_table ||= options[:join_table] || derive_join_table
204
327
  end
205
328
 
206
- def primary_key_column
207
- @primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
329
+ def foreign_key
330
+ @foreign_key ||= options[:foreign_key] || derive_foreign_key.freeze
208
331
  end
209
332
 
210
333
  def association_foreign_key
@@ -228,32 +351,36 @@ module ActiveRecord
228
351
  end
229
352
  end
230
353
 
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
354
  def check_validity!
240
355
  check_validity_of_inverse!
241
356
  end
242
357
 
243
- def check_validity_of_inverse!
244
- unless options[:polymorphic]
245
- if has_inverse? && inverse_of.nil?
246
- raise InverseOfAssociationNotFoundError.new(self)
247
- end
358
+ def check_preloadable!
359
+ return unless scope
360
+
361
+ if scope.arity > 0
362
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
363
+ The association scope '#{name}' is instance dependent (the scope
364
+ block takes an argument). Preloading happens before the individual
365
+ instances are created. This means that there is no instance being
366
+ passed to the association scope. This will most likely result in
367
+ broken or incorrect behavior. Joining, Preloading and eager loading
368
+ of these associations is deprecated and will be removed in the future.
369
+ MSG
248
370
  end
249
371
  end
372
+ alias :check_eager_loadable! :check_preloadable!
373
+
374
+ def join_id_for(owner) # :nodoc:
375
+ owner[active_record_primary_key]
376
+ end
250
377
 
251
378
  def through_reflection
252
379
  nil
253
380
  end
254
381
 
255
382
  def source_reflection
256
- nil
383
+ self
257
384
  end
258
385
 
259
386
  # A chain of reflections from this one back to the owner. For more see the explanation in
@@ -266,28 +393,19 @@ module ActiveRecord
266
393
  false
267
394
  end
268
395
 
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]
396
+ # An array of arrays of scopes. Each item in the outside array corresponds to a reflection
397
+ # in the #chain.
398
+ def scope_chain
399
+ scope ? [[scope]] : [[]]
274
400
  end
275
401
 
276
- alias :source_macro :macro
277
-
278
402
  def has_inverse?
279
- @options[:inverse_of]
280
- end
281
-
282
- def inverse_of
283
- if has_inverse?
284
- @inverse_of ||= klass.reflect_on_association(options[:inverse_of])
285
- end
403
+ inverse_name
286
404
  end
287
405
 
288
406
  def polymorphic_inverse_of(associated_class)
289
407
  if has_inverse?
290
- if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of])
408
+ if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
291
409
  inverse_relationship
292
410
  else
293
411
  raise InverseOfAssociationNotFoundError.new(self, associated_class)
@@ -295,41 +413,45 @@ module ActiveRecord
295
413
  end
296
414
  end
297
415
 
416
+ # Returns the macro type.
417
+ #
418
+ # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
419
+ def macro; raise NotImplementedError; end
420
+
298
421
  # Returns whether or not this association reflection is for a collection
299
422
  # association. Returns +true+ if the +macro+ is either +has_many+ or
300
423
  # +has_and_belongs_to_many+, +false+ otherwise.
301
424
  def collection?
302
- @collection
425
+ false
303
426
  end
304
427
 
305
428
  # Returns whether or not the association should be validated as part of
306
429
  # the parent's validation.
307
430
  #
308
431
  # Unless you explicitly disable validation with
309
- # <tt>:validate => false</tt>, validation will take place when:
432
+ # <tt>validate: false</tt>, validation will take place when:
310
433
  #
311
- # * you explicitly enable validation; <tt>:validate => true</tt>
312
- # * you use autosave; <tt>:autosave => true</tt>
434
+ # * you explicitly enable validation; <tt>validate: true</tt>
435
+ # * you use autosave; <tt>autosave: true</tt>
313
436
  # * the association is a +has_many+ association
314
437
  def validate?
315
- !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
438
+ !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?)
316
439
  end
317
440
 
318
441
  # Returns +true+ if +self+ is a +belongs_to+ reflection.
319
- def belongs_to?
320
- macro == :belongs_to
321
- end
442
+ def belongs_to?; false; end
443
+
444
+ # Returns +true+ if +self+ is a +has_one+ reflection.
445
+ def has_one?; false; end
322
446
 
323
447
  def association_class
324
448
  case macro
325
449
  when :belongs_to
326
- if options[:polymorphic]
450
+ if polymorphic?
327
451
  Associations::BelongsToPolymorphicAssociation
328
452
  else
329
453
  Associations::BelongsToAssociation
330
454
  end
331
- when :has_and_belongs_to_many
332
- Associations::HasAndBelongsToManyAssociation
333
455
  when :has_many
334
456
  if options[:through]
335
457
  Associations::HasManyThroughAssociation
@@ -345,11 +467,100 @@ module ActiveRecord
345
467
  end
346
468
  end
347
469
 
470
+ def polymorphic?
471
+ options[:polymorphic]
472
+ end
473
+
474
+ VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
475
+ INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
476
+
477
+ protected
478
+
479
+ def actual_source_reflection # FIXME: this is a horrible name
480
+ self
481
+ end
482
+
348
483
  private
484
+
485
+ def calculate_constructable(macro, options)
486
+ case macro
487
+ when :belongs_to
488
+ !polymorphic?
489
+ when :has_one
490
+ !options[:through]
491
+ else
492
+ true
493
+ end
494
+ end
495
+
496
+ # Attempts to find the inverse association name automatically.
497
+ # If it cannot find a suitable inverse association name, it returns
498
+ # nil.
499
+ def inverse_name
500
+ options.fetch(:inverse_of) do
501
+ if @automatic_inverse_of == false
502
+ nil
503
+ else
504
+ @automatic_inverse_of ||= automatic_inverse_of
505
+ end
506
+ end
507
+ end
508
+
509
+ # returns either nil or the inverse association name that it finds.
510
+ def automatic_inverse_of
511
+ if can_find_inverse_of_automatically?(self)
512
+ inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
513
+
514
+ begin
515
+ reflection = klass._reflect_on_association(inverse_name)
516
+ rescue NameError
517
+ # Give up: we couldn't compute the klass type so we won't be able
518
+ # to find any associations either.
519
+ reflection = false
520
+ end
521
+
522
+ if valid_inverse_reflection?(reflection)
523
+ return inverse_name
524
+ end
525
+ end
526
+
527
+ false
528
+ end
529
+
530
+ # Checks if the inverse reflection that is returned from the
531
+ # +automatic_inverse_of+ method is a valid reflection. We must
532
+ # make sure that the reflection's active_record name matches up
533
+ # with the current reflection's klass name.
534
+ #
535
+ # Note: klass will always be valid because when there's a NameError
536
+ # from calling +klass+, +reflection+ will already be set to false.
537
+ def valid_inverse_reflection?(reflection)
538
+ reflection &&
539
+ klass.name == reflection.active_record.name &&
540
+ can_find_inverse_of_automatically?(reflection)
541
+ end
542
+
543
+ # Checks to see if the reflection doesn't have any options that prevent
544
+ # us from being able to guess the inverse automatically. First, the
545
+ # <tt>inverse_of</tt> option cannot be set to false. Second, we must
546
+ # have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
547
+ # Third, we must not have options such as <tt>:polymorphic</tt> or
548
+ # <tt>:foreign_key</tt> which prevent us from correctly guessing the
549
+ # inverse association.
550
+ #
551
+ # Anything with a scope can additionally ruin our attempt at finding an
552
+ # inverse, so we exclude reflections with scopes.
553
+ def can_find_inverse_of_automatically?(reflection)
554
+ reflection.options[:inverse_of] != false &&
555
+ VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
556
+ !INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
557
+ !reflection.scope
558
+ end
559
+
349
560
  def derive_class_name
350
- class_name = name.to_s.camelize
561
+ class_name = name.to_s
351
562
  class_name = class_name.singularize if collection?
352
- class_name
563
+ class_name.camelize
353
564
  end
354
565
 
355
566
  def derive_foreign_key
@@ -362,27 +573,102 @@ module ActiveRecord
362
573
  end
363
574
  end
364
575
 
576
+ def derive_join_table
577
+ ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
578
+ end
579
+
365
580
  def primary_key(klass)
366
581
  klass.primary_key || raise(UnknownPrimaryKey.new(klass))
367
582
  end
368
583
  end
369
584
 
585
+ class HasManyReflection < AssociationReflection # :nodoc:
586
+ def initialize(name, scope, options, active_record)
587
+ super(name, scope, options, active_record)
588
+ end
589
+
590
+ def macro; :has_many; end
591
+
592
+ def collection?; true; end
593
+ end
594
+
595
+ class HasOneReflection < AssociationReflection # :nodoc:
596
+ def initialize(name, scope, options, active_record)
597
+ super(name, scope, options, active_record)
598
+ end
599
+
600
+ def macro; :has_one; end
601
+
602
+ def has_one?; true; end
603
+ end
604
+
605
+ class BelongsToReflection < AssociationReflection # :nodoc:
606
+ def initialize(name, scope, options, active_record)
607
+ super(name, scope, options, active_record)
608
+ end
609
+
610
+ def macro; :belongs_to; end
611
+
612
+ def belongs_to?; true; end
613
+
614
+ def join_keys(assoc_klass)
615
+ key = polymorphic? ? association_primary_key(assoc_klass) : association_primary_key
616
+ JoinKeys.new(key, foreign_key)
617
+ end
618
+
619
+ def join_id_for(owner) # :nodoc:
620
+ owner[foreign_key]
621
+ end
622
+ end
623
+
624
+ class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
625
+ def initialize(name, scope, options, active_record)
626
+ super
627
+ end
628
+
629
+ def macro; :has_and_belongs_to_many; end
630
+
631
+ def collection?
632
+ true
633
+ end
634
+ end
635
+
370
636
  # Holds all the meta-data about a :through association as it was specified
371
637
  # in the Active Record class.
372
- class ThroughReflection < AssociationReflection #:nodoc:
638
+ class ThroughReflection < AbstractReflection #:nodoc:
639
+ attr_reader :delegate_reflection
373
640
  delegate :foreign_key, :foreign_type, :association_foreign_key,
374
641
  :active_record_primary_key, :type, :to => :source_reflection
375
642
 
376
- # Gets the source of the through reflection. It checks both a singularized
643
+ def initialize(delegate_reflection)
644
+ @delegate_reflection = delegate_reflection
645
+ @klass = delegate_reflection.options[:anonymous_class]
646
+ @source_reflection_name = delegate_reflection.options[:source]
647
+ end
648
+
649
+ def klass
650
+ @klass ||= delegate_reflection.compute_class(class_name)
651
+ end
652
+
653
+ # Returns the source of the through reflection. It checks both a singularized
377
654
  # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
378
655
  #
379
656
  # class Post < ActiveRecord::Base
380
657
  # has_many :taggings
381
- # has_many :tags, :through => :taggings
658
+ # has_many :tags, through: :taggings
659
+ # end
660
+ #
661
+ # class Tagging < ActiveRecord::Base
662
+ # belongs_to :post
663
+ # belongs_to :tag
382
664
  # end
383
665
  #
666
+ # tags_reflection = Post.reflect_on_association(:tags)
667
+ # tags_reflection.source_reflection
668
+ # # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
669
+ #
384
670
  def source_reflection
385
- @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
671
+ through_reflection.klass._reflect_on_association(source_reflection_name)
386
672
  end
387
673
 
388
674
  # Returns the AssociationReflection object specified in the <tt>:through</tt> option
@@ -390,14 +676,15 @@ module ActiveRecord
390
676
  #
391
677
  # class Post < ActiveRecord::Base
392
678
  # has_many :taggings
393
- # has_many :tags, :through => :taggings
679
+ # has_many :tags, through: :taggings
394
680
  # end
395
681
  #
396
682
  # tags_reflection = Post.reflect_on_association(:tags)
397
- # taggings_reflection = tags_reflection.through_reflection
683
+ # tags_reflection.through_reflection
684
+ # # => <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">
398
685
  #
399
686
  def through_reflection
400
- @through_reflection ||= active_record.reflect_on_association(options[:through])
687
+ active_record._reflect_on_association(options[:through])
401
688
  end
402
689
 
403
690
  # Returns an array of reflections which are involved in this association. Each item in the
@@ -406,9 +693,22 @@ module ActiveRecord
406
693
  # The chain is built by recursively calling #chain on the source reflection and the through
407
694
  # reflection. The base case for the recursion is a normal association, which just returns
408
695
  # [self] as its #chain.
696
+ #
697
+ # class Post < ActiveRecord::Base
698
+ # has_many :taggings
699
+ # has_many :tags, through: :taggings
700
+ # end
701
+ #
702
+ # tags_reflection = Post.reflect_on_association(:tags)
703
+ # tags_reflection.chain
704
+ # # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
705
+ # <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
706
+ #
409
707
  def chain
410
708
  @chain ||= begin
411
- chain = source_reflection.chain + through_reflection.chain
709
+ a = source_reflection.chain
710
+ b = through_reflection.chain
711
+ chain = a + b
412
712
  chain[0] = self # Use self so we don't lose the information from :source_type
413
713
  chain
414
714
  end
@@ -418,52 +718,60 @@ module ActiveRecord
418
718
  #
419
719
  # class Person
420
720
  # has_many :articles
421
- # has_many :comment_tags, :through => :articles
721
+ # has_many :comment_tags, through: :articles
422
722
  # end
423
723
  #
424
724
  # class Article
425
725
  # has_many :comments
426
- # has_many :comment_tags, :through => :comments, :source => :tags
726
+ # has_many :comment_tags, through: :comments, source: :tags
427
727
  # end
428
728
  #
429
729
  # class Comment
430
730
  # has_many :tags
431
731
  # end
432
732
  #
433
- # There may be conditions on Person.comment_tags, Article.comment_tags and/or Comment.tags,
733
+ # There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
434
734
  # 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 }
735
+ # of scopes corresponding to the chain.
736
+ def scope_chain
737
+ @scope_chain ||= begin
738
+ scope_chain = source_reflection.scope_chain.map(&:dup)
441
739
 
442
- # Add to it the conditions from this reflection if necessary.
443
- conditions.first << options[:conditions] if options[:conditions]
740
+ # Add to it the scope from this reflection (if any)
741
+ scope_chain.first << scope if scope
444
742
 
445
- through_conditions = through_reflection.conditions
743
+ through_scope_chain = through_reflection.scope_chain.map(&:dup)
446
744
 
447
745
  if options[:source_type]
448
- through_conditions.first << { foreign_type => options[:source_type] }
746
+ type = foreign_type
747
+ source_type = options[:source_type]
748
+ through_scope_chain.first << lambda { |object|
749
+ where(type => source_type)
750
+ }
449
751
  end
450
752
 
451
753
  # Recursively fill out the rest of the array from the through reflection
452
- conditions += through_conditions
453
-
454
- # And return
455
- conditions
754
+ scope_chain + through_scope_chain
456
755
  end
457
756
  end
458
757
 
758
+ def join_keys(assoc_klass)
759
+ source_reflection.join_keys(assoc_klass)
760
+ end
761
+
459
762
  # The macro used by the source association
460
763
  def source_macro
764
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
765
+ ActiveRecord::Base.source_macro is deprecated and will be removed
766
+ without replacement.
767
+ MSG
768
+
461
769
  source_reflection.source_macro
462
770
  end
463
771
 
464
772
  # A through association is nested if there would be more than one join table
465
773
  def nested?
466
- chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
774
+ chain.length > 2
467
775
  end
468
776
 
469
777
  # We want to use the klass from this reflection, rather than just delegate straight to
@@ -472,20 +780,45 @@ module ActiveRecord
472
780
  def association_primary_key(klass = nil)
473
781
  # Get the "actual" source reflection if the immediate source reflection has a
474
782
  # 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)
783
+ actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
481
784
  end
482
785
 
483
- # Gets an array of possible <tt>:through</tt> source reflection names:
786
+ # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
484
787
  #
485
- # [:singularized, :pluralized]
788
+ # class Post < ActiveRecord::Base
789
+ # has_many :taggings
790
+ # has_many :tags, through: :taggings
791
+ # end
792
+ #
793
+ # tags_reflection = Post.reflect_on_association(:tags)
794
+ # tags_reflection.source_reflection_names
795
+ # # => [:tag, :tags]
486
796
  #
487
797
  def source_reflection_names
488
- @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
798
+ options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq
799
+ end
800
+
801
+ def source_reflection_name # :nodoc:
802
+ return @source_reflection_name if @source_reflection_name
803
+
804
+ names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
805
+ names = names.find_all { |n|
806
+ through_reflection.klass._reflect_on_association(n)
807
+ }
808
+
809
+ if names.length > 1
810
+ example_options = options.dup
811
+ example_options[:source] = source_reflection_names.first
812
+ ActiveSupport::Deprecation.warn \
813
+ "Ambiguous source reflection for through association. Please " \
814
+ "specify a :source directive on your declaration like:\n" \
815
+ "\n" \
816
+ " class #{active_record.name} < ActiveRecord::Base\n" \
817
+ " #{macro} :#{name}, #{example_options}\n" \
818
+ " end"
819
+ end
820
+
821
+ @source_reflection_name = names.first
489
822
  end
490
823
 
491
824
  def source_options
@@ -496,39 +829,65 @@ module ActiveRecord
496
829
  through_reflection.options
497
830
  end
498
831
 
832
+ def join_id_for(owner) # :nodoc:
833
+ source_reflection.join_id_for(owner)
834
+ end
835
+
499
836
  def check_validity!
500
837
  if through_reflection.nil?
501
838
  raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
502
839
  end
503
840
 
504
- if through_reflection.options[:polymorphic]
505
- raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
841
+ if through_reflection.polymorphic?
842
+ if has_one?
843
+ raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self)
844
+ else
845
+ raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
846
+ end
506
847
  end
507
848
 
508
849
  if source_reflection.nil?
509
850
  raise HasManyThroughSourceAssociationNotFoundError.new(self)
510
851
  end
511
852
 
512
- if options[:source_type] && source_reflection.options[:polymorphic].nil?
853
+ if options[:source_type] && !source_reflection.polymorphic?
513
854
  raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
514
855
  end
515
856
 
516
- if source_reflection.options[:polymorphic] && options[:source_type].nil?
857
+ if source_reflection.polymorphic? && options[:source_type].nil?
517
858
  raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
518
859
  end
519
860
 
520
- if macro == :has_one && through_reflection.collection?
861
+ if has_one? && through_reflection.collection?
521
862
  raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
522
863
  end
523
864
 
524
865
  check_validity_of_inverse!
525
866
  end
526
867
 
868
+ protected
869
+
870
+ def actual_source_reflection # FIXME: this is a horrible name
871
+ source_reflection.send(:actual_source_reflection)
872
+ end
873
+
874
+ def primary_key(klass)
875
+ klass.primary_key || raise(UnknownPrimaryKey.new(klass))
876
+ end
877
+
878
+ def inverse_name; delegate_reflection.send(:inverse_name); end
879
+
527
880
  private
528
881
  def derive_class_name
529
882
  # get the class_name of the belongs_to association of the through reflection
530
883
  options[:source_type] || source_reflection.class_name
531
884
  end
885
+
886
+ delegate_methods = AssociationReflection.public_instance_methods -
887
+ public_instance_methods
888
+
889
+ delegate(*delegate_methods, to: :delegate_reflection)
890
+
532
891
  end
533
892
  end
534
893
  end