activerecord 4.1.15 → 4.2.11.3

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

Potentially problematic release.


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

Files changed (185) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1162 -1792
  3. data/README.rdoc +15 -10
  4. data/lib/active_record.rb +4 -0
  5. data/lib/active_record/aggregations.rb +15 -8
  6. data/lib/active_record/association_relation.rb +13 -0
  7. data/lib/active_record/associations.rb +158 -49
  8. data/lib/active_record/associations/alias_tracker.rb +3 -12
  9. data/lib/active_record/associations/association.rb +16 -4
  10. data/lib/active_record/associations/association_scope.rb +83 -38
  11. data/lib/active_record/associations/belongs_to_association.rb +28 -10
  12. data/lib/active_record/associations/builder/association.rb +15 -4
  13. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  14. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -13
  16. data/lib/active_record/associations/builder/has_many.rb +1 -1
  17. data/lib/active_record/associations/builder/has_one.rb +2 -2
  18. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  19. data/lib/active_record/associations/collection_association.rb +63 -27
  20. data/lib/active_record/associations/collection_proxy.rb +29 -35
  21. data/lib/active_record/associations/foreign_association.rb +11 -0
  22. data/lib/active_record/associations/has_many_association.rb +83 -22
  23. data/lib/active_record/associations/has_many_through_association.rb +49 -26
  24. data/lib/active_record/associations/has_one_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +26 -13
  26. data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
  27. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  28. data/lib/active_record/associations/preloader.rb +36 -26
  29. data/lib/active_record/associations/preloader/association.rb +14 -11
  30. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  31. data/lib/active_record/associations/singular_association.rb +17 -2
  32. data/lib/active_record/associations/through_association.rb +5 -12
  33. data/lib/active_record/attribute.rb +163 -0
  34. data/lib/active_record/attribute_assignment.rb +19 -11
  35. data/lib/active_record/attribute_decorators.rb +66 -0
  36. data/lib/active_record/attribute_methods.rb +56 -94
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  38. data/lib/active_record/attribute_methods/dirty.rb +107 -43
  39. data/lib/active_record/attribute_methods/primary_key.rb +7 -8
  40. data/lib/active_record/attribute_methods/query.rb +1 -1
  41. data/lib/active_record/attribute_methods/read.rb +22 -59
  42. data/lib/active_record/attribute_methods/serialization.rb +16 -150
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -40
  44. data/lib/active_record/attribute_methods/write.rb +9 -24
  45. data/lib/active_record/attribute_set.rb +81 -0
  46. data/lib/active_record/attribute_set/builder.rb +106 -0
  47. data/lib/active_record/attributes.rb +147 -0
  48. data/lib/active_record/autosave_association.rb +19 -12
  49. data/lib/active_record/base.rb +13 -24
  50. data/lib/active_record/callbacks.rb +6 -6
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +84 -52
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
  55. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +138 -56
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +268 -71
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +171 -59
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +293 -139
  63. data/lib/active_record/connection_adapters/column.rb +29 -240
  64. data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +67 -144
  67. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  68. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  69. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
  70. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  71. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  96. data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
  97. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  99. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +131 -43
  100. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  101. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
  102. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  103. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
  104. data/lib/active_record/connection_handling.rb +1 -1
  105. data/lib/active_record/core.rb +163 -39
  106. data/lib/active_record/counter_cache.rb +60 -6
  107. data/lib/active_record/enum.rb +9 -11
  108. data/lib/active_record/errors.rb +53 -30
  109. data/lib/active_record/explain.rb +1 -1
  110. data/lib/active_record/explain_subscriber.rb +1 -1
  111. data/lib/active_record/fixtures.rb +55 -69
  112. data/lib/active_record/gem_version.rb +4 -4
  113. data/lib/active_record/inheritance.rb +35 -10
  114. data/lib/active_record/integration.rb +4 -4
  115. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  116. data/lib/active_record/locking/optimistic.rb +46 -26
  117. data/lib/active_record/migration.rb +71 -46
  118. data/lib/active_record/migration/command_recorder.rb +19 -2
  119. data/lib/active_record/migration/join_table.rb +1 -1
  120. data/lib/active_record/model_schema.rb +52 -58
  121. data/lib/active_record/nested_attributes.rb +5 -5
  122. data/lib/active_record/no_touching.rb +1 -1
  123. data/lib/active_record/persistence.rb +46 -26
  124. data/lib/active_record/query_cache.rb +3 -3
  125. data/lib/active_record/querying.rb +10 -7
  126. data/lib/active_record/railtie.rb +18 -11
  127. data/lib/active_record/railties/databases.rake +50 -51
  128. data/lib/active_record/readonly_attributes.rb +0 -1
  129. data/lib/active_record/reflection.rb +273 -114
  130. data/lib/active_record/relation.rb +57 -25
  131. data/lib/active_record/relation/batches.rb +0 -2
  132. data/lib/active_record/relation/calculations.rb +41 -37
  133. data/lib/active_record/relation/finder_methods.rb +70 -47
  134. data/lib/active_record/relation/merger.rb +39 -29
  135. data/lib/active_record/relation/predicate_builder.rb +16 -8
  136. data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
  137. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -5
  138. data/lib/active_record/relation/query_methods.rb +114 -65
  139. data/lib/active_record/relation/spawn_methods.rb +3 -0
  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 -6
  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 +53 -27
  155. data/lib/active_record/type.rb +23 -0
  156. data/lib/active_record/type/big_integer.rb +13 -0
  157. data/lib/active_record/type/binary.rb +50 -0
  158. data/lib/active_record/type/boolean.rb +31 -0
  159. data/lib/active_record/type/date.rb +50 -0
  160. data/lib/active_record/type/date_time.rb +54 -0
  161. data/lib/active_record/type/decimal.rb +64 -0
  162. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  163. data/lib/active_record/type/decorator.rb +14 -0
  164. data/lib/active_record/type/float.rb +19 -0
  165. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  166. data/lib/active_record/type/integer.rb +59 -0
  167. data/lib/active_record/type/mutable.rb +16 -0
  168. data/lib/active_record/type/numeric.rb +36 -0
  169. data/lib/active_record/type/serialized.rb +62 -0
  170. data/lib/active_record/type/string.rb +40 -0
  171. data/lib/active_record/type/text.rb +11 -0
  172. data/lib/active_record/type/time.rb +26 -0
  173. data/lib/active_record/type/time_value.rb +38 -0
  174. data/lib/active_record/type/type_map.rb +64 -0
  175. data/lib/active_record/type/unsigned_integer.rb +15 -0
  176. data/lib/active_record/type/value.rb +110 -0
  177. data/lib/active_record/validations.rb +25 -19
  178. data/lib/active_record/validations/associated.rb +5 -3
  179. data/lib/active_record/validations/presence.rb +5 -3
  180. data/lib/active_record/validations/uniqueness.rb +25 -29
  181. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  182. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +66 -11
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -0,0 +1,11 @@
1
+ module ActiveRecord::Associations
2
+ module ForeignAssociation
3
+ def foreign_key_present?
4
+ if reflection.klass.primary_key
5
+ owner.attribute_present?(reflection.active_record_primary_key)
6
+ else
7
+ false
8
+ end
9
+ end
10
+ end
11
+ end
@@ -6,6 +6,7 @@ module ActiveRecord
6
6
  # If the association has a <tt>:through</tt> option further specialization
7
7
  # is provided by its child HasManyThroughAssociation.
8
8
  class HasManyAssociation < CollectionAssociation #:nodoc:
9
+ include ForeignAssociation
9
10
 
10
11
  def handle_dependency
11
12
  case options[:dependent]
@@ -41,6 +42,14 @@ module ActiveRecord
41
42
  end
42
43
  end
43
44
 
45
+ def empty?
46
+ if has_cached_counter?
47
+ size.zero?
48
+ else
49
+ super
50
+ end
51
+ end
52
+
44
53
  private
45
54
 
46
55
  # Returns the number of records in this collection.
@@ -58,7 +67,7 @@ module ActiveRecord
58
67
  # the loaded flag is set to true as well.
59
68
  def count_records
60
69
  count = if has_cached_counter?
61
- owner.send(:read_attribute, cached_counter_attribute_name)
70
+ owner._read_attribute cached_counter_attribute_name
62
71
  else
63
72
  scope.count
64
73
  end
@@ -71,20 +80,42 @@ module ActiveRecord
71
80
  [association_scope.limit_value, count].compact.min
72
81
  end
73
82
 
83
+
84
+ # Returns whether a counter cache should be used for this association.
85
+ #
86
+ # The counter_cache option must be given on either the owner or inverse
87
+ # association, and the column must be present on the owner.
74
88
  def has_cached_counter?(reflection = reflection())
75
- owner.attribute_present?(cached_counter_attribute_name(reflection))
89
+ if reflection.options[:counter_cache] || (inverse = inverse_which_updates_counter_cache(reflection)) && inverse.options[:counter_cache]
90
+ owner.attribute_present?(cached_counter_attribute_name(reflection))
91
+ end
76
92
  end
77
93
 
78
94
  def cached_counter_attribute_name(reflection = reflection())
79
- options[:counter_cache] || "#{reflection.name}_count"
95
+ if reflection.options[:counter_cache]
96
+ reflection.options[:counter_cache].to_s
97
+ else
98
+ "#{reflection.name}_count"
99
+ end
80
100
  end
81
101
 
82
102
  def update_counter(difference, reflection = reflection())
103
+ update_counter_in_database(difference, reflection)
104
+ update_counter_in_memory(difference, reflection)
105
+ end
106
+
107
+ def update_counter_in_database(difference, reflection = reflection())
83
108
  if has_cached_counter?(reflection)
84
109
  counter = cached_counter_attribute_name(reflection)
85
110
  owner.class.update_counters(owner.id, counter => difference)
111
+ end
112
+ end
113
+
114
+ def update_counter_in_memory(difference, reflection = reflection())
115
+ if counter_must_be_updated_by_has_many?(reflection)
116
+ counter = cached_counter_attribute_name(reflection)
86
117
  owner[counter] += difference
87
- owner.changed_attributes.delete(counter) # eww
118
+ owner.send(:clear_attribute_changes, counter) # eww
88
119
  end
89
120
  end
90
121
 
@@ -98,13 +129,41 @@ module ActiveRecord
98
129
  # it will be decremented twice.
99
130
  #
100
131
  # Hence this method.
101
- def inverse_updates_counter_cache?(reflection = reflection())
132
+ def inverse_which_updates_counter_cache(reflection = reflection())
102
133
  counter_name = cached_counter_attribute_name(reflection)
103
- reflection.klass._reflections.values.any? { |inverse_reflection|
104
- :belongs_to == inverse_reflection.macro &&
134
+ inverse_which_updates_counter_named(counter_name, reflection)
135
+ end
136
+ alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
137
+
138
+ def inverse_which_updates_counter_named(counter_name, reflection)
139
+ reflection.klass._reflections.values.find { |inverse_reflection|
140
+ inverse_reflection.belongs_to? &&
105
141
  inverse_reflection.counter_cache_column == counter_name
106
142
  }
107
143
  end
144
+ alias inverse_updates_counter_named? inverse_which_updates_counter_named
145
+
146
+ def inverse_updates_counter_in_memory?(reflection)
147
+ inverse = inverse_which_updates_counter_cache(reflection)
148
+ inverse && inverse == reflection.inverse_of
149
+ end
150
+
151
+ def counter_must_be_updated_by_has_many?(reflection)
152
+ !inverse_updates_counter_in_memory?(reflection) && has_cached_counter?(reflection)
153
+ end
154
+
155
+ def delete_count(method, scope)
156
+ if method == :delete_all
157
+ scope.delete_all
158
+ else
159
+ scope.update_all(reflection.foreign_key => nil)
160
+ end
161
+ end
162
+
163
+ def delete_or_nullify_all_records(method)
164
+ count = delete_count(method, self.scope)
165
+ update_counter(-count)
166
+ end
108
167
 
109
168
  # Deletes the records according to the <tt>:dependent</tt> option.
110
169
  def delete_records(records, method)
@@ -112,26 +171,28 @@ module ActiveRecord
112
171
  records.each(&:destroy!)
113
172
  update_counter(-records.length) unless inverse_updates_counter_cache?
114
173
  else
115
- if records == :all || !reflection.klass.primary_key
116
- scope = self.scope
117
- else
118
- scope = self.scope.where(reflection.klass.primary_key => records)
119
- end
120
-
121
- if method == :delete_all
122
- update_counter(-scope.delete_all)
123
- else
124
- update_counter(-scope.update_all(reflection.foreign_key => nil))
125
- end
174
+ scope = self.scope.where(reflection.klass.primary_key => records)
175
+ update_counter(-delete_count(method, scope))
126
176
  end
127
177
  end
128
178
 
129
- def foreign_key_present?
130
- if reflection.klass.primary_key
131
- owner.attribute_present?(reflection.association_primary_key)
179
+ def concat_records(records, *)
180
+ update_counter_if_success(super, records.length)
181
+ end
182
+
183
+ def _create_record(attributes, *)
184
+ if attributes.is_a?(Array)
185
+ super
132
186
  else
133
- false
187
+ update_counter_if_success(super, 1)
188
+ end
189
+ end
190
+
191
+ def update_counter_if_success(saved_successfully, difference)
192
+ if saved_successfully
193
+ update_counter_in_memory(difference)
134
194
  end
195
+ saved_successfully
135
196
  end
136
197
  end
137
198
  end
@@ -1,3 +1,4 @@
1
+ require 'active_support/core_ext/string/filters'
1
2
 
2
3
  module ActiveRecord
3
4
  # = Active Record Has Many Through Association
@@ -12,13 +13,14 @@ module ActiveRecord
12
13
  @through_association = nil
13
14
  end
14
15
 
15
- # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
16
- # loaded and calling collection.size if it has. If it's more likely than not that the collection does
17
- # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
18
- # SELECT query if you use #length.
16
+ # Returns the size of the collection by executing a SELECT COUNT(*) query
17
+ # if the collection hasn't been loaded, and by calling collection.size if
18
+ # it has. If the collection will likely have a size greater than zero,
19
+ # and if fetching the collection will be needed afterwards, one less
20
+ # SELECT query will be generated by using #length instead.
19
21
  def size
20
22
  if has_cached_counter?
21
- owner.send(:read_attribute, cached_counter_attribute_name)
23
+ owner._read_attribute cached_counter_attribute_name(reflection)
22
24
  elsif loaded?
23
25
  target.size
24
26
  else
@@ -62,7 +64,16 @@ module ActiveRecord
62
64
  end
63
65
 
64
66
  save_through_record(record)
65
- update_counter(1)
67
+ if has_cached_counter? && !through_reflection_updates_counter_cache?
68
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
69
+ Automatic updating of counter caches on through associations has been
70
+ deprecated, and will be removed in Rails 5. Instead, please set the
71
+ appropriate `counter_cache` options on the `has_many` and `belongs_to`
72
+ for your associations to #{through_reflection.name}.
73
+ MSG
74
+
75
+ update_counter_in_database(1)
76
+ end
66
77
  record
67
78
  end
68
79
 
@@ -72,19 +83,22 @@ module ActiveRecord
72
83
  @through_association ||= owner.association(through_reflection.name)
73
84
  end
74
85
 
75
- # We temporarily cache through record that has been build, because if we build a
76
- # through record in build_record and then subsequently call insert_record, then we
77
- # want to use the exact same object.
86
+ # The through record (built with build_record) is temporarily cached
87
+ # so that it may be reused if insert_record is subsequently called.
78
88
  #
79
- # However, after insert_record has been called, we clear the cache entry because
80
- # we want it to be possible to have multiple instances of the same record in an
81
- # association
89
+ # However, after insert_record has been called, the cache is cleared in
90
+ # order to allow multiple instances of the same record in an association.
82
91
  def build_through_record(record)
83
92
  @through_records[record.object_id] ||= begin
84
93
  ensure_mutable
85
94
 
86
95
  through_record = through_association.build(*options_for_through_record)
87
96
  through_record.send("#{source_reflection.name}=", record)
97
+
98
+ if options[:source_type]
99
+ through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
100
+ end
101
+
88
102
  through_record
89
103
  end
90
104
  end
@@ -112,9 +126,9 @@ module ActiveRecord
112
126
 
113
127
  inverse = source_reflection.inverse_of
114
128
  if inverse
115
- if inverse.macro == :has_many
129
+ if inverse.collection?
116
130
  record.send(inverse.name) << build_through_record(record)
117
- elsif inverse.macro == :has_one
131
+ elsif inverse.has_one?
118
132
  record.send("#{inverse.name}=", build_through_record(record))
119
133
  end
120
134
  end
@@ -123,7 +137,7 @@ module ActiveRecord
123
137
  end
124
138
 
125
139
  def target_reflection_has_associated_record?
126
- !(through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?)
140
+ !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
127
141
  end
128
142
 
129
143
  def update_through_counter?(method)
@@ -137,13 +151,13 @@ module ActiveRecord
137
151
  end
138
152
  end
139
153
 
154
+ def delete_or_nullify_all_records(method)
155
+ delete_records(load_target, method)
156
+ end
157
+
140
158
  def delete_records(records, method)
141
159
  ensure_not_nested
142
160
 
143
- # This is unoptimised; it will load all the target records
144
- # even when we just want to delete everything.
145
- records = load_target if records == :all
146
-
147
161
  scope = through_association.scope
148
162
  scope.where! construct_join_attributes(*records)
149
163
 
@@ -152,8 +166,8 @@ module ActiveRecord
152
166
  if scope.klass.primary_key
153
167
  count = scope.destroy_all.length
154
168
  else
155
- scope.to_a.each do |record|
156
- record.run_callbacks :destroy
169
+ scope.each do |record|
170
+ record._run_destroy_callbacks
157
171
  end
158
172
 
159
173
  arel = scope.arel
@@ -177,11 +191,11 @@ module ActiveRecord
177
191
  klass.decrement_counter counter, records.map(&:id)
178
192
  end
179
193
 
180
- if through_reflection.macro == :has_many && update_through_counter?(method)
194
+ if through_reflection.collection? && update_through_counter?(method)
181
195
  update_counter(-count, through_reflection)
196
+ else
197
+ update_counter(-count)
182
198
  end
183
-
184
- update_counter(-count)
185
199
  end
186
200
 
187
201
  def through_records_for(record)
@@ -198,7 +212,7 @@ module ActiveRecord
198
212
  records.each do |record|
199
213
  through_records = through_records_for(record)
200
214
 
201
- if through_reflection.macro == :has_many
215
+ if through_reflection.collection?
202
216
  through_records.each { |r| through_association.target.delete(r) }
203
217
  else
204
218
  if through_records.include?(through_association.target)
@@ -212,13 +226,22 @@ module ActiveRecord
212
226
 
213
227
  def find_target
214
228
  return [] unless target_reflection_has_associated_record?
215
- scope.to_a
229
+ get_records
216
230
  end
217
231
 
218
232
  # NOTE - not sure that we can actually cope with inverses here
219
233
  def invertible_for?(record)
220
234
  false
221
235
  end
236
+
237
+ def has_cached_counter?(reflection = reflection())
238
+ owner.attribute_present?(cached_counter_attribute_name(reflection))
239
+ end
240
+
241
+ def through_reflection_updates_counter_cache?
242
+ counter_name = cached_counter_attribute_name
243
+ inverse_updates_counter_named?(counter_name, through_reflection)
244
+ end
222
245
  end
223
246
  end
224
247
  end
@@ -1,8 +1,8 @@
1
-
2
1
  module ActiveRecord
3
2
  # = Active Record Belongs To Has One Association
4
3
  module Associations
5
4
  class HasOneAssociation < SingularAssociation #:nodoc:
5
+ include ForeignAssociation
6
6
 
7
7
  def handle_dependency
8
8
  case options[:dependent]
@@ -94,7 +94,7 @@ module ActiveRecord
94
94
  #
95
95
  def initialize(base, associations, joins)
96
96
  @alias_tracker = AliasTracker.create(base.connection, joins)
97
- @alias_tracker.aliased_name_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
97
+ @alias_tracker.aliased_table_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
98
98
  tree = self.class.make_tree associations
99
99
  @join_root = JoinBase.new base, build(tree, base)
100
100
  @join_root.children.each { |child| construct_tables! @join_root, child }
@@ -131,7 +131,6 @@ module ActiveRecord
131
131
 
132
132
  def instantiate(result_set, aliases)
133
133
  primary_key = aliases.column_alias(join_root, join_root.primary_key)
134
- type_caster = result_set.column_type primary_key
135
134
 
136
135
  seen = Hash.new { |h,parent_klass|
137
136
  h[parent_klass] = Hash.new { |i,parent_id|
@@ -143,13 +142,21 @@ module ActiveRecord
143
142
  parents = model_cache[join_root]
144
143
  column_aliases = aliases.column_aliases join_root
145
144
 
146
- result_set.each { |row_hash|
147
- parent_key = primary_key ? row_hash[primary_key] : row_hash
148
- primary_id = type_caster.type_cast parent_key
149
- parent = parents[primary_id] ||= join_root.instantiate(row_hash, column_aliases)
150
- construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
145
+ message_bus = ActiveSupport::Notifications.instrumenter
146
+
147
+ payload = {
148
+ record_count: result_set.length,
149
+ class_name: join_root.base_klass.name
151
150
  }
152
151
 
152
+ message_bus.instrument('instantiation.active_record', payload) do
153
+ result_set.each { |row_hash|
154
+ parent_key = primary_key ? row_hash[primary_key] : row_hash
155
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases)
156
+ construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
157
+ }
158
+ end
159
+
153
160
  parents.values
154
161
  end
155
162
 
@@ -165,17 +172,17 @@ module ActiveRecord
165
172
  def make_outer_joins(parent, child)
166
173
  tables = table_aliases_for(parent, child)
167
174
  join_type = Arel::Nodes::OuterJoin
168
- joins = make_constraints parent, child, tables, join_type
175
+ info = make_constraints parent, child, tables, join_type
169
176
 
170
- joins.concat child.children.flat_map { |c| make_outer_joins(child, c) }
177
+ [info] + child.children.flat_map { |c| make_outer_joins(child, c) }
171
178
  end
172
179
 
173
180
  def make_inner_joins(parent, child)
174
181
  tables = child.tables
175
182
  join_type = Arel::Nodes::InnerJoin
176
- joins = make_constraints parent, child, tables, join_type
183
+ info = make_constraints parent, child, tables, join_type
177
184
 
178
- joins.concat child.children.flat_map { |c| make_inner_joins(child, c) }
185
+ [info] + child.children.flat_map { |c| make_inner_joins(child, c) }
179
186
  end
180
187
 
181
188
  def table_aliases_for(parent, node)
@@ -216,8 +223,9 @@ module ActiveRecord
216
223
  associations.map do |name, right|
217
224
  reflection = find_reflection base_klass, name
218
225
  reflection.check_validity!
226
+ reflection.check_eager_loadable!
219
227
 
220
- if reflection.options[:polymorphic]
228
+ if reflection.polymorphic?
221
229
  raise EagerLoadPolymorphicError.new(reflection)
222
230
  end
223
231
 
@@ -226,6 +234,7 @@ module ActiveRecord
226
234
  end
227
235
 
228
236
  def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
237
+ return if ar_parent.nil?
229
238
  primary_id = ar_parent.id
230
239
 
231
240
  parent.children.each do |node|
@@ -242,7 +251,11 @@ module ActiveRecord
242
251
 
243
252
  key = aliases.column_alias(node, node.primary_key)
244
253
  id = row[key]
245
- next if id.nil?
254
+ if id.nil?
255
+ nil_association = ar_parent.association(node.reflection.name)
256
+ nil_association.loaded!
257
+ next
258
+ end
246
259
 
247
260
  model = seen[parent.base_klass][primary_id][node.base_klass][id]
248
261
 
@@ -21,8 +21,11 @@ module ActiveRecord
21
21
  super && reflection == other.reflection
22
22
  end
23
23
 
24
+ JoinInformation = Struct.new :joins, :binds
25
+
24
26
  def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
25
27
  joins = []
28
+ bind_values = []
26
29
  tables = tables.reverse
27
30
 
28
31
  scope_chain_index = 0
@@ -34,14 +37,9 @@ module ActiveRecord
34
37
  table = tables.shift
35
38
  klass = reflection.klass
36
39
 
37
- case reflection.source_macro
38
- when :belongs_to
39
- key = reflection.association_primary_key
40
- foreign_key = reflection.foreign_key
41
- else
42
- key = reflection.foreign_key
43
- foreign_key = reflection.active_record_primary_key
44
- end
40
+ join_keys = reflection.join_keys(klass)
41
+ key = join_keys.key
42
+ foreign_key = join_keys.foreign_key
45
43
 
46
44
  constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
47
45
 
@@ -54,27 +52,39 @@ module ActiveRecord
54
52
  end
55
53
  scope_chain_index += 1
56
54
 
57
- scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact
55
+ klass_scope =
56
+ if klass.current_scope && klass.current_scope.values.blank?
57
+ klass.unscoped
58
+ else
59
+ klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))
60
+ end
61
+ scope_chain_items.concat [klass_scope].compact
58
62
 
59
63
  rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
60
64
  left.merge right
61
65
  end
62
66
 
63
- if reflection.type
64
- constraint = constraint.and table[reflection.type].eq foreign_klass.base_class.name
65
- end
66
-
67
67
  if rel && !rel.arel.constraints.empty?
68
+ bind_values.concat rel.bind_values
68
69
  constraint = constraint.and rel.arel.constraints
69
70
  end
70
71
 
72
+ if reflection.type
73
+ value = foreign_klass.base_class.name
74
+ column = klass.columns_hash[reflection.type.to_s]
75
+
76
+ substitute = klass.connection.substitute_at(column)
77
+ bind_values.push [column, value]
78
+ constraint = constraint.and table[reflection.type].eq substitute
79
+ end
80
+
71
81
  joins << table.create_join(table, table.create_on(constraint), join_type)
72
82
 
73
83
  # The current table in this iteration becomes the foreign table in the next
74
84
  foreign_table, foreign_klass = table, klass
75
85
  end
76
86
 
77
- joins
87
+ JoinInformation.new joins, bind_values
78
88
  end
79
89
 
80
90
  # Builds equality condition.
@@ -86,7 +96,7 @@ module ActiveRecord
86
96
  # end
87
97
  #
88
98
  # If I execute `Physician.joins(:appointments).to_a` then
89
- # reflection # => #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
99
+ # klass # => Physician
90
100
  # table # => #<Arel::Table @name="appointments" ...>
91
101
  # key # => physician_id
92
102
  # foreign_table # => #<Arel::Table @name="physicians" ...>