activerecord 3.0.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -4,22 +4,20 @@ module ActiveRecord
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  def clear_aggregation_cache #:nodoc:
7
- self.class.reflect_on_all_aggregations.to_a.each do |assoc|
8
- instance_variable_set "@#{assoc.name}", nil
9
- end unless self.new_record?
7
+ @aggregation_cache.clear if persisted?
10
8
  end
11
9
 
12
10
  # Active Record implements aggregation through a macro-like class method called +composed_of+
13
11
  # for representing attributes as value objects. It expresses relationships like "Account [is]
14
12
  # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
15
- # to the macro adds a description of how the value objects are created from the attributes of
16
- # the entity object (when the entity is initialized either as a new object or from finding an
17
- # existing object) and how it can be turned back into attributes (when the entity is saved to
13
+ # to the macro adds a description of how the value objects are created from the attributes of
14
+ # the entity object (when the entity is initialized either as a new object or from finding an
15
+ # existing object) and how it can be turned back into attributes (when the entity is saved to
18
16
  # the database).
19
17
  #
20
18
  # class Customer < ActiveRecord::Base
21
- # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
22
- # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
19
+ # composed_of :balance, class_name: "Money", mapping: %w(balance amount)
20
+ # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
23
21
  # end
24
22
  #
25
23
  # The customer class now has the following methods to manipulate the value objects:
@@ -48,7 +46,7 @@ module ActiveRecord
48
46
  #
49
47
  # def <=>(other_money)
50
48
  # if currency == other_money.currency
51
- # amount <=> amount
49
+ # amount <=> other_money.amount
52
50
  # else
53
51
  # amount <=> other_money.exchange_to(currency).amount
54
52
  # end
@@ -73,7 +71,7 @@ module ActiveRecord
73
71
  # Now it's possible to access attributes from the database through the value objects instead. If
74
72
  # you choose to name the composition the same as the attribute's name, it will be the only way to
75
73
  # access that attribute. That's the case with our +balance+ attribute. You interact with the value
76
- # objects just like you would any other attribute, though:
74
+ # objects just like you would with any other attribute:
77
75
  #
78
76
  # customer.balance = Money.new(20) # sets the Money value object and the attribute
79
77
  # customer.balance # => Money value object
@@ -88,6 +86,12 @@ module ActiveRecord
88
86
  # customer.address_street = "Hyancintvej"
89
87
  # customer.address_city = "Copenhagen"
90
88
  # customer.address # => Address.new("Hyancintvej", "Copenhagen")
89
+ #
90
+ # customer.address_street = "Vesterbrogade"
91
+ # customer.address # => Address.new("Hyancintvej", "Copenhagen")
92
+ # customer.clear_aggregation_cache
93
+ # customer.address # => Address.new("Vesterbrogade", "Copenhagen")
94
+ #
91
95
  # customer.address = Address.new("May Street", "Chicago")
92
96
  # customer.address_street # => "May Street"
93
97
  # customer.address_city # => "Chicago"
@@ -103,13 +107,13 @@ module ActiveRecord
103
107
  # ActiveRecord::Base classes are entity objects.
104
108
  #
105
109
  # It's also important to treat the value objects as immutable. Don't allow the Money object to have
106
- # its amount changed after creation. Create a new Money object with the new value instead. This
107
- # is exemplified by the Money#exchange_to method that returns a new value object instead of changing
110
+ # its amount changed after creation. Create a new Money object with the new value instead. The
111
+ # Money#exchange_to method is an example of this. It returns a new value object instead of changing
108
112
  # its own values. Active Record won't persist value objects that have been changed through means
109
113
  # other than the writer method.
110
114
  #
111
115
  # The immutable requirement is enforced by Active Record by freezing any object assigned as a value
112
- # object. Attempting to change it afterwards will result in a ActiveSupport::FrozenObjectError.
116
+ # object. Attempting to change it afterwards will result in a RuntimeError.
113
117
  #
114
118
  # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
115
119
  # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
@@ -121,7 +125,7 @@ module ActiveRecord
121
125
  # option, as arguments. If the value class doesn't support this convention then +composed_of+ allows
122
126
  # a custom constructor to be specified.
123
127
  #
124
- # When a new value is assigned to the value object the default assumption is that the new value
128
+ # When a new value is assigned to the value object, the default assumption is that the new value
125
129
  # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
126
130
  # converted to an instance of value class if necessary.
127
131
  #
@@ -134,15 +138,15 @@ module ActiveRecord
134
138
  #
135
139
  # class NetworkResource < ActiveRecord::Base
136
140
  # composed_of :cidr,
137
- # :class_name => 'NetAddr::CIDR',
138
- # :mapping => [ %w(network_address network), %w(cidr_range bits) ],
139
- # :allow_nil => true,
140
- # :constructor => Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
141
- # :converter => Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
141
+ # class_name: 'NetAddr::CIDR',
142
+ # mapping: [ %w(network_address network), %w(cidr_range bits) ],
143
+ # allow_nil: true,
144
+ # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
145
+ # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
142
146
  # end
143
147
  #
144
148
  # # This calls the :constructor
145
- # network_resource = NetworkResource.new(:network_address => '192.168.0.1', :cidr_range => 24)
149
+ # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24)
146
150
  #
147
151
  # # These assignments will both use the :converter
148
152
  # network_resource.cidr = [ '192.168.2.1', 8 ]
@@ -161,7 +165,7 @@ module ActiveRecord
161
165
  # by specifying an instance of the value object in the conditions hash. The following example
162
166
  # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
163
167
  #
164
- # Customer.find(:all, :conditions => {:balance => Money.new(20, "USD")})
168
+ # Customer.where(balance: Money.new(20, "USD"))
165
169
  #
166
170
  module ClassMethods
167
171
  # Adds reader and writer methods for manipulating a value object:
@@ -174,11 +178,11 @@ module ActiveRecord
174
178
  # with this option.
175
179
  # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
176
180
  # object. Each mapping is represented as an array where the first item is the name of the
177
- # entity attribute and the second item is the name the attribute in the value object. The
178
- # order in which mappings are defined determine the order in which attributes are sent to the
181
+ # entity attribute and the second item is the name of the attribute in the value object. The
182
+ # order in which mappings are defined determines the order in which attributes are sent to the
179
183
  # value class constructor.
180
184
  # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
181
- # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
185
+ # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
182
186
  # mapped attributes.
183
187
  # This defaults to +false+.
184
188
  # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
@@ -189,19 +193,21 @@ module ActiveRecord
189
193
  # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
190
194
  # or a Proc that is called when a new value is assigned to the value object. The converter is
191
195
  # passed the single value that is used in the assignment and is only called if the new value is
192
- # not an instance of <tt>:class_name</tt>.
196
+ # not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter
197
+ # can return nil to skip the assignment.
193
198
  #
194
199
  # Option examples:
195
- # composed_of :temperature, :mapping => %w(reading celsius)
196
- # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money }
197
- # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
200
+ # composed_of :temperature, mapping: %w(reading celsius)
201
+ # composed_of :balance, class_name: "Money", mapping: %w(balance amount),
202
+ # converter: Proc.new { |balance| balance.to_money }
203
+ # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
198
204
  # composed_of :gps_location
199
- # composed_of :gps_location, :allow_nil => true
205
+ # composed_of :gps_location, allow_nil: true
200
206
  # composed_of :ip_address,
201
- # :class_name => 'IPAddr',
202
- # :mapping => %w(ip to_i),
203
- # :constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
204
- # :converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
207
+ # class_name: 'IPAddr',
208
+ # mapping: %w(ip to_i),
209
+ # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
210
+ # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
205
211
  #
206
212
  def composed_of(part_id, options = {})
207
213
  options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
@@ -217,58 +223,36 @@ module ActiveRecord
217
223
  reader_method(name, class_name, mapping, allow_nil, constructor)
218
224
  writer_method(name, class_name, mapping, allow_nil, converter)
219
225
 
220
- create_reflection(:composed_of, part_id, options, self)
226
+ create_reflection(:composed_of, part_id, nil, options, self)
221
227
  end
222
228
 
223
229
  private
224
230
  def reader_method(name, class_name, mapping, allow_nil, constructor)
225
- module_eval do
226
- define_method(name) do |*args|
227
- force_reload = args.first || false
228
-
229
- unless instance_variable_defined?("@#{name}")
230
- instance_variable_set("@#{name}", nil)
231
- end
232
-
233
- if (instance_variable_get("@#{name}").nil? || force_reload) && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
234
- attrs = mapping.collect {|pair| read_attribute(pair.first)}
235
- object = case constructor
236
- when Symbol
237
- class_name.constantize.send(constructor, *attrs)
238
- when Proc, Method
239
- constructor.call(*attrs)
240
- else
241
- raise ArgumentError, 'Constructor must be a symbol denoting the constructor method to call or a Proc to be invoked.'
242
- end
243
- instance_variable_set("@#{name}", object)
244
- end
245
- instance_variable_get("@#{name}")
231
+ define_method(name) do
232
+ if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
233
+ attrs = mapping.collect {|pair| read_attribute(pair.first)}
234
+ object = constructor.respond_to?(:call) ?
235
+ constructor.call(*attrs) :
236
+ class_name.constantize.send(constructor, *attrs)
237
+ @aggregation_cache[name] = object
246
238
  end
239
+ @aggregation_cache[name]
247
240
  end
248
-
249
241
  end
250
242
 
251
243
  def writer_method(name, class_name, mapping, allow_nil, converter)
252
- module_eval do
253
- define_method("#{name}=") do |part|
254
- if part.nil? && allow_nil
255
- mapping.each { |pair| self[pair.first] = nil }
256
- instance_variable_set("@#{name}", nil)
257
- else
258
- unless part.is_a?(class_name.constantize) || converter.nil?
259
- part = case converter
260
- when Symbol
261
- class_name.constantize.send(converter, part)
262
- when Proc, Method
263
- converter.call(part)
264
- else
265
- raise ArgumentError, 'Converter must be a symbol denoting the converter method to call or a Proc to be invoked.'
266
- end
267
- end
244
+ define_method("#{name}=") do |part|
245
+ klass = class_name.constantize
246
+ unless part.is_a?(klass) || converter.nil? || part.nil?
247
+ part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
248
+ end
268
249
 
269
- mapping.each { |pair| self[pair.first] = part.send(pair.last) }
270
- instance_variable_set("@#{name}", part.freeze)
271
- end
250
+ if part.nil? && allow_nil
251
+ mapping.each { |pair| self[pair.first] = nil }
252
+ @aggregation_cache[name] = nil
253
+ else
254
+ mapping.each { |pair| self[pair.first] = part.send(pair.last) }
255
+ @aggregation_cache[name] = part.freeze
272
256
  end
273
257
  end
274
258
  end
@@ -0,0 +1,76 @@
1
+ require 'active_support/core_ext/string/conversions'
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ # Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
6
+ # ActiveRecord::Associations::ThroughAssociationScope
7
+ class AliasTracker # :nodoc:
8
+ attr_reader :aliases, :table_joins, :connection
9
+
10
+ # table_joins is an array of arel joins which might conflict with the aliases we assign here
11
+ def initialize(connection = Base.connection, table_joins = [])
12
+ @aliases = Hash.new { |h,k| h[k] = initial_count_for(k) }
13
+ @table_joins = table_joins
14
+ @connection = connection
15
+ end
16
+
17
+ def aliased_table_for(table_name, aliased_name = nil)
18
+ table_alias = aliased_name_for(table_name, aliased_name)
19
+
20
+ if table_alias == table_name
21
+ Arel::Table.new(table_name)
22
+ else
23
+ Arel::Table.new(table_name).alias(table_alias)
24
+ end
25
+ end
26
+
27
+ def aliased_name_for(table_name, aliased_name = nil)
28
+ aliased_name ||= table_name
29
+
30
+ if aliases[table_name].zero?
31
+ # If it's zero, we can have our table_name
32
+ aliases[table_name] = 1
33
+ table_name
34
+ else
35
+ # Otherwise, we need to use an alias
36
+ aliased_name = connection.table_alias_for(aliased_name)
37
+
38
+ # Update the count
39
+ aliases[aliased_name] += 1
40
+
41
+ if aliases[aliased_name] > 1
42
+ "#{truncate(aliased_name)}_#{aliases[aliased_name]}"
43
+ else
44
+ aliased_name
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def initial_count_for(name)
52
+ return 0 if Arel::Table === table_joins
53
+
54
+ # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
55
+ quoted_name = connection.quote_table_name(name).downcase
56
+
57
+ counts = table_joins.map do |join|
58
+ if join.is_a?(Arel::Nodes::StringJoin)
59
+ # Table names + table aliases
60
+ join.left.downcase.scan(
61
+ /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
62
+ ).size
63
+ else
64
+ join.left.table_name == name ? 1 : 0
65
+ end
66
+ end
67
+
68
+ counts.sum
69
+ end
70
+
71
+ def truncate(name)
72
+ name.slice(0, connection.table_alias_length - 2)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,248 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ # = Active Record Associations
6
+ #
7
+ # This is the root class of all associations ('+ Foo' signifies an included module Foo):
8
+ #
9
+ # Association
10
+ # SingularAssociation
11
+ # HasOneAssociation
12
+ # HasOneThroughAssociation + ThroughAssociation
13
+ # BelongsToAssociation
14
+ # BelongsToPolymorphicAssociation
15
+ # CollectionAssociation
16
+ # HasAndBelongsToManyAssociation
17
+ # HasManyAssociation
18
+ # HasManyThroughAssociation + ThroughAssociation
19
+ class Association #:nodoc:
20
+ attr_reader :owner, :target, :reflection
21
+
22
+ delegate :options, :to => :reflection
23
+
24
+ def initialize(owner, reflection)
25
+ reflection.check_validity!
26
+
27
+ @owner, @reflection = owner, reflection
28
+
29
+ reset
30
+ reset_scope
31
+ end
32
+
33
+ # Returns the name of the table of the associated class:
34
+ #
35
+ # post.comments.aliased_table_name # => "comments"
36
+ #
37
+ def aliased_table_name
38
+ klass.table_name
39
+ end
40
+
41
+ # Resets the \loaded flag to +false+ and sets the \target to +nil+.
42
+ def reset
43
+ @loaded = false
44
+ @target = nil
45
+ @stale_state = nil
46
+ end
47
+
48
+ # Reloads the \target and returns +self+ on success.
49
+ def reload
50
+ reset
51
+ reset_scope
52
+ load_target
53
+ self unless target.nil?
54
+ end
55
+
56
+ # Has the \target been already \loaded?
57
+ def loaded?
58
+ @loaded
59
+ end
60
+
61
+ # Asserts the \target has been loaded setting the \loaded flag to +true+.
62
+ def loaded!
63
+ @loaded = true
64
+ @stale_state = stale_state
65
+ end
66
+
67
+ # The target is stale if the target no longer points to the record(s) that the
68
+ # relevant foreign_key(s) refers to. If stale, the association accessor method
69
+ # on the owner will reload the target. It's up to subclasses to implement the
70
+ # state_state method if relevant.
71
+ #
72
+ # Note that if the target has not been loaded, it is not considered stale.
73
+ def stale_target?
74
+ loaded? && @stale_state != stale_state
75
+ end
76
+
77
+ # Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
78
+ def target=(target)
79
+ @target = target
80
+ loaded!
81
+ end
82
+
83
+ def scope
84
+ target_scope.merge(association_scope)
85
+ end
86
+
87
+ def scoped
88
+ ActiveSupport::Deprecation.warn "#scoped is deprecated. use #scope instead."
89
+ scope
90
+ end
91
+
92
+ # The scope for this association.
93
+ #
94
+ # Note that the association_scope is merged into the target_scope only when the
95
+ # scope method is called. This is because at that point the call may be surrounded
96
+ # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
97
+ # actually gets built.
98
+ def association_scope
99
+ if klass
100
+ @association_scope ||= AssociationScope.new(self).scope
101
+ end
102
+ end
103
+
104
+ def reset_scope
105
+ @association_scope = nil
106
+ end
107
+
108
+ # Set the inverse association, if possible
109
+ def set_inverse_instance(record)
110
+ if record && invertible_for?(record)
111
+ inverse = record.association(inverse_reflection_for(record).name)
112
+ inverse.target = owner
113
+ end
114
+ end
115
+
116
+ # Returns the class of the target. belongs_to polymorphic overrides this to look at the
117
+ # polymorphic_type field on the owner.
118
+ def klass
119
+ reflection.klass
120
+ end
121
+
122
+ # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
123
+ # through association's scope)
124
+ def target_scope
125
+ klass.all
126
+ end
127
+
128
+ # Loads the \target if needed and returns it.
129
+ #
130
+ # This method is abstract in the sense that it relies on +find_target+,
131
+ # which is expected to be provided by descendants.
132
+ #
133
+ # If the \target is already \loaded it is just returned. Thus, you can call
134
+ # +load_target+ unconditionally to get the \target.
135
+ #
136
+ # ActiveRecord::RecordNotFound is rescued within the method, and it is
137
+ # not reraised. The proxy is \reset and +nil+ is the return value.
138
+ def load_target
139
+ @target = find_target if (@stale_state && stale_target?) || find_target?
140
+
141
+ loaded! unless loaded?
142
+ target
143
+ rescue ActiveRecord::RecordNotFound
144
+ reset
145
+ end
146
+
147
+ def interpolate(sql, record = nil)
148
+ if sql.respond_to?(:to_proc)
149
+ owner.instance_exec(record, &sql)
150
+ else
151
+ sql
152
+ end
153
+ end
154
+
155
+ # We can't dump @reflection since it contains the scope proc
156
+ def marshal_dump
157
+ ivars = (instance_variables - [:@reflection]).map { |name| [name, instance_variable_get(name)] }
158
+ [@reflection.name, ivars]
159
+ end
160
+
161
+ def marshal_load(data)
162
+ reflection_name, ivars = data
163
+ ivars.each { |name, val| instance_variable_set(name, val) }
164
+ @reflection = @owner.class.reflect_on_association(reflection_name)
165
+ end
166
+
167
+ def initialize_attributes(record) #:nodoc:
168
+ skip_assign = [reflection.foreign_key, reflection.type].compact
169
+ attributes = create_scope.except(*(record.changed - skip_assign))
170
+ record.assign_attributes(attributes)
171
+ set_inverse_instance(record)
172
+ end
173
+
174
+ private
175
+
176
+ def find_target?
177
+ !loaded? && (!owner.new_record? || foreign_key_present?) && klass
178
+ end
179
+
180
+ def creation_attributes
181
+ attributes = {}
182
+
183
+ if (reflection.macro == :has_one || reflection.macro == :has_many) && !options[:through]
184
+ attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
185
+
186
+ if reflection.options[:as]
187
+ attributes[reflection.type] = owner.class.base_class.name
188
+ end
189
+ end
190
+
191
+ attributes
192
+ end
193
+
194
+ # Sets the owner attributes on the given record
195
+ def set_owner_attributes(record)
196
+ creation_attributes.each { |key, value| record[key] = value }
197
+ end
198
+
199
+ # Should be true if there is a foreign key present on the owner which
200
+ # references the target. This is used to determine whether we can load
201
+ # the target if the owner is currently a new record (and therefore
202
+ # without a key).
203
+ #
204
+ # Currently implemented by belongs_to (vanilla and polymorphic) and
205
+ # has_one/has_many :through associations which go through a belongs_to
206
+ def foreign_key_present?
207
+ false
208
+ end
209
+
210
+ # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
211
+ # the kind of the class of the associated objects. Meant to be used as
212
+ # a sanity check when you are about to assign an associated record.
213
+ def raise_on_type_mismatch!(record)
214
+ unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
215
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
216
+ raise ActiveRecord::AssociationTypeMismatch, message
217
+ end
218
+ end
219
+
220
+ # Can be redefined by subclasses, notably polymorphic belongs_to
221
+ # The record parameter is necessary to support polymorphic inverses as we must check for
222
+ # the association in the specific class of the record.
223
+ def inverse_reflection_for(record)
224
+ reflection.inverse_of
225
+ end
226
+
227
+ # Returns true if inverse association on the given record needs to be set.
228
+ # This method is redefined by subclasses.
229
+ def invertible_for?(record)
230
+ inverse_reflection_for(record)
231
+ end
232
+
233
+ # This should be implemented to return the values of the relevant key(s) on the owner,
234
+ # so that when stale_state is different from the value stored on the last find_target,
235
+ # the target is stale.
236
+ #
237
+ # This is only relevant to certain associations, which is why it returns nil by default.
238
+ def stale_state
239
+ end
240
+
241
+ def build_record(attributes)
242
+ reflection.build_association(attributes) do |record|
243
+ initialize_attributes(record)
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end