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,4 @@
1
1
  require 'active_support/core_ext/array/wrap'
2
- require 'active_support/core_ext/object/inclusion'
3
2
 
4
3
  module ActiveRecord
5
4
  module Associations
@@ -14,39 +13,37 @@ module ActiveRecord
14
13
  # BelongsToAssociation
15
14
  # BelongsToPolymorphicAssociation
16
15
  # CollectionAssociation
17
- # HasAndBelongsToManyAssociation
18
16
  # HasManyAssociation
19
17
  # HasManyThroughAssociation + ThroughAssociation
20
18
  class Association #:nodoc:
21
19
  attr_reader :owner, :target, :reflection
20
+ attr_accessor :inversed
22
21
 
23
22
  delegate :options, :to => :reflection
24
23
 
25
24
  def initialize(owner, reflection)
26
25
  reflection.check_validity!
27
26
 
28
- @target = nil
29
27
  @owner, @reflection = owner, reflection
30
- @updated = false
31
28
 
32
29
  reset
33
30
  reset_scope
34
31
  end
35
32
 
36
- # Returns the name of the table of the related class:
33
+ # Returns the name of the table of the associated class:
37
34
  #
38
35
  # post.comments.aliased_table_name # => "comments"
39
36
  #
40
37
  def aliased_table_name
41
- reflection.klass.table_name
38
+ klass.table_name
42
39
  end
43
40
 
44
41
  # Resets the \loaded flag to +false+ and sets the \target to +nil+.
45
42
  def reset
46
43
  @loaded = false
47
- IdentityMap.remove(target) if IdentityMap.enabled? && target
48
44
  @target = nil
49
45
  @stale_state = nil
46
+ @inversed = false
50
47
  end
51
48
 
52
49
  # Reloads the \target and returns +self+ on success.
@@ -64,18 +61,19 @@ module ActiveRecord
64
61
 
65
62
  # Asserts the \target has been loaded setting the \loaded flag to +true+.
66
63
  def loaded!
67
- @loaded = true
64
+ @loaded = true
68
65
  @stale_state = stale_state
66
+ @inversed = false
69
67
  end
70
68
 
71
69
  # The target is stale if the target no longer points to the record(s) that the
72
70
  # relevant foreign_key(s) refers to. If stale, the association accessor method
73
71
  # on the owner will reload the target. It's up to subclasses to implement the
74
- # state_state method if relevant.
72
+ # stale_state method if relevant.
75
73
  #
76
74
  # Note that if the target has not been loaded, it is not considered stale.
77
75
  def stale_target?
78
- loaded? && @stale_state != stale_state
76
+ !inversed && loaded? && @stale_state != stale_state
79
77
  end
80
78
 
81
79
  # Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
@@ -84,19 +82,19 @@ module ActiveRecord
84
82
  loaded!
85
83
  end
86
84
 
87
- def scoped
85
+ def scope
88
86
  target_scope.merge(association_scope)
89
87
  end
90
88
 
91
89
  # The scope for this association.
92
90
  #
93
91
  # Note that the association_scope is merged into the target_scope only when the
94
- # scoped method is called. This is because at that point the call may be surrounded
92
+ # scope method is called. This is because at that point the call may be surrounded
95
93
  # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
96
94
  # actually gets built.
97
95
  def association_scope
98
96
  if klass
99
- @association_scope ||= AssociationScope.new(self).scope
97
+ @association_scope ||= AssociationScope.scope(self, klass.connection)
100
98
  end
101
99
  end
102
100
 
@@ -106,13 +104,15 @@ module ActiveRecord
106
104
 
107
105
  # Set the inverse association, if possible
108
106
  def set_inverse_instance(record)
109
- if record && invertible_for?(record)
107
+ if invertible_for?(record)
110
108
  inverse = record.association(inverse_reflection_for(record).name)
111
109
  inverse.target = owner
110
+ inverse.inversed = true
112
111
  end
112
+ record
113
113
  end
114
114
 
115
- # This class of the target. belongs_to polymorphic overrides this to look at the
115
+ # Returns the class of the target. belongs_to polymorphic overrides this to look at the
116
116
  # polymorphic_type field on the owner.
117
117
  def klass
118
118
  reflection.klass
@@ -121,7 +121,7 @@ module ActiveRecord
121
121
  # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
122
122
  # through association's scope)
123
123
  def target_scope
124
- klass.scoped
124
+ AssociationRelation.create(klass, klass.arel_table, self).merge!(klass.all)
125
125
  end
126
126
 
127
127
  # Loads the \target if needed and returns it.
@@ -129,28 +129,14 @@ module ActiveRecord
129
129
  # This method is abstract in the sense that it relies on +find_target+,
130
130
  # which is expected to be provided by descendants.
131
131
  #
132
- # If the \target is stale(the target no longer points to the record(s) that the
133
- # relevant foreign_key(s) refers to.), force reload the \target.
134
- #
135
- # Otherwise if the \target is already \loaded it is just returned. Thus, you can
136
- # call +load_target+ unconditionally to get the \target.
132
+ # If the \target is already \loaded it is just returned. Thus, you can call
133
+ # +load_target+ unconditionally to get the \target.
137
134
  #
138
135
  # ActiveRecord::RecordNotFound is rescued within the method, and it is
139
136
  # not reraised. The proxy is \reset and +nil+ is the return value.
140
137
  def load_target
141
- if (@stale_state && stale_target?) || find_target?
142
- begin
143
- if IdentityMap.enabled? && association_class && association_class.respond_to?(:base_class)
144
- @target = IdentityMap.get(association_class, owner[reflection.foreign_key])
145
- elsif @stale_state && stale_target?
146
- @target = find_target
147
- end
148
- rescue NameError
149
- nil
150
- ensure
151
- @target ||= find_target
152
- end
153
- end
138
+ @target = find_target if (@stale_state && stale_target?) || find_target?
139
+
154
140
  loaded! unless loaded?
155
141
  target
156
142
  rescue ActiveRecord::RecordNotFound
@@ -158,13 +144,32 @@ module ActiveRecord
158
144
  end
159
145
 
160
146
  def interpolate(sql, record = nil)
161
- if sql.respond_to?(:to_proc) && !sql.is_a?(Hash)
162
- owner.send(:instance_exec, record, &sql)
147
+ if sql.respond_to?(:to_proc)
148
+ owner.instance_exec(record, &sql)
163
149
  else
164
150
  sql
165
151
  end
166
152
  end
167
153
 
154
+ # We can't dump @reflection since it contains the scope proc
155
+ def marshal_dump
156
+ ivars = (instance_variables - [:@reflection]).map { |name| [name, instance_variable_get(name)] }
157
+ [@reflection.name, ivars]
158
+ end
159
+
160
+ def marshal_load(data)
161
+ reflection_name, ivars = data
162
+ ivars.each { |name, val| instance_variable_set(name, val) }
163
+ @reflection = @owner.class._reflect_on_association(reflection_name)
164
+ end
165
+
166
+ def initialize_attributes(record) #:nodoc:
167
+ skip_assign = [reflection.foreign_key, reflection.type].compact
168
+ attributes = create_scope.except(*(record.changed - skip_assign))
169
+ record.assign_attributes(attributes)
170
+ set_inverse_instance(record)
171
+ end
172
+
168
173
  private
169
174
 
170
175
  def find_target?
@@ -174,7 +179,7 @@ module ActiveRecord
174
179
  def creation_attributes
175
180
  attributes = {}
176
181
 
177
- if reflection.macro.in?([:has_one, :has_many]) && !options[:through]
182
+ if (reflection.has_one? || reflection.collection?) && !options[:through]
178
183
  attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
179
184
 
180
185
  if reflection.options[:as]
@@ -190,13 +195,14 @@ module ActiveRecord
190
195
  creation_attributes.each { |key, value| record[key] = value }
191
196
  end
192
197
 
193
- # Should be true if there is a foreign key present on the owner which
198
+ # Returns true if there is a foreign key present on the owner which
194
199
  # references the target. This is used to determine whether we can load
195
200
  # the target if the owner is currently a new record (and therefore
196
- # without a key).
201
+ # without a key). If the owner is a new record then foreign_key must
202
+ # be present in order to load target.
197
203
  #
198
204
  # Currently implemented by belongs_to (vanilla and polymorphic) and
199
- # has_one/has_many :through associations which go through a belongs_to
205
+ # has_one/has_many :through associations which go through a belongs_to.
200
206
  def foreign_key_present?
201
207
  false
202
208
  end
@@ -204,10 +210,13 @@ module ActiveRecord
204
210
  # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
205
211
  # the kind of the class of the associated objects. Meant to be used as
206
212
  # a sanity check when you are about to assign an associated record.
207
- def raise_on_type_mismatch(record)
208
- unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
209
- message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
210
- raise ActiveRecord::AssociationTypeMismatch, message
213
+ def raise_on_type_mismatch!(record)
214
+ unless record.is_a?(reflection.klass)
215
+ fresh_class = reflection.class_name.safe_constantize
216
+ unless fresh_class && record.is_a?(fresh_class)
217
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
218
+ raise ActiveRecord::AssociationTypeMismatch, message
219
+ end
211
220
  end
212
221
  end
213
222
 
@@ -218,29 +227,38 @@ module ActiveRecord
218
227
  reflection.inverse_of
219
228
  end
220
229
 
221
- # Is this association invertible? Can be redefined by subclasses.
230
+ # Returns true if inverse association on the given record needs to be set.
231
+ # This method is redefined by subclasses.
222
232
  def invertible_for?(record)
223
- inverse_reflection_for(record)
233
+ foreign_key_for?(record) && inverse_reflection_for(record)
234
+ end
235
+
236
+ # Returns true if record contains the foreign_key
237
+ def foreign_key_for?(record)
238
+ record.has_attribute?(reflection.foreign_key)
224
239
  end
225
240
 
226
241
  # This should be implemented to return the values of the relevant key(s) on the owner,
227
- # so that when state_state is different from the value stored on the last find_target,
242
+ # so that when stale_state is different from the value stored on the last find_target,
228
243
  # the target is stale.
229
244
  #
230
245
  # This is only relevant to certain associations, which is why it returns nil by default.
231
246
  def stale_state
232
247
  end
233
248
 
234
- def association_class
235
- @reflection.klass
249
+ def build_record(attributes)
250
+ reflection.build_association(attributes) do |record|
251
+ initialize_attributes(record)
252
+ end
236
253
  end
237
254
 
238
- def build_record(attributes, options)
239
- reflection.build_association(attributes, options) do |record|
240
- skip_assign = [reflection.foreign_key, reflection.type].compact
241
- attributes = create_scope.except(*(record.changed - skip_assign))
242
- record.assign_attributes(attributes, :without_protection => true)
243
- end
255
+ # Returns true if statement cache should be skipped on the association reader.
256
+ def skip_statement_cache?
257
+ reflection.scope_chain.any?(&:any?) ||
258
+ scope.eager_loading? ||
259
+ klass.current_scope ||
260
+ klass.default_scopes.any? ||
261
+ reflection.source_reflection.active_record.default_scopes.any?
244
262
  end
245
263
  end
246
264
  end
@@ -1,110 +1,183 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
3
  class AssociationScope #:nodoc:
4
- include JoinHelper
4
+ def self.scope(association, connection)
5
+ INSTANCE.scope association, connection
6
+ end
5
7
 
6
- attr_reader :association, :alias_tracker
8
+ class BindSubstitution
9
+ def initialize(block)
10
+ @block = block
11
+ end
7
12
 
8
- delegate :klass, :owner, :reflection, :interpolate, :to => :association
9
- delegate :chain, :conditions, :options, :source_options, :active_record, :to => :reflection
13
+ def bind_value(scope, column, value, alias_tracker)
14
+ substitute = alias_tracker.connection.substitute_at(column)
15
+ scope.bind_values += [[column, @block.call(value)]]
16
+ substitute
17
+ end
18
+ end
10
19
 
11
- def initialize(association)
12
- @association = association
13
- @alias_tracker = AliasTracker.new klass.connection
20
+ def self.create(&block)
21
+ block = block ? block : lambda { |val| val }
22
+ new BindSubstitution.new(block)
14
23
  end
15
24
 
16
- def scope
17
- scope = klass.unscoped
18
- scope = scope.extending(*Array.wrap(options[:extend]))
25
+ def initialize(bind_substitution)
26
+ @bind_substitution = bind_substitution
27
+ end
19
28
 
20
- # It's okay to just apply all these like this. The options will only be present if the
21
- # association supports that option; this is enforced by the association builder.
22
- scope = scope.apply_finder_options(options.slice(
23
- :readonly, :include, :order, :limit, :joins, :group, :having, :offset, :select))
29
+ INSTANCE = create
24
30
 
25
- if options[:through] && !options[:include]
26
- scope = scope.includes(source_options[:include])
27
- end
31
+ def scope(association, connection)
32
+ klass = association.klass
33
+ reflection = association.reflection
34
+ scope = klass.unscoped
35
+ owner = association.owner
36
+ alias_tracker = AliasTracker.empty connection
28
37
 
29
- scope = scope.uniq if options[:uniq]
38
+ scope.extending! Array(reflection.options[:extend])
39
+ add_constraints(scope, owner, klass, reflection, alias_tracker)
40
+ end
30
41
 
31
- add_constraints(scope)
42
+ def join_type
43
+ Arel::Nodes::InnerJoin
44
+ end
45
+
46
+ def self.get_bind_values(owner, chain)
47
+ binds = []
48
+ last_reflection = chain.last
49
+
50
+ binds << last_reflection.join_id_for(owner)
51
+ if last_reflection.type
52
+ binds << owner.class.base_class.name
53
+ end
54
+
55
+ chain.each_cons(2).each do |reflection, next_reflection|
56
+ if reflection.type
57
+ binds << next_reflection.klass.base_class.name
58
+ end
59
+ end
60
+ binds
32
61
  end
33
62
 
34
63
  private
35
64
 
36
- def add_constraints(scope)
37
- tables = construct_tables
65
+ def construct_tables(chain, klass, refl, alias_tracker)
66
+ chain.map do |reflection|
67
+ alias_tracker.aliased_table_for(
68
+ table_name_for(reflection, klass, refl),
69
+ table_alias_for(reflection, refl, reflection != refl)
70
+ )
71
+ end
72
+ end
38
73
 
39
- chain.each_with_index do |reflection, i|
40
- table, foreign_table = tables.shift, tables.first
74
+ def table_alias_for(reflection, refl, join = false)
75
+ name = "#{reflection.plural_name}_#{alias_suffix(refl)}"
76
+ name << "_join" if join
77
+ name
78
+ end
41
79
 
42
- if reflection.source_macro == :has_and_belongs_to_many
43
- join_table = tables.shift
80
+ def join(table, constraint)
81
+ table.create_join(table, table.create_on(constraint), join_type)
82
+ end
44
83
 
45
- scope = scope.joins(join(
46
- join_table,
47
- table[reflection.association_primary_key].
48
- eq(join_table[reflection.association_foreign_key])
49
- ))
84
+ def column_for(table_name, column_name, alias_tracker)
85
+ columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
86
+ columns[column_name]
87
+ end
50
88
 
51
- table, foreign_table = join_table, tables.first
52
- end
89
+ def bind_value(scope, column, value, alias_tracker)
90
+ @bind_substitution.bind_value scope, column, value, alias_tracker
91
+ end
53
92
 
54
- if reflection.source_macro == :belongs_to
55
- if reflection.options[:polymorphic]
56
- key = reflection.association_primary_key(klass)
57
- else
58
- key = reflection.association_primary_key
59
- end
93
+ def bind(scope, table_name, column_name, value, tracker)
94
+ column = column_for table_name, column_name, tracker
95
+ bind_value scope, column, value, tracker
96
+ end
60
97
 
61
- foreign_key = reflection.foreign_key
62
- else
63
- key = reflection.foreign_key
64
- foreign_key = reflection.active_record_primary_key
65
- end
98
+ def last_chain_scope(scope, table, reflection, owner, tracker, assoc_klass)
99
+ join_keys = reflection.join_keys(assoc_klass)
100
+ key = join_keys.key
101
+ foreign_key = join_keys.foreign_key
66
102
 
67
- conditions = self.conditions[i]
103
+ bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
104
+ scope = scope.where(table[key].eq(bind_val))
68
105
 
69
- if reflection == chain.last
70
- scope = scope.where(table[key].eq(owner[foreign_key]))
106
+ if reflection.type
107
+ value = owner.class.base_class.name
108
+ bind_val = bind scope, table.table_name, reflection.type, value, tracker
109
+ scope = scope.where(table[reflection.type].eq(bind_val))
110
+ else
111
+ scope
112
+ end
113
+ end
71
114
 
72
- if reflection.type
73
- scope = scope.where(table[reflection.type].eq(owner.class.base_class.name))
74
- end
115
+ def next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
116
+ join_keys = reflection.join_keys(assoc_klass)
117
+ key = join_keys.key
118
+ foreign_key = join_keys.foreign_key
75
119
 
76
- conditions.each do |condition|
77
- if options[:through] && condition.is_a?(Hash)
78
- condition = disambiguate_condition(table, condition)
79
- end
120
+ constraint = table[key].eq(foreign_table[foreign_key])
80
121
 
81
- scope = scope.where(interpolate(condition))
82
- end
83
- else
84
- constraint = table[key].eq(foreign_table[foreign_key])
122
+ if reflection.type
123
+ value = next_reflection.klass.base_class.name
124
+ bind_val = bind scope, table.table_name, reflection.type, value, tracker
125
+ scope = scope.where(table[reflection.type].eq(bind_val))
126
+ end
85
127
 
86
- if reflection.type
87
- type = chain[i + 1].klass.base_class.name
88
- constraint = constraint.and(table[reflection.type].eq(type))
89
- end
128
+ scope = scope.joins(join(foreign_table, constraint))
129
+ end
130
+
131
+ def add_constraints(scope, owner, assoc_klass, refl, tracker)
132
+ chain = refl.chain
133
+ scope_chain = refl.scope_chain
134
+
135
+ tables = construct_tables(chain, assoc_klass, refl, tracker)
136
+
137
+ owner_reflection = chain.last
138
+ table = tables.last
139
+ scope = last_chain_scope(scope, table, owner_reflection, owner, tracker, assoc_klass)
90
140
 
91
- scope = scope.joins(join(foreign_table, constraint))
141
+ chain.each_with_index do |reflection, i|
142
+ table, foreign_table = tables.shift, tables.first
143
+
144
+ unless reflection == chain.last
145
+ next_reflection = chain[i + 1]
146
+ scope = next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
147
+ end
148
+
149
+ is_first_chain = i == 0
150
+ klass = is_first_chain ? assoc_klass : reflection.klass
151
+
152
+ # Exclude the scope of the association itself, because that
153
+ # was already merged in the #scope method.
154
+ scope_chain[i].each do |scope_chain_item|
155
+ item = eval_scope(klass, scope_chain_item, owner)
92
156
 
93
- unless conditions.empty?
94
- scope = scope.where(sanitize(conditions, table))
157
+ if scope_chain_item == refl.scope
158
+ scope.merge! item.except(:where, :includes, :bind)
95
159
  end
160
+
161
+ if is_first_chain
162
+ scope.includes! item.includes_values
163
+ end
164
+
165
+ scope.unscope!(*item.unscope_values)
166
+ scope.where_values += item.where_values
167
+ scope.bind_values += item.bind_values
168
+ scope.order_values |= item.order_values
96
169
  end
97
170
  end
98
171
 
99
172
  scope
100
173
  end
101
174
 
102
- def alias_suffix
103
- reflection.name
175
+ def alias_suffix(refl)
176
+ refl.name
104
177
  end
105
178
 
106
- def table_name_for(reflection)
107
- if reflection == self.reflection
179
+ def table_name_for(reflection, klass, refl)
180
+ if reflection == refl
108
181
  # If this is a polymorphic belongs_to, we want to get the klass from the
109
182
  # association because it depends on the polymorphic_type attribute of
110
183
  # the owner
@@ -114,20 +187,8 @@ module ActiveRecord
114
187
  end
115
188
  end
116
189
 
117
- def disambiguate_condition(table, condition)
118
- if condition.is_a?(Hash)
119
- Hash[
120
- condition.map do |k, v|
121
- if v.is_a?(Hash)
122
- [k, v]
123
- else
124
- [table.table_alias || table.name, { k => v }]
125
- end
126
- end
127
- ]
128
- else
129
- condition
130
- end
190
+ def eval_scope(klass, scope, owner)
191
+ klass.unscoped.instance_exec(owner, &scope)
131
192
  end
132
193
  end
133
194
  end