activerecord 3.1.10 → 4.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  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 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  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 -102
  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 +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  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 +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  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 +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  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 +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  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 +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  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 +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  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 +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  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 +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  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 +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  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 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  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 +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,6 +1,5 @@
1
- require 'active_support/core_ext/class/attribute'
2
- require 'active_support/core_ext/module/deprecation'
3
- require 'active_support/core_ext/object/inclusion'
1
+ require 'thread'
2
+ require 'active_support/core_ext/string/filters'
4
3
 
5
4
  module ActiveRecord
6
5
  # = Active Record Reflection
@@ -8,11 +7,40 @@ module ActiveRecord
8
7
  extend ActiveSupport::Concern
9
8
 
10
9
  included do
11
- class_attribute :reflections
12
- self.reflections = {}
10
+ class_attribute :_reflections, instance_writer: false
11
+ class_attribute :aggregate_reflections, instance_writer: false
12
+ self._reflections = {}
13
+ self.aggregate_reflections = {}
13
14
  end
14
15
 
15
- # 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
16
44
  # about their associations and aggregations. This information can,
17
45
  # for example, be used in a form builder that takes an Active Record object
18
46
  # and creates input fields for all of the attributes depending on their type
@@ -21,22 +49,9 @@ module ActiveRecord
21
49
  # MacroReflection class has info for AggregateReflection and AssociationReflection
22
50
  # classes.
23
51
  module ClassMethods
24
- def create_reflection(macro, name, options, active_record)
25
- case macro
26
- when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
27
- klass = options[:through] ? ThroughReflection : AssociationReflection
28
- reflection = klass.new(macro, name, options, active_record)
29
- when :composed_of
30
- reflection = AggregateReflection.new(macro, name, options, active_record)
31
- end
32
-
33
- self.reflections = self.reflections.merge(name => reflection)
34
- reflection
35
- end
36
-
37
52
  # Returns an array of AggregateReflection objects for all the aggregations in the class.
38
53
  def reflect_on_all_aggregations
39
- reflections.values.grep(AggregateReflection)
54
+ aggregate_reflections.values
40
55
  end
41
56
 
42
57
  # Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
@@ -44,7 +59,30 @@ module ActiveRecord
44
59
  # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
45
60
  #
46
61
  def reflect_on_aggregation(aggregation)
47
- 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
48
86
  end
49
87
 
50
88
  # Returns an array of AssociationReflection objects for all the
@@ -57,8 +95,9 @@ module ActiveRecord
57
95
  # Account.reflect_on_all_associations # returns an array of all associations
58
96
  # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
59
97
  #
98
+ # @api public
60
99
  def reflect_on_all_associations(macro = nil)
61
- association_reflections = reflections.values.grep(AssociationReflection)
100
+ association_reflections = reflections.values
62
101
  macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
63
102
  end
64
103
 
@@ -67,65 +106,144 @@ module ActiveRecord
67
106
  # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
68
107
  # Invoice.reflect_on_association(:line_items).macro # returns :has_many
69
108
  #
109
+ # @api public
70
110
  def reflect_on_association(association)
71
- 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]
72
117
  end
73
118
 
74
119
  # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
120
+ #
121
+ # @api public
75
122
  def reflect_on_all_autosave_associations
76
123
  reflections.values.select { |reflection| reflection.options[:autosave] }
77
124
  end
125
+
126
+ def clear_reflections_cache #:nodoc:
127
+ @__reflections = nil
128
+ end
78
129
  end
79
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
80
159
 
81
- # Abstract base class for AggregateReflection and AssociationReflection. Objects of
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
165
+
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
82
190
  # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
83
- class MacroReflection
191
+ #
192
+ # MacroReflection
193
+ # AssociationReflection
194
+ # AggregateReflection
195
+ # HasManyReflection
196
+ # HasOneReflection
197
+ # BelongsToReflection
198
+ # ThroughReflection
199
+ class MacroReflection < AbstractReflection
84
200
  # Returns the name of the macro.
85
201
  #
86
- # <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>
87
203
  # <tt>has_many :clients</tt> returns <tt>:clients</tt>
88
204
  attr_reader :name
89
205
 
90
- # Returns the macro type.
91
- #
92
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
93
- # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
94
- attr_reader :macro
206
+ attr_reader :scope
95
207
 
96
208
  # Returns the hash of options used for the macro.
97
209
  #
98
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
99
- # <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>
100
212
  attr_reader :options
101
213
 
102
214
  attr_reader :active_record
103
215
 
104
216
  attr_reader :plural_name # :nodoc:
105
217
 
106
- def initialize(macro, name, options, active_record)
107
- @macro = macro
218
+ def initialize(name, scope, options, active_record)
108
219
  @name = name
220
+ @scope = scope
109
221
  @options = options
110
222
  @active_record = active_record
223
+ @klass = options[:anonymous_class]
111
224
  @plural_name = active_record.pluralize_table_names ?
112
225
  name.to_s.pluralize : name.to_s
113
226
  end
114
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
+
115
237
  # Returns the class for the macro.
116
238
  #
117
- # <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
118
240
  # <tt>has_many :clients</tt> returns the Client class
119
241
  def klass
120
- @klass ||= class_name.constantize
242
+ @klass ||= compute_class(class_name)
121
243
  end
122
244
 
123
- # Returns the class name for the macro.
124
- #
125
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
126
- # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
127
- def class_name
128
- @class_name ||= options[:class_name] || derive_class_name
245
+ def compute_class(name)
246
+ name.constantize
129
247
  end
130
248
 
131
249
  # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
@@ -134,14 +252,10 @@ module ActiveRecord
134
252
  super ||
135
253
  other_aggregation.kind_of?(self.class) &&
136
254
  name == other_aggregation.name &&
137
- other_aggregation.options &&
255
+ !other_aggregation.options.nil? &&
138
256
  active_record == other_aggregation.active_record
139
257
  end
140
258
 
141
- def sanitized_conditions #:nodoc:
142
- @sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
143
- end
144
-
145
259
  private
146
260
  def derive_class_name
147
261
  name.to_s.camelize
@@ -152,6 +266,10 @@ module ActiveRecord
152
266
  # Holds all the meta-data about an aggregation as it was specified in the
153
267
  # Active Record class.
154
268
  class AggregateReflection < MacroReflection #:nodoc:
269
+ def mapping
270
+ mapping = options[:mapping] || [name, name]
271
+ mapping.first.is_a?(Array) ? mapping : [mapping]
272
+ end
155
273
  end
156
274
 
157
275
  # Holds all the meta-data about an association as it was specified in the
@@ -170,53 +288,46 @@ module ActiveRecord
170
288
  # a new association object. Use +build_association+ or +create_association+
171
289
  # instead. This allows plugins to hook into association object creation.
172
290
  def klass
173
- @klass ||= active_record.send(:compute_type, class_name)
291
+ @klass ||= compute_class(class_name)
174
292
  end
175
293
 
176
- def initialize(macro, name, options, active_record)
177
- super
178
- @collection = macro.in?([:has_many, :has_and_belongs_to_many])
294
+ def compute_class(name)
295
+ active_record.send(:compute_type, name)
179
296
  end
180
297
 
181
- # This is a hack so that we can tell if build_association was overridden, in order to
182
- # provide an appropriate deprecation if the overridden method ignored the &block. Please
183
- # see Association#build_record for details.
184
- attr_accessor :original_build_association_called # :nodoc
298
+ attr_reader :type, :foreign_type
299
+ attr_accessor :parent_reflection # [:name, Reflection]
185
300
 
186
- # Returns a new, unsaved instance of the associated class. +options+ will
187
- # be passed to the class's constructor.
188
- def build_association(*options, &block)
189
- @original_build_association_called = true
190
- klass.new(*options, &block)
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
+ }
191
319
  end
192
320
 
193
- def table_name
194
- @table_name ||= klass.table_name
321
+ def constructable? # :nodoc:
322
+ @constructable
195
323
  end
196
324
 
197
- def quoted_table_name
198
- @quoted_table_name ||= klass.quoted_table_name
325
+ def join_table
326
+ @join_table ||= options[:join_table] || derive_join_table
199
327
  end
200
328
 
201
329
  def foreign_key
202
- @foreign_key ||= options[:foreign_key] || derive_foreign_key
203
- end
204
-
205
- def primary_key_name
206
- foreign_key
207
- end
208
- deprecate :primary_key_name => :foreign_key
209
-
210
- def foreign_type
211
- @foreign_type ||= options[:foreign_type] || "#{name}_type"
212
- end
213
-
214
- def type
215
- @type ||= options[:as] && "#{options[:as]}_type"
216
- end
217
-
218
- def primary_key_column
219
- @primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
330
+ @foreign_key ||= options[:foreign_key] || derive_foreign_key.freeze
220
331
  end
221
332
 
222
333
  def association_foreign_key
@@ -240,32 +351,36 @@ module ActiveRecord
240
351
  end
241
352
  end
242
353
 
243
- def columns(tbl_name, log_msg)
244
- @columns ||= klass.connection.columns(tbl_name, log_msg)
245
- end
246
-
247
- def reset_column_information
248
- @columns = nil
249
- end
250
-
251
354
  def check_validity!
252
355
  check_validity_of_inverse!
253
356
  end
254
357
 
255
- def check_validity_of_inverse!
256
- unless options[:polymorphic]
257
- if has_inverse? && inverse_of.nil?
258
- raise InverseOfAssociationNotFoundError.new(self)
259
- 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
260
370
  end
261
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
262
377
 
263
378
  def through_reflection
264
379
  nil
265
380
  end
266
381
 
267
382
  def source_reflection
268
- nil
383
+ self
269
384
  end
270
385
 
271
386
  # A chain of reflections from this one back to the owner. For more see the explanation in
@@ -274,28 +389,23 @@ module ActiveRecord
274
389
  [self]
275
390
  end
276
391
 
277
- # An array of arrays of conditions. Each item in the outside array corresponds to a reflection
278
- # in the #chain. The inside arrays are simply conditions (and each condition may itself be
279
- # a hash, array, arel predicate, etc...)
280
- def conditions
281
- [[options[:conditions]].compact]
392
+ def nested?
393
+ false
282
394
  end
283
395
 
284
- alias :source_macro :macro
285
-
286
- def has_inverse?
287
- @options[:inverse_of]
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]] : [[]]
288
400
  end
289
401
 
290
- def inverse_of
291
- if has_inverse?
292
- @inverse_of ||= klass.reflect_on_association(options[:inverse_of])
293
- end
402
+ def has_inverse?
403
+ inverse_name
294
404
  end
295
405
 
296
406
  def polymorphic_inverse_of(associated_class)
297
407
  if has_inverse?
298
- if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of])
408
+ if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
299
409
  inverse_relationship
300
410
  else
301
411
  raise InverseOfAssociationNotFoundError.new(self, associated_class)
@@ -303,41 +413,45 @@ module ActiveRecord
303
413
  end
304
414
  end
305
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
+
306
421
  # Returns whether or not this association reflection is for a collection
307
422
  # association. Returns +true+ if the +macro+ is either +has_many+ or
308
423
  # +has_and_belongs_to_many+, +false+ otherwise.
309
424
  def collection?
310
- @collection
425
+ false
311
426
  end
312
427
 
313
428
  # Returns whether or not the association should be validated as part of
314
429
  # the parent's validation.
315
430
  #
316
431
  # Unless you explicitly disable validation with
317
- # <tt>:validate => false</tt>, validation will take place when:
432
+ # <tt>validate: false</tt>, validation will take place when:
318
433
  #
319
- # * you explicitly enable validation; <tt>:validate => true</tt>
320
- # * you use autosave; <tt>:autosave => true</tt>
434
+ # * you explicitly enable validation; <tt>validate: true</tt>
435
+ # * you use autosave; <tt>autosave: true</tt>
321
436
  # * the association is a +has_many+ association
322
437
  def validate?
323
- !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
438
+ !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?)
324
439
  end
325
440
 
326
441
  # Returns +true+ if +self+ is a +belongs_to+ reflection.
327
- def belongs_to?
328
- macro == :belongs_to
329
- end
442
+ def belongs_to?; false; end
443
+
444
+ # Returns +true+ if +self+ is a +has_one+ reflection.
445
+ def has_one?; false; end
330
446
 
331
447
  def association_class
332
448
  case macro
333
449
  when :belongs_to
334
- if options[:polymorphic]
450
+ if polymorphic?
335
451
  Associations::BelongsToPolymorphicAssociation
336
452
  else
337
453
  Associations::BelongsToAssociation
338
454
  end
339
- when :has_and_belongs_to_many
340
- Associations::HasAndBelongsToManyAssociation
341
455
  when :has_many
342
456
  if options[:through]
343
457
  Associations::HasManyThroughAssociation
@@ -353,11 +467,100 @@ module ActiveRecord
353
467
  end
354
468
  end
355
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
+
356
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
+
357
560
  def derive_class_name
358
- class_name = name.to_s.camelize
561
+ class_name = name.to_s
359
562
  class_name = class_name.singularize if collection?
360
- class_name
563
+ class_name.camelize
361
564
  end
362
565
 
363
566
  def derive_foreign_key
@@ -370,27 +573,102 @@ module ActiveRecord
370
573
  end
371
574
  end
372
575
 
576
+ def derive_join_table
577
+ ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
578
+ end
579
+
373
580
  def primary_key(klass)
374
581
  klass.primary_key || raise(UnknownPrimaryKey.new(klass))
375
582
  end
376
583
  end
377
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
+
378
636
  # Holds all the meta-data about a :through association as it was specified
379
637
  # in the Active Record class.
380
- class ThroughReflection < AssociationReflection #:nodoc:
638
+ class ThroughReflection < AbstractReflection #:nodoc:
639
+ attr_reader :delegate_reflection
381
640
  delegate :foreign_key, :foreign_type, :association_foreign_key,
382
641
  :active_record_primary_key, :type, :to => :source_reflection
383
642
 
384
- # 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
385
654
  # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
386
655
  #
387
656
  # class Post < ActiveRecord::Base
388
657
  # has_many :taggings
389
- # 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
390
664
  # end
391
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
+ #
392
670
  def source_reflection
393
- @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)
394
672
  end
395
673
 
396
674
  # Returns the AssociationReflection object specified in the <tt>:through</tt> option
@@ -398,14 +676,15 @@ module ActiveRecord
398
676
  #
399
677
  # class Post < ActiveRecord::Base
400
678
  # has_many :taggings
401
- # has_many :tags, :through => :taggings
679
+ # has_many :tags, through: :taggings
402
680
  # end
403
681
  #
404
682
  # tags_reflection = Post.reflect_on_association(:tags)
405
- # taggings_reflection = tags_reflection.through_reflection
683
+ # tags_reflection.through_reflection
684
+ # # => <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">
406
685
  #
407
686
  def through_reflection
408
- @through_reflection ||= active_record.reflect_on_association(options[:through])
687
+ active_record._reflect_on_association(options[:through])
409
688
  end
410
689
 
411
690
  # Returns an array of reflections which are involved in this association. Each item in the
@@ -414,9 +693,22 @@ module ActiveRecord
414
693
  # The chain is built by recursively calling #chain on the source reflection and the through
415
694
  # reflection. The base case for the recursion is a normal association, which just returns
416
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
+ #
417
707
  def chain
418
708
  @chain ||= begin
419
- chain = source_reflection.chain + through_reflection.chain
709
+ a = source_reflection.chain
710
+ b = through_reflection.chain
711
+ chain = a + b
420
712
  chain[0] = self # Use self so we don't lose the information from :source_type
421
713
  chain
422
714
  end
@@ -426,52 +718,60 @@ module ActiveRecord
426
718
  #
427
719
  # class Person
428
720
  # has_many :articles
429
- # has_many :comment_tags, :through => :articles
721
+ # has_many :comment_tags, through: :articles
430
722
  # end
431
723
  #
432
724
  # class Article
433
725
  # has_many :comments
434
- # has_many :comment_tags, :through => :comments, :source => :tags
726
+ # has_many :comment_tags, through: :comments, source: :tags
435
727
  # end
436
728
  #
437
729
  # class Comment
438
730
  # has_many :tags
439
731
  # end
440
732
  #
441
- # 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,
442
734
  # but only Comment.tags will be represented in the #chain. So this method creates an array
443
- # of conditions corresponding to the chain. Each item in the #conditions array corresponds
444
- # to an item in the #chain, and is itself an array of conditions from an arbitrary number
445
- # of relevant reflections, plus any :source_type or polymorphic :as constraints.
446
- def conditions
447
- @conditions ||= begin
448
- 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)
449
739
 
450
- # Add to it the conditions from this reflection if necessary.
451
- 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
452
742
 
453
- through_conditions = through_reflection.conditions
743
+ through_scope_chain = through_reflection.scope_chain.map(&:dup)
454
744
 
455
745
  if options[:source_type]
456
- 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
+ }
457
751
  end
458
752
 
459
753
  # Recursively fill out the rest of the array from the through reflection
460
- conditions += through_conditions
461
-
462
- # And return
463
- conditions
754
+ scope_chain + through_scope_chain
464
755
  end
465
756
  end
466
757
 
758
+ def join_keys(assoc_klass)
759
+ source_reflection.join_keys(assoc_klass)
760
+ end
761
+
467
762
  # The macro used by the source association
468
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
+
469
769
  source_reflection.source_macro
470
770
  end
471
771
 
472
- # A through association is nested iff there would be more than one join table
772
+ # A through association is nested if there would be more than one join table
473
773
  def nested?
474
- chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
774
+ chain.length > 2
475
775
  end
476
776
 
477
777
  # We want to use the klass from this reflection, rather than just delegate straight to
@@ -480,20 +780,45 @@ module ActiveRecord
480
780
  def association_primary_key(klass = nil)
481
781
  # Get the "actual" source reflection if the immediate source reflection has a
482
782
  # source reflection itself
483
- source_reflection = self.source_reflection
484
- while source_reflection.source_reflection
485
- source_reflection = source_reflection.source_reflection
486
- end
487
-
488
- source_reflection.options[:primary_key] || primary_key(klass || self.klass)
783
+ actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
489
784
  end
490
785
 
491
- # 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.
492
787
  #
493
- # [: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]
494
796
  #
495
797
  def source_reflection_names
496
- @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
497
822
  end
498
823
 
499
824
  def source_options
@@ -504,39 +829,65 @@ module ActiveRecord
504
829
  through_reflection.options
505
830
  end
506
831
 
832
+ def join_id_for(owner) # :nodoc:
833
+ source_reflection.join_id_for(owner)
834
+ end
835
+
507
836
  def check_validity!
508
837
  if through_reflection.nil?
509
838
  raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
510
839
  end
511
840
 
512
- if through_reflection.options[:polymorphic]
513
- 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
514
847
  end
515
848
 
516
849
  if source_reflection.nil?
517
850
  raise HasManyThroughSourceAssociationNotFoundError.new(self)
518
851
  end
519
852
 
520
- if options[:source_type] && source_reflection.options[:polymorphic].nil?
853
+ if options[:source_type] && !source_reflection.polymorphic?
521
854
  raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
522
855
  end
523
856
 
524
- if source_reflection.options[:polymorphic] && options[:source_type].nil?
857
+ if source_reflection.polymorphic? && options[:source_type].nil?
525
858
  raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
526
859
  end
527
860
 
528
- if macro == :has_one && through_reflection.collection?
861
+ if has_one? && through_reflection.collection?
529
862
  raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
530
863
  end
531
864
 
532
865
  check_validity_of_inverse!
533
866
  end
534
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
+
535
880
  private
536
881
  def derive_class_name
537
882
  # get the class_name of the belongs_to association of the through reflection
538
883
  options[:source_type] || source_reflection.class_name
539
884
  end
885
+
886
+ delegate_methods = AssociationReflection.public_instance_methods -
887
+ public_instance_methods
888
+
889
+ delegate(*delegate_methods, to: :delegate_reflection)
890
+
540
891
  end
541
892
  end
542
893
  end