activerecord 4.1.8 → 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 (186) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1165 -1591
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +15 -8
  5. data/lib/active_record/association_relation.rb +13 -0
  6. data/lib/active_record/associations/alias_tracker.rb +3 -12
  7. data/lib/active_record/associations/association.rb +16 -4
  8. data/lib/active_record/associations/association_scope.rb +84 -43
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -10
  10. data/lib/active_record/associations/builder/association.rb +16 -5
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -14
  14. data/lib/active_record/associations/builder/has_many.rb +1 -1
  15. data/lib/active_record/associations/builder/has_one.rb +2 -2
  16. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  17. data/lib/active_record/associations/collection_association.rb +87 -30
  18. data/lib/active_record/associations/collection_proxy.rb +33 -35
  19. data/lib/active_record/associations/foreign_association.rb +11 -0
  20. data/lib/active_record/associations/has_many_association.rb +83 -22
  21. data/lib/active_record/associations/has_many_through_association.rb +49 -26
  22. data/lib/active_record/associations/has_one_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
  24. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  25. data/lib/active_record/associations/join_dependency.rb +26 -12
  26. data/lib/active_record/associations/preloader/association.rb +14 -10
  27. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  28. data/lib/active_record/associations/preloader.rb +37 -26
  29. data/lib/active_record/associations/singular_association.rb +17 -2
  30. data/lib/active_record/associations/through_association.rb +16 -12
  31. data/lib/active_record/associations.rb +158 -49
  32. data/lib/active_record/attribute.rb +163 -0
  33. data/lib/active_record/attribute_assignment.rb +20 -12
  34. data/lib/active_record/attribute_decorators.rb +66 -0
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  36. data/lib/active_record/attribute_methods/dirty.rb +107 -43
  37. data/lib/active_record/attribute_methods/primary_key.rb +7 -8
  38. data/lib/active_record/attribute_methods/query.rb +1 -1
  39. data/lib/active_record/attribute_methods/read.rb +22 -59
  40. data/lib/active_record/attribute_methods/serialization.rb +16 -150
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -28
  42. data/lib/active_record/attribute_methods/write.rb +9 -24
  43. data/lib/active_record/attribute_methods.rb +57 -95
  44. data/lib/active_record/attribute_set/builder.rb +106 -0
  45. data/lib/active_record/attribute_set.rb +81 -0
  46. data/lib/active_record/attributes.rb +147 -0
  47. data/lib/active_record/autosave_association.rb +30 -12
  48. data/lib/active_record/base.rb +13 -24
  49. data/lib/active_record/callbacks.rb +6 -6
  50. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +85 -53
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
  54. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +139 -57
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +271 -74
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -60
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +295 -141
  62. data/lib/active_record/connection_adapters/column.rb +29 -240
  63. data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
  64. data/lib/active_record/connection_adapters/mysql2_adapter.rb +17 -33
  65. data/lib/active_record/connection_adapters/mysql_adapter.rb +68 -145
  66. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  67. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
  69. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -385
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  97. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  98. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +134 -43
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
  101. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
  103. data/lib/active_record/connection_handling.rb +1 -1
  104. data/lib/active_record/core.rb +163 -40
  105. data/lib/active_record/counter_cache.rb +60 -6
  106. data/lib/active_record/enum.rb +10 -12
  107. data/lib/active_record/errors.rb +53 -30
  108. data/lib/active_record/explain.rb +1 -1
  109. data/lib/active_record/explain_subscriber.rb +1 -1
  110. data/lib/active_record/fixtures.rb +62 -74
  111. data/lib/active_record/gem_version.rb +4 -4
  112. data/lib/active_record/inheritance.rb +35 -10
  113. data/lib/active_record/integration.rb +4 -4
  114. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  115. data/lib/active_record/locking/optimistic.rb +46 -26
  116. data/lib/active_record/migration/command_recorder.rb +19 -2
  117. data/lib/active_record/migration/join_table.rb +1 -1
  118. data/lib/active_record/migration.rb +79 -47
  119. data/lib/active_record/model_schema.rb +52 -58
  120. data/lib/active_record/nested_attributes.rb +18 -8
  121. data/lib/active_record/no_touching.rb +1 -1
  122. data/lib/active_record/persistence.rb +48 -27
  123. data/lib/active_record/query_cache.rb +3 -3
  124. data/lib/active_record/querying.rb +10 -7
  125. data/lib/active_record/railtie.rb +19 -14
  126. data/lib/active_record/railties/databases.rake +55 -56
  127. data/lib/active_record/readonly_attributes.rb +0 -1
  128. data/lib/active_record/reflection.rb +281 -117
  129. data/lib/active_record/relation/batches.rb +0 -1
  130. data/lib/active_record/relation/calculations.rb +41 -37
  131. data/lib/active_record/relation/delegation.rb +1 -1
  132. data/lib/active_record/relation/finder_methods.rb +71 -48
  133. data/lib/active_record/relation/merger.rb +39 -29
  134. data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
  135. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  136. data/lib/active_record/relation/predicate_builder.rb +42 -12
  137. data/lib/active_record/relation/query_methods.rb +130 -73
  138. data/lib/active_record/relation/spawn_methods.rb +10 -3
  139. data/lib/active_record/relation.rb +57 -25
  140. data/lib/active_record/result.rb +18 -7
  141. data/lib/active_record/sanitization.rb +12 -2
  142. data/lib/active_record/schema.rb +0 -1
  143. data/lib/active_record/schema_dumper.rb +59 -28
  144. data/lib/active_record/schema_migration.rb +5 -4
  145. data/lib/active_record/scoping/default.rb +6 -4
  146. data/lib/active_record/scoping/named.rb +4 -0
  147. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  148. data/lib/active_record/statement_cache.rb +95 -10
  149. data/lib/active_record/store.rb +5 -5
  150. data/lib/active_record/tasks/database_tasks.rb +61 -8
  151. data/lib/active_record/tasks/mysql_database_tasks.rb +32 -17
  152. data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
  153. data/lib/active_record/timestamp.rb +9 -7
  154. data/lib/active_record/transactions.rb +54 -28
  155. data/lib/active_record/type/big_integer.rb +13 -0
  156. data/lib/active_record/type/binary.rb +50 -0
  157. data/lib/active_record/type/boolean.rb +31 -0
  158. data/lib/active_record/type/date.rb +50 -0
  159. data/lib/active_record/type/date_time.rb +54 -0
  160. data/lib/active_record/type/decimal.rb +64 -0
  161. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  162. data/lib/active_record/type/decorator.rb +14 -0
  163. data/lib/active_record/type/float.rb +19 -0
  164. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  165. data/lib/active_record/type/integer.rb +59 -0
  166. data/lib/active_record/type/mutable.rb +16 -0
  167. data/lib/active_record/type/numeric.rb +36 -0
  168. data/lib/active_record/type/serialized.rb +62 -0
  169. data/lib/active_record/type/string.rb +40 -0
  170. data/lib/active_record/type/text.rb +11 -0
  171. data/lib/active_record/type/time.rb +26 -0
  172. data/lib/active_record/type/time_value.rb +38 -0
  173. data/lib/active_record/type/type_map.rb +64 -0
  174. data/lib/active_record/type/unsigned_integer.rb +15 -0
  175. data/lib/active_record/type/value.rb +110 -0
  176. data/lib/active_record/type.rb +23 -0
  177. data/lib/active_record/validations/associated.rb +5 -3
  178. data/lib/active_record/validations/presence.rb +5 -3
  179. data/lib/active_record/validations/uniqueness.rb +24 -20
  180. data/lib/active_record/validations.rb +25 -19
  181. data/lib/active_record.rb +5 -0
  182. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  183. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  184. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  185. metadata +66 -11
  186. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
data/README.rdoc CHANGED
@@ -1,4 +1,4 @@
1
- = Active Record -- Object-relational mapping put on rails
1
+ = Active Record -- Object-relational mapping in Rails
2
2
 
3
3
  Active Record connects classes to relational database tables to establish an
4
4
  almost zero-configuration persistence layer for applications. The library
@@ -20,8 +20,10 @@ A short rundown of some of the major features:
20
20
  class Product < ActiveRecord::Base
21
21
  end
22
22
 
23
- The Product class is automatically mapped to the table named "products",
24
- which might look like this:
23
+ {Learn more}[link:classes/ActiveRecord/Base.html]
24
+
25
+ The Product class is automatically mapped to the table named "products",
26
+ which might look like this:
25
27
 
26
28
  CREATE TABLE products (
27
29
  id int(11) NOT NULL auto_increment,
@@ -29,10 +31,8 @@ A short rundown of some of the major features:
29
31
  PRIMARY KEY (id)
30
32
  );
31
33
 
32
- This would also define the following accessors: `Product#name` and
33
- `Product#name=(new_name)`
34
-
35
- {Learn more}[link:classes/ActiveRecord/Base.html]
34
+ This would also define the following accessors: `Product#name` and
35
+ `Product#name=(new_name)`.
36
36
 
37
37
 
38
38
  * Associations between objects defined by simple class methods.
@@ -130,7 +130,7 @@ A short rundown of some of the major features:
130
130
  SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html].
131
131
 
132
132
 
133
- * Logging support for Log4r[http://log4r.rubyforge.org] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc].
133
+ * Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc].
134
134
 
135
135
  ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
136
136
  ActiveRecord::Base.logger = Log4r::Logger.new('Application Log')
@@ -192,7 +192,7 @@ The latest version of Active Record can be installed with RubyGems:
192
192
 
193
193
  Source code can be downloaded as part of the Rails project on GitHub:
194
194
 
195
- * https://github.com/rails/rails/tree/4-1-stable/activerecord
195
+ * https://github.com/rails/rails/tree/4-2-stable/activerecord
196
196
 
197
197
 
198
198
  == License
@@ -208,6 +208,11 @@ API documentation is at:
208
208
 
209
209
  * http://api.rubyonrails.org
210
210
 
211
- Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
211
+ Bug reports can be filed for the Ruby on Rails project here:
212
212
 
213
213
  * https://github.com/rails/rails/issues
214
+
215
+ Feature requests should be discussed on the rails-core mailing list here:
216
+
217
+ * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
218
+
@@ -129,10 +129,10 @@ module ActiveRecord
129
129
  # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
130
130
  # converted to an instance of value class if necessary.
131
131
  #
132
- # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that
133
- # should be aggregated using the NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor
134
- # for the value class is called +create+ and it expects a CIDR address string as a parameter. New
135
- # values can be assigned to the value object using either another NetAddr::CIDR object, a string
132
+ # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that should be
133
+ # aggregated using the NetAddr::CIDR value class (http://www.ruby-doc.org/gems/docs/n/netaddr-1.5.0/NetAddr/CIDR.html).
134
+ # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter.
135
+ # New values can be assigned to the value object using either another NetAddr::CIDR object, a string
136
136
  # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
137
137
  # these requirements:
138
138
  #
@@ -230,8 +230,8 @@ module ActiveRecord
230
230
  private
231
231
  def reader_method(name, class_name, mapping, allow_nil, constructor)
232
232
  define_method(name) do
233
- if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
234
- attrs = mapping.collect {|pair| read_attribute(pair.first)}
233
+ if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !_read_attribute(key).nil? })
234
+ attrs = mapping.collect {|key, _| _read_attribute(key)}
235
235
  object = constructor.respond_to?(:call) ?
236
236
  constructor.call(*attrs) :
237
237
  class_name.constantize.send(constructor, *attrs)
@@ -244,15 +244,22 @@ module ActiveRecord
244
244
  def writer_method(name, class_name, mapping, allow_nil, converter)
245
245
  define_method("#{name}=") do |part|
246
246
  klass = class_name.constantize
247
+
247
248
  unless part.is_a?(klass) || converter.nil? || part.nil?
248
249
  part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
249
250
  end
250
251
 
252
+ hash_from_multiparameter_assignment = part.is_a?(Hash) &&
253
+ part.each_key.all? { |k| k.is_a?(Integer) }
254
+ if hash_from_multiparameter_assignment
255
+ part = klass.new(*part.values)
256
+ end
257
+
251
258
  if part.nil? && allow_nil
252
- mapping.each { |pair| self[pair.first] = nil }
259
+ mapping.each { |key, _| self[key] = nil }
253
260
  @aggregation_cache[name] = nil
254
261
  else
255
- mapping.each { |pair| self[pair.first] = part.send(pair.last) }
262
+ mapping.each { |key, value| self[key] = part.send(value) }
256
263
  @aggregation_cache[name] = part.freeze
257
264
  end
258
265
  end
@@ -13,6 +13,19 @@ module ActiveRecord
13
13
  other == to_a
14
14
  end
15
15
 
16
+ def build(*args, &block)
17
+ scoping { @association.build(*args, &block) }
18
+ end
19
+ alias new build
20
+
21
+ def create(*args, &block)
22
+ scoping { @association.create(*args, &block) }
23
+ end
24
+
25
+ def create!(*args, &block)
26
+ scoping { @association.create!(*args, &block) }
27
+ end
28
+
16
29
  private
17
30
 
18
31
  def exec_queries
@@ -57,20 +57,10 @@ module ActiveRecord
57
57
  end
58
58
 
59
59
  def aliased_table_for(table_name, aliased_name)
60
- table_alias = aliased_name_for(table_name, aliased_name)
61
-
62
- if table_alias == table_name
63
- Arel::Table.new(table_name)
64
- else
65
- Arel::Table.new(table_name).alias(table_alias)
66
- end
67
- end
68
-
69
- def aliased_name_for(table_name, aliased_name)
70
60
  if aliases[table_name].zero?
71
61
  # If it's zero, we can have our table_name
72
62
  aliases[table_name] = 1
73
- table_name
63
+ Arel::Table.new(table_name)
74
64
  else
75
65
  # Otherwise, we need to use an alias
76
66
  aliased_name = connection.table_alias_for(aliased_name)
@@ -78,11 +68,12 @@ module ActiveRecord
78
68
  # Update the count
79
69
  aliases[aliased_name] += 1
80
70
 
81
- if aliases[aliased_name] > 1
71
+ table_alias = if aliases[aliased_name] > 1
82
72
  "#{truncate(aliased_name)}_#{aliases[aliased_name]}"
83
73
  else
84
74
  aliased_name
85
75
  end
76
+ Arel::Table.new(table_name).alias(table_alias)
86
77
  end
87
78
  end
88
79
 
@@ -179,7 +179,7 @@ module ActiveRecord
179
179
  def creation_attributes
180
180
  attributes = {}
181
181
 
182
- if (reflection.macro == :has_one || reflection.macro == :has_many) && !options[:through]
182
+ if (reflection.has_one? || reflection.collection?) && !options[:through]
183
183
  attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
184
184
 
185
185
  if reflection.options[:as]
@@ -211,9 +211,12 @@ module ActiveRecord
211
211
  # the kind of the class of the associated objects. Meant to be used as
212
212
  # a sanity check when you are about to assign an associated record.
213
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
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
217
220
  end
218
221
  end
219
222
 
@@ -248,6 +251,15 @@ module ActiveRecord
248
251
  initialize_attributes(record)
249
252
  end
250
253
  end
254
+
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?
262
+ end
251
263
  end
252
264
  end
253
265
  end
@@ -1,12 +1,33 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
3
  class AssociationScope #:nodoc:
4
- INSTANCE = new
5
-
6
4
  def self.scope(association, connection)
7
5
  INSTANCE.scope association, connection
8
6
  end
9
7
 
8
+ class BindSubstitution
9
+ def initialize(block)
10
+ @block = block
11
+ end
12
+
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
19
+
20
+ def self.create(&block)
21
+ block = block ? block : lambda { |val| val }
22
+ new BindSubstitution.new(block)
23
+ end
24
+
25
+ def initialize(bind_substitution)
26
+ @bind_substitution = bind_substitution
27
+ end
28
+
29
+ INSTANCE = create
30
+
10
31
  def scope(association, connection)
11
32
  klass = association.klass
12
33
  reflection = association.reflection
@@ -22,6 +43,23 @@ module ActiveRecord
22
43
  Arel::Nodes::InnerJoin
23
44
  end
24
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
61
+ end
62
+
25
63
  private
26
64
 
27
65
  def construct_tables(chain, klass, refl, alias_tracker)
@@ -49,10 +87,7 @@ module ActiveRecord
49
87
  end
50
88
 
51
89
  def bind_value(scope, column, value, alias_tracker)
52
- substitute = alias_tracker.connection.substitute_at(
53
- column, scope.bind_values.length)
54
- scope.bind_values += [[column, value]]
55
- substitute
90
+ @bind_substitution.bind_value scope, column, value, alias_tracker
56
91
  end
57
92
 
58
93
  def bind(scope, table_name, column_name, value, tracker)
@@ -60,47 +95,55 @@ module ActiveRecord
60
95
  bind_value scope, column, value, tracker
61
96
  end
62
97
 
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
102
+
103
+ bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
104
+ scope = scope.where(table[key].eq(bind_val))
105
+
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
114
+
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
119
+
120
+ constraint = table[key].eq(foreign_table[foreign_key])
121
+
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
127
+
128
+ scope = scope.joins(join(foreign_table, constraint))
129
+ end
130
+
63
131
  def add_constraints(scope, owner, assoc_klass, refl, tracker)
64
132
  chain = refl.chain
65
133
  scope_chain = refl.scope_chain
66
134
 
67
135
  tables = construct_tables(chain, assoc_klass, refl, tracker)
68
136
 
137
+ owner_reflection = chain.last
138
+ table = tables.last
139
+ scope = last_chain_scope(scope, table, owner_reflection, owner, tracker, assoc_klass)
140
+
69
141
  chain.each_with_index do |reflection, i|
70
142
  table, foreign_table = tables.shift, tables.first
71
143
 
72
- if reflection.source_macro == :belongs_to
73
- if reflection.options[:polymorphic]
74
- key = reflection.association_primary_key(assoc_klass)
75
- else
76
- key = reflection.association_primary_key
77
- end
78
-
79
- foreign_key = reflection.foreign_key
80
- else
81
- key = reflection.foreign_key
82
- foreign_key = reflection.active_record_primary_key
83
- end
84
-
85
- if reflection == chain.last
86
- bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
87
- scope = scope.where(table[key].eq(bind_val))
88
-
89
- if reflection.type
90
- value = owner.class.base_class.name
91
- bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker
92
- scope = scope.where(table[reflection.type].eq(bind_val))
93
- end
94
- else
95
- constraint = table[key].eq(foreign_table[foreign_key])
96
-
97
- if reflection.type
98
- value = chain[i + 1].klass.base_class.name
99
- bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker
100
- scope = scope.where(table[reflection.type].eq(bind_val))
101
- end
102
-
103
- scope = scope.joins(join(foreign_table, constraint))
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)
104
147
  end
105
148
 
106
149
  is_first_chain = i == 0
@@ -119,7 +162,9 @@ module ActiveRecord
119
162
  scope.includes! item.includes_values
120
163
  end
121
164
 
165
+ scope.unscope!(*item.unscope_values)
122
166
  scope.where_values += item.where_values
167
+ scope.bind_values += item.bind_values
123
168
  scope.order_values |= item.order_values
124
169
  end
125
170
  end
@@ -143,11 +188,7 @@ module ActiveRecord
143
188
  end
144
189
 
145
190
  def eval_scope(klass, scope, owner)
146
- if scope.is_a?(Relation)
147
- scope
148
- else
149
- klass.unscoped.instance_exec(owner, &scope)
150
- end
191
+ klass.unscoped.instance_exec(owner, &scope)
151
192
  end
152
193
  end
153
194
  end
@@ -31,6 +31,14 @@ module ActiveRecord
31
31
  @updated
32
32
  end
33
33
 
34
+ def decrement_counters # :nodoc:
35
+ with_cache_name { |name| decrement_counter name }
36
+ end
37
+
38
+ def increment_counters # :nodoc:
39
+ with_cache_name { |name| increment_counter name }
40
+ end
41
+
34
42
  private
35
43
 
36
44
  def find_target?
@@ -51,23 +59,28 @@ module ActiveRecord
51
59
  end
52
60
  end
53
61
 
54
- def decrement_counters
55
- with_cache_name { |name| decrement_counter name }
62
+ def decrement_counter(counter_cache_name)
63
+ if foreign_key_present?
64
+ klass.decrement_counter(counter_cache_name, target_id)
65
+ end
56
66
  end
57
67
 
58
- def decrement_counter counter_cache_name
68
+ def increment_counter(counter_cache_name)
59
69
  if foreign_key_present?
60
- klass.decrement_counter(counter_cache_name, target_id)
70
+ klass.increment_counter(counter_cache_name, target_id)
71
+ if target && !stale_target? && counter_cache_available_in_memory?(counter_cache_name)
72
+ target.increment(counter_cache_name)
73
+ end
61
74
  end
62
75
  end
63
76
 
64
77
  # Checks whether record is different to the current target, without loading it
65
78
  def different_target?(record)
66
- record.id != owner[reflection.foreign_key]
79
+ record.id != owner._read_attribute(reflection.foreign_key)
67
80
  end
68
81
 
69
82
  def replace_keys(record)
70
- owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
83
+ owner[reflection.foreign_key] = record._read_attribute(reflection.association_primary_key(record.class))
71
84
  end
72
85
 
73
86
  def remove_keys
@@ -75,26 +88,31 @@ module ActiveRecord
75
88
  end
76
89
 
77
90
  def foreign_key_present?
78
- owner[reflection.foreign_key]
91
+ owner._read_attribute(reflection.foreign_key)
79
92
  end
80
93
 
81
94
  # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
82
95
  # has_one associations.
83
96
  def invertible_for?(record)
84
97
  inverse = inverse_reflection_for(record)
85
- inverse && inverse.macro == :has_one
98
+ inverse && inverse.has_one?
86
99
  end
87
100
 
88
101
  def target_id
89
102
  if options[:primary_key]
90
103
  owner.send(reflection.name).try(:id)
91
104
  else
92
- owner[reflection.foreign_key]
105
+ owner._read_attribute(reflection.foreign_key)
93
106
  end
94
107
  end
95
108
 
96
109
  def stale_state
97
- owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
110
+ result = owner._read_attribute(reflection.foreign_key)
111
+ result && result.to_s
112
+ end
113
+
114
+ def counter_cache_available_in_memory?(counter_cache_name)
115
+ target.respond_to?(counter_cache_name)
98
116
  end
99
117
  end
100
118
  end
@@ -21,7 +21,7 @@ module ActiveRecord::Associations::Builder
21
21
  end
22
22
  self.extensions = []
23
23
 
24
- self.valid_options = [:class_name, :class, :foreign_key, :validate]
24
+ self.valid_options = [:class_name, :anonymous_class, :foreign_key, :validate]
25
25
 
26
26
  attr_reader :name, :scope, :options
27
27
 
@@ -36,6 +36,7 @@ module ActiveRecord::Associations::Builder
36
36
  reflection = builder.build(model)
37
37
  define_accessors model, reflection
38
38
  define_callbacks model, reflection
39
+ define_validations model, reflection
39
40
  builder.define_extensions model
40
41
  reflection
41
42
  end
@@ -85,7 +86,11 @@ module ActiveRecord::Associations::Builder
85
86
  end
86
87
 
87
88
  def self.define_callbacks(model, reflection)
88
- add_before_destroy_callbacks(model, reflection) if reflection.options[:dependent]
89
+ if dependent = reflection.options[:dependent]
90
+ check_dependent_options(dependent)
91
+ add_destroy_callbacks(model, reflection)
92
+ end
93
+
89
94
  Association.extensions.each do |extension|
90
95
  extension.build model, reflection
91
96
  end
@@ -120,17 +125,23 @@ module ActiveRecord::Associations::Builder
120
125
  CODE
121
126
  end
122
127
 
128
+ def self.define_validations(model, reflection)
129
+ # noop
130
+ end
131
+
123
132
  def self.valid_dependent_options
124
133
  raise NotImplementedError
125
134
  end
126
135
 
127
136
  private
128
137
 
129
- def self.add_before_destroy_callbacks(model, reflection)
130
- unless valid_dependent_options.include? reflection.options[:dependent]
131
- raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{reflection.options[:dependent]}"
138
+ def self.check_dependent_options(dependent)
139
+ unless valid_dependent_options.include? dependent
140
+ raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
132
141
  end
142
+ end
133
143
 
144
+ def self.add_destroy_callbacks(model, reflection)
134
145
  name = reflection.name
135
146
  model.before_destroy lambda { |o| o.association(name).handle_dependency }
136
147
  end
@@ -26,28 +26,9 @@ module ActiveRecord::Associations::Builder
26
26
  private
27
27
 
28
28
  def self.add_counter_cache_methods(mixin)
29
- return if mixin.method_defined? :belongs_to_counter_cache_after_create
29
+ return if mixin.method_defined? :belongs_to_counter_cache_after_update
30
30
 
31
31
  mixin.class_eval do
32
- def belongs_to_counter_cache_after_create(reflection)
33
- if record = send(reflection.name)
34
- cache_column = reflection.counter_cache_column
35
- record.class.increment_counter(cache_column, record.id)
36
- @_after_create_counter_called = true
37
- end
38
- end
39
-
40
- def belongs_to_counter_cache_before_destroy(reflection)
41
- foreign_key = reflection.foreign_key.to_sym
42
- unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
43
- record = send reflection.name
44
- if record && !self.destroyed?
45
- cache_column = reflection.counter_cache_column
46
- record.class.decrement_counter(cache_column, record.id)
47
- end
48
- end
49
- end
50
-
51
32
  def belongs_to_counter_cache_after_update(reflection)
52
33
  foreign_key = reflection.foreign_key
53
34
  cache_column = reflection.counter_cache_column
@@ -73,14 +54,6 @@ module ActiveRecord::Associations::Builder
73
54
  def self.add_counter_cache_callbacks(model, reflection)
74
55
  cache_column = reflection.counter_cache_column
75
56
 
76
- model.after_create lambda { |record|
77
- record.belongs_to_counter_cache_after_create(reflection)
78
- }
79
-
80
- model.before_destroy lambda { |record|
81
- record.belongs_to_counter_cache_before_destroy(reflection)
82
- }
83
-
84
57
  model.after_update lambda { |record|
85
58
  record.belongs_to_counter_cache_after_update(reflection)
86
59
  }
@@ -130,9 +103,14 @@ module ActiveRecord::Associations::Builder
130
103
  BelongsTo.touch_record(record, foreign_key, n, touch)
131
104
  }
132
105
 
133
- model.after_save callback
106
+ model.after_save callback, if: :changed?
134
107
  model.after_touch callback
135
108
  model.after_destroy callback
136
109
  end
110
+
111
+ def self.add_destroy_callbacks(model, reflection)
112
+ name = reflection.name
113
+ model.after_destroy lambda { |o| o.association(name).handle_dependency }
114
+ end
137
115
  end
138
116
  end
@@ -82,7 +82,11 @@ module ActiveRecord::Associations::Builder
82
82
 
83
83
  def wrap_scope(scope, mod)
84
84
  if scope
85
- proc { |owner| instance_exec(owner, &scope).extending(mod) }
85
+ if scope.arity > 0
86
+ proc { |owner| instance_exec(owner, &scope).extending(mod) }
87
+ else
88
+ proc { instance_exec(&scope).extending(mod) }
89
+ end
86
90
  else
87
91
  proc { extending(mod) }
88
92
  end
@@ -11,7 +11,7 @@ module ActiveRecord::Associations::Builder
11
11
  end
12
12
 
13
13
  def join_table
14
- @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
14
+ @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
15
15
  end
16
16
 
17
17
  private
@@ -46,7 +46,7 @@ module ActiveRecord::Associations::Builder
46
46
 
47
47
  join_model = Class.new(ActiveRecord::Base) {
48
48
  class << self;
49
- attr_accessor :class_resolver
49
+ attr_accessor :left_model
50
50
  attr_accessor :name
51
51
  attr_accessor :table_name_resolver
52
52
  attr_accessor :left_reflection
@@ -58,7 +58,7 @@ module ActiveRecord::Associations::Builder
58
58
  end
59
59
 
60
60
  def self.compute_type(class_name)
61
- class_resolver.compute_type class_name
61
+ left_model.compute_type class_name
62
62
  end
63
63
 
64
64
  def self.add_left_association(name, options)
@@ -72,22 +72,17 @@ module ActiveRecord::Associations::Builder
72
72
  self.right_reflection = _reflect_on_association(rhs_name)
73
73
  end
74
74
 
75
- def hash
76
- object_id.hash
75
+ def self.retrieve_connection
76
+ left_model.retrieve_connection
77
77
  end
78
78
 
79
- def ==(other)
80
- equal?(other)
81
- end
82
- alias :eql? :==
83
-
84
79
  }
85
80
 
86
81
  join_model.name = "HABTM_#{association_name.to_s.camelize}"
87
82
  join_model.table_name_resolver = habtm
88
- join_model.class_resolver = lhs_model
83
+ join_model.left_model = lhs_model
89
84
 
90
- join_model.add_left_association :left_side, class: lhs_model
85
+ join_model.add_left_association :left_side, anonymous_class: lhs_model
91
86
  join_model.add_right_association association_name, belongs_to_options(options)
92
87
  join_model
93
88
  end
@@ -107,7 +102,7 @@ module ActiveRecord::Associations::Builder
107
102
 
108
103
  def middle_options(join_model)
109
104
  middle_options = {}
110
- middle_options[:class] = join_model
105
+ middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}"
111
106
  middle_options[:source] = join_model.left_reflection.name
112
107
  if options.key? :foreign_key
113
108
  middle_options[:foreign_key] = options[:foreign_key]
@@ -119,7 +114,7 @@ module ActiveRecord::Associations::Builder
119
114
  rhs_options = {}
120
115
 
121
116
  if options.key? :class_name
122
- rhs_options[:foreign_key] = options[:class_name].foreign_key
117
+ rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key
123
118
  rhs_options[:class_name] = options[:class_name]
124
119
  end
125
120