activerecord 5.2.4.4 → 6.0.0.beta1

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 (240) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +300 -725
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +1 -1
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +2 -1
  7. data/lib/active_record/aggregations.rb +4 -2
  8. data/lib/active_record/associations.rb +16 -12
  9. data/lib/active_record/associations/association.rb +35 -19
  10. data/lib/active_record/associations/association_scope.rb +4 -6
  11. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  13. data/lib/active_record/associations/builder/belongs_to.rb +14 -50
  14. data/lib/active_record/associations/builder/collection_association.rb +3 -3
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
  16. data/lib/active_record/associations/collection_association.rb +11 -25
  17. data/lib/active_record/associations/collection_proxy.rb +32 -6
  18. data/lib/active_record/associations/foreign_association.rb +7 -0
  19. data/lib/active_record/associations/has_many_association.rb +1 -1
  20. data/lib/active_record/associations/has_many_through_association.rb +25 -18
  21. data/lib/active_record/associations/has_one_association.rb +28 -30
  22. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  23. data/lib/active_record/associations/join_dependency.rb +15 -20
  24. data/lib/active_record/associations/join_dependency/join_association.rb +11 -26
  25. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  26. data/lib/active_record/associations/preloader.rb +32 -29
  27. data/lib/active_record/associations/preloader/association.rb +1 -2
  28. data/lib/active_record/associations/singular_association.rb +2 -16
  29. data/lib/active_record/attribute_assignment.rb +7 -10
  30. data/lib/active_record/attribute_methods.rb +34 -56
  31. data/lib/active_record/attribute_methods/dirty.rb +64 -26
  32. data/lib/active_record/attribute_methods/primary_key.rb +8 -7
  33. data/lib/active_record/attribute_methods/read.rb +16 -48
  34. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  36. data/lib/active_record/attribute_methods/write.rb +15 -16
  37. data/lib/active_record/autosave_association.rb +7 -21
  38. data/lib/active_record/base.rb +2 -2
  39. data/lib/active_record/callbacks.rb +3 -17
  40. data/lib/active_record/collection_cache_key.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +13 -36
  42. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +25 -84
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -14
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +5 -11
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +15 -11
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +30 -13
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +0 -2
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +41 -27
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +81 -52
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +95 -31
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +65 -90
  53. data/lib/active_record/connection_adapters/connection_specification.rb +52 -42
  54. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +5 -9
  55. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -7
  56. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  57. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
  58. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +65 -10
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -4
  60. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
  61. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +16 -1
  62. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  63. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  64. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
  65. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
  66. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
  67. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  68. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  69. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +11 -36
  70. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +9 -2
  71. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +38 -20
  72. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +2 -1
  73. data/lib/active_record/connection_adapters/postgresql_adapter.rb +75 -56
  74. data/lib/active_record/connection_adapters/schema_cache.rb +5 -0
  75. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +5 -5
  76. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +14 -9
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +95 -62
  78. data/lib/active_record/connection_handling.rb +132 -26
  79. data/lib/active_record/core.rb +76 -43
  80. data/lib/active_record/counter_cache.rb +4 -29
  81. data/lib/active_record/database_configurations.rb +184 -0
  82. data/lib/active_record/database_configurations/database_config.rb +37 -0
  83. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  84. data/lib/active_record/database_configurations/url_config.rb +74 -0
  85. data/lib/active_record/enum.rb +22 -7
  86. data/lib/active_record/errors.rb +24 -21
  87. data/lib/active_record/explain.rb +1 -1
  88. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  89. data/lib/active_record/fixture_set/render_context.rb +17 -0
  90. data/lib/active_record/fixture_set/table_row.rb +153 -0
  91. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  92. data/lib/active_record/fixtures.rb +140 -472
  93. data/lib/active_record/gem_version.rb +4 -4
  94. data/lib/active_record/inheritance.rb +12 -2
  95. data/lib/active_record/integration.rb +56 -16
  96. data/lib/active_record/internal_metadata.rb +5 -1
  97. data/lib/active_record/locking/optimistic.rb +2 -2
  98. data/lib/active_record/locking/pessimistic.rb +3 -3
  99. data/lib/active_record/log_subscriber.rb +7 -26
  100. data/lib/active_record/migration.rb +38 -37
  101. data/lib/active_record/migration/command_recorder.rb +35 -5
  102. data/lib/active_record/migration/compatibility.rb +34 -16
  103. data/lib/active_record/model_schema.rb +30 -9
  104. data/lib/active_record/nested_attributes.rb +2 -2
  105. data/lib/active_record/no_touching.rb +7 -0
  106. data/lib/active_record/persistence.rb +18 -7
  107. data/lib/active_record/query_cache.rb +11 -4
  108. data/lib/active_record/querying.rb +19 -11
  109. data/lib/active_record/railtie.rb +71 -42
  110. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  111. data/lib/active_record/railties/controller_runtime.rb +30 -35
  112. data/lib/active_record/railties/databases.rake +94 -43
  113. data/lib/active_record/reflection.rb +60 -44
  114. data/lib/active_record/relation.rb +150 -69
  115. data/lib/active_record/relation/batches.rb +13 -10
  116. data/lib/active_record/relation/calculations.rb +38 -28
  117. data/lib/active_record/relation/delegation.rb +4 -13
  118. data/lib/active_record/relation/finder_methods.rb +12 -25
  119. data/lib/active_record/relation/merger.rb +2 -6
  120. data/lib/active_record/relation/predicate_builder.rb +4 -6
  121. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  122. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  123. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  124. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  125. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  126. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  127. data/lib/active_record/relation/query_attribute.rb +15 -12
  128. data/lib/active_record/relation/query_methods.rb +29 -52
  129. data/lib/active_record/relation/where_clause.rb +4 -0
  130. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  131. data/lib/active_record/result.rb +30 -11
  132. data/lib/active_record/sanitization.rb +2 -39
  133. data/lib/active_record/schema.rb +1 -10
  134. data/lib/active_record/schema_dumper.rb +12 -6
  135. data/lib/active_record/schema_migration.rb +4 -0
  136. data/lib/active_record/scoping.rb +9 -8
  137. data/lib/active_record/scoping/default.rb +10 -3
  138. data/lib/active_record/scoping/named.rb +10 -14
  139. data/lib/active_record/statement_cache.rb +32 -5
  140. data/lib/active_record/store.rb +39 -8
  141. data/lib/active_record/table_metadata.rb +1 -4
  142. data/lib/active_record/tasks/database_tasks.rb +89 -23
  143. data/lib/active_record/tasks/mysql_database_tasks.rb +2 -4
  144. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
  145. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
  146. data/lib/active_record/test_databases.rb +38 -0
  147. data/lib/active_record/test_fixtures.rb +224 -0
  148. data/lib/active_record/timestamp.rb +4 -6
  149. data/lib/active_record/transactions.rb +3 -22
  150. data/lib/active_record/translation.rb +1 -1
  151. data/lib/active_record/type.rb +3 -4
  152. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  153. data/lib/active_record/type_caster/connection.rb +1 -6
  154. data/lib/active_record/type_caster/map.rb +1 -4
  155. data/lib/active_record/validations/uniqueness.rb +13 -25
  156. data/lib/arel.rb +44 -0
  157. data/lib/arel/alias_predication.rb +9 -0
  158. data/lib/arel/attributes.rb +22 -0
  159. data/lib/arel/attributes/attribute.rb +37 -0
  160. data/lib/arel/collectors/bind.rb +24 -0
  161. data/lib/arel/collectors/composite.rb +31 -0
  162. data/lib/arel/collectors/plain_string.rb +20 -0
  163. data/lib/arel/collectors/sql_string.rb +20 -0
  164. data/lib/arel/collectors/substitute_binds.rb +28 -0
  165. data/lib/arel/crud.rb +42 -0
  166. data/lib/arel/delete_manager.rb +18 -0
  167. data/lib/arel/errors.rb +9 -0
  168. data/lib/arel/expressions.rb +29 -0
  169. data/lib/arel/factory_methods.rb +49 -0
  170. data/lib/arel/insert_manager.rb +49 -0
  171. data/lib/arel/math.rb +45 -0
  172. data/lib/arel/nodes.rb +67 -0
  173. data/lib/arel/nodes/and.rb +32 -0
  174. data/lib/arel/nodes/ascending.rb +23 -0
  175. data/lib/arel/nodes/binary.rb +52 -0
  176. data/lib/arel/nodes/bind_param.rb +36 -0
  177. data/lib/arel/nodes/case.rb +55 -0
  178. data/lib/arel/nodes/casted.rb +50 -0
  179. data/lib/arel/nodes/count.rb +12 -0
  180. data/lib/arel/nodes/delete_statement.rb +45 -0
  181. data/lib/arel/nodes/descending.rb +23 -0
  182. data/lib/arel/nodes/equality.rb +18 -0
  183. data/lib/arel/nodes/extract.rb +24 -0
  184. data/lib/arel/nodes/false.rb +16 -0
  185. data/lib/arel/nodes/full_outer_join.rb +8 -0
  186. data/lib/arel/nodes/function.rb +44 -0
  187. data/lib/arel/nodes/grouping.rb +8 -0
  188. data/lib/arel/nodes/in.rb +8 -0
  189. data/lib/arel/nodes/infix_operation.rb +80 -0
  190. data/lib/arel/nodes/inner_join.rb +8 -0
  191. data/lib/arel/nodes/insert_statement.rb +37 -0
  192. data/lib/arel/nodes/join_source.rb +20 -0
  193. data/lib/arel/nodes/matches.rb +18 -0
  194. data/lib/arel/nodes/named_function.rb +23 -0
  195. data/lib/arel/nodes/node.rb +50 -0
  196. data/lib/arel/nodes/node_expression.rb +13 -0
  197. data/lib/arel/nodes/outer_join.rb +8 -0
  198. data/lib/arel/nodes/over.rb +15 -0
  199. data/lib/arel/nodes/regexp.rb +16 -0
  200. data/lib/arel/nodes/right_outer_join.rb +8 -0
  201. data/lib/arel/nodes/select_core.rb +63 -0
  202. data/lib/arel/nodes/select_statement.rb +41 -0
  203. data/lib/arel/nodes/sql_literal.rb +16 -0
  204. data/lib/arel/nodes/string_join.rb +11 -0
  205. data/lib/arel/nodes/table_alias.rb +27 -0
  206. data/lib/arel/nodes/terminal.rb +16 -0
  207. data/lib/arel/nodes/true.rb +16 -0
  208. data/lib/arel/nodes/unary.rb +44 -0
  209. data/lib/arel/nodes/unary_operation.rb +20 -0
  210. data/lib/arel/nodes/unqualified_column.rb +22 -0
  211. data/lib/arel/nodes/update_statement.rb +41 -0
  212. data/lib/arel/nodes/values.rb +16 -0
  213. data/lib/arel/nodes/values_list.rb +24 -0
  214. data/lib/arel/nodes/window.rb +126 -0
  215. data/lib/arel/nodes/with.rb +11 -0
  216. data/lib/arel/order_predications.rb +13 -0
  217. data/lib/arel/predications.rb +257 -0
  218. data/lib/arel/select_manager.rb +271 -0
  219. data/lib/arel/table.rb +110 -0
  220. data/lib/arel/tree_manager.rb +72 -0
  221. data/lib/arel/update_manager.rb +34 -0
  222. data/lib/arel/visitors.rb +20 -0
  223. data/lib/arel/visitors/depth_first.rb +199 -0
  224. data/lib/arel/visitors/dot.rb +292 -0
  225. data/lib/arel/visitors/ibm_db.rb +21 -0
  226. data/lib/arel/visitors/informix.rb +56 -0
  227. data/lib/arel/visitors/mssql.rb +143 -0
  228. data/lib/arel/visitors/mysql.rb +83 -0
  229. data/lib/arel/visitors/oracle.rb +159 -0
  230. data/lib/arel/visitors/oracle12.rb +67 -0
  231. data/lib/arel/visitors/postgresql.rb +116 -0
  232. data/lib/arel/visitors/sqlite.rb +39 -0
  233. data/lib/arel/visitors/to_sql.rb +913 -0
  234. data/lib/arel/visitors/visitor.rb +42 -0
  235. data/lib/arel/visitors/where_sql.rb +23 -0
  236. data/lib/arel/window_predications.rb +9 -0
  237. data/lib/rails/generators/active_record/migration.rb +14 -1
  238. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  239. data/lib/rails/generators/active_record/model/model_generator.rb +1 -0
  240. metadata +104 -26
@@ -20,11 +20,11 @@ module ActiveRecord::Associations::Builder # :nodoc:
20
20
  }
21
21
  end
22
22
 
23
- def self.define_extensions(model, name, &block)
23
+ def self.define_extensions(model, name)
24
24
  if block_given?
25
25
  extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
26
- extension = Module.new(&block)
27
- model.parent.const_set(extension_module_name, extension)
26
+ extension = Module.new(&Proc.new)
27
+ model.module_parent.const_set(extension_module_name, extension)
28
28
  end
29
29
  end
30
30
 
@@ -2,39 +2,6 @@
2
2
 
3
3
  module ActiveRecord::Associations::Builder # :nodoc:
4
4
  class HasAndBelongsToMany # :nodoc:
5
- class JoinTableResolver # :nodoc:
6
- KnownTable = Struct.new :join_table
7
-
8
- class KnownClass # :nodoc:
9
- def initialize(lhs_class, rhs_class_name)
10
- @lhs_class = lhs_class
11
- @rhs_class_name = rhs_class_name
12
- @join_table = nil
13
- end
14
-
15
- def join_table
16
- @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
17
- end
18
-
19
- private
20
-
21
- def klass
22
- @lhs_class.send(:compute_type, @rhs_class_name)
23
- end
24
- end
25
-
26
- def self.build(lhs_class, name, options)
27
- if options[:join_table]
28
- KnownTable.new options[:join_table].to_s
29
- else
30
- class_name = options.fetch(:class_name) {
31
- name.to_s.camelize.singularize
32
- }
33
- KnownClass.new lhs_class, class_name.to_s
34
- end
35
- end
36
- end
37
-
38
5
  attr_reader :lhs_model, :association_name, :options
39
6
 
40
7
  def initialize(association_name, lhs_model, options)
@@ -44,8 +11,6 @@ module ActiveRecord::Associations::Builder # :nodoc:
44
11
  end
45
12
 
46
13
  def through_model
47
- habtm = JoinTableResolver.build lhs_model, association_name, options
48
-
49
14
  join_model = Class.new(ActiveRecord::Base) {
50
15
  class << self
51
16
  attr_accessor :left_model
@@ -56,7 +21,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
56
21
  end
57
22
 
58
23
  def self.table_name
59
- table_name_resolver.join_table
24
+ # Table name needs to be resolved lazily
25
+ # because RHS class might not have been loaded
26
+ @table_name ||= table_name_resolver.call
60
27
  end
61
28
 
62
29
  def self.compute_type(class_name)
@@ -86,7 +53,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
86
53
  }
87
54
 
88
55
  join_model.name = "HABTM_#{association_name.to_s.camelize}"
89
- join_model.table_name_resolver = habtm
56
+ join_model.table_name_resolver = -> { table_name }
90
57
  join_model.left_model = lhs_model
91
58
 
92
59
  join_model.add_left_association :left_side, anonymous_class: lhs_model
@@ -96,7 +63,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
96
63
 
97
64
  def middle_reflection(join_model)
98
65
  middle_name = [lhs_model.name.downcase.pluralize,
99
- association_name].join("_".freeze).gsub("::".freeze, "_".freeze).to_sym
66
+ association_name].join("_").gsub("::", "_").to_sym
100
67
  middle_options = middle_options join_model
101
68
 
102
69
  HasMany.create_reflection(lhs_model,
@@ -117,6 +84,18 @@ module ActiveRecord::Associations::Builder # :nodoc:
117
84
  middle_options
118
85
  end
119
86
 
87
+ def table_name
88
+ if options[:join_table]
89
+ options[:join_table].to_s
90
+ else
91
+ class_name = options.fetch(:class_name) {
92
+ association_name.to_s.camelize.singularize
93
+ }
94
+ klass = lhs_model.send(:compute_type, class_name.to_s)
95
+ [lhs_model.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
96
+ end
97
+ end
98
+
120
99
  def belongs_to_options(options)
121
100
  rhs_options = {}
122
101
 
@@ -109,8 +109,9 @@ module ActiveRecord
109
109
  end
110
110
  end
111
111
 
112
- # Add +records+ to this association. Since +<<+ flattens its argument list
113
- # and inserts each record, +push+ and +concat+ behave identically.
112
+ # Add +records+ to this association. Returns +self+ so method calls may
113
+ # be chained. Since << flattens its argument list and inserts each record,
114
+ # +push+ and +concat+ behave identically.
114
115
  def concat(*records)
115
116
  records = records.flatten
116
117
  if owner.new_record?
@@ -211,9 +212,11 @@ module ActiveRecord
211
212
  def size
212
213
  if !find_target? || loaded?
213
214
  target.size
215
+ elsif @association_ids
216
+ @association_ids.size
214
217
  elsif !association_scope.group_values.empty?
215
218
  load_target.size
216
- elsif !association_scope.distinct_value && target.is_a?(Array)
219
+ elsif !association_scope.distinct_value && !target.empty?
217
220
  unsaved_records = target.select(&:new_record?)
218
221
  unsaved_records.size + count_records
219
222
  else
@@ -230,10 +233,10 @@ module ActiveRecord
230
233
  # loaded and you are going to fetch the records anyway it is better to
231
234
  # check <tt>collection.length.zero?</tt>.
232
235
  def empty?
233
- if loaded?
236
+ if loaded? || @association_ids
234
237
  size.zero?
235
238
  else
236
- @target.blank? && !scope.exists?
239
+ target.empty? && !scope.exists?
237
240
  end
238
241
  end
239
242
 
@@ -300,23 +303,6 @@ module ActiveRecord
300
303
  end
301
304
 
302
305
  private
303
-
304
- def find_target
305
- scope = self.scope
306
- return scope.to_a if skip_statement_cache?(scope)
307
-
308
- conn = klass.connection
309
- sc = reflection.association_scope_cache(conn, owner) do |params|
310
- as = AssociationScope.create { params.bind }
311
- target_scope.merge!(as.scope(self))
312
- end
313
-
314
- binds = AssociationScope.get_bind_values(owner, reflection.chain)
315
- sc.execute(binds, conn) do |record|
316
- set_inverse_instance(record)
317
- end
318
- end
319
-
320
306
  # We have some records loaded from the database (persisted) and some that are
321
307
  # in-memory (memory). The same record may be represented in the persisted array
322
308
  # and in the memory array.
@@ -361,6 +347,7 @@ module ActiveRecord
361
347
  add_to_target(record) do
362
348
  result = insert_record(record, true, raise) {
363
349
  @_was_loaded = loaded?
350
+ @association_ids = nil
364
351
  }
365
352
  end
366
353
  raise ActiveRecord::Rollback unless result
@@ -396,8 +383,7 @@ module ActiveRecord
396
383
  records.each { |record| callback(:before_remove, record) }
397
384
 
398
385
  delete_records(existing_records, method) if existing_records.any?
399
- records.each { |record| target.delete(record) }
400
- @association_ids = nil
386
+ @target -= records
401
387
 
402
388
  records.each { |record| callback(:after_remove, record) }
403
389
  end
@@ -438,6 +424,7 @@ module ActiveRecord
438
424
  unless owner.new_record?
439
425
  result &&= insert_record(record, true, raise) {
440
426
  @_was_loaded = loaded?
427
+ @association_ids = nil
441
428
  }
442
429
  end
443
430
  end
@@ -460,7 +447,6 @@ module ActiveRecord
460
447
  if index
461
448
  target[index] = record
462
449
  elsif @_was_loaded || !loaded?
463
- @association_ids = nil
464
450
  target << record
465
451
  end
466
452
 
@@ -366,6 +366,34 @@ module ActiveRecord
366
366
  @association.create!(attributes, &block)
367
367
  end
368
368
 
369
+ # Add one or more records to the collection by setting their foreign keys
370
+ # to the association's primary key. Since #<< flattens its argument list and
371
+ # inserts each record, +push+ and #concat behave identically. Returns +self+
372
+ # so method calls may be chained.
373
+ #
374
+ # class Person < ActiveRecord::Base
375
+ # has_many :pets
376
+ # end
377
+ #
378
+ # person.pets.size # => 0
379
+ # person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
380
+ # person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
381
+ # person.pets.size # => 3
382
+ #
383
+ # person.id # => 1
384
+ # person.pets
385
+ # # => [
386
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
387
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
388
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
389
+ # # ]
390
+ #
391
+ # person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
392
+ # person.pets.size # => 5
393
+ def concat(*records)
394
+ @association.concat(*records)
395
+ end
396
+
369
397
  # Replaces this collection with +other_array+. This will perform a diff
370
398
  # and delete/add only records that have changed.
371
399
  #
@@ -1005,9 +1033,8 @@ module ActiveRecord
1005
1033
  end
1006
1034
 
1007
1035
  # Adds one or more +records+ to the collection by setting their foreign keys
1008
- # to the association's primary key. Since +<<+ flattens its argument list and
1009
- # inserts each record, +push+ and +concat+ behave identically. Returns +self+
1010
- # so several appends may be chained together.
1036
+ # to the association's primary key. Returns +self+, so several appends may be
1037
+ # chained together.
1011
1038
  #
1012
1039
  # class Person < ActiveRecord::Base
1013
1040
  # has_many :pets
@@ -1030,7 +1057,6 @@ module ActiveRecord
1030
1057
  end
1031
1058
  alias_method :push, :<<
1032
1059
  alias_method :append, :<<
1033
- alias_method :concat, :<<
1034
1060
 
1035
1061
  def prepend(*args)
1036
1062
  raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
@@ -1062,7 +1088,7 @@ module ActiveRecord
1062
1088
  # person.pets.reload # fetches pets from the database
1063
1089
  # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
1064
1090
  def reload
1065
- proxy_association.reload
1091
+ proxy_association.reload(true)
1066
1092
  reset_scope
1067
1093
  end
1068
1094
 
@@ -1099,7 +1125,7 @@ module ActiveRecord
1099
1125
  SpawnMethods,
1100
1126
  ].flat_map { |klass|
1101
1127
  klass.public_instance_methods(false)
1102
- } - self.public_instance_methods(false) - [:select] + [:scoping]
1128
+ } - self.public_instance_methods(false) - [:select] + [:scoping, :values]
1103
1129
 
1104
1130
  delegate(*delegate_methods, to: :scope)
1105
1131
 
@@ -9,5 +9,12 @@ module ActiveRecord::Associations
9
9
  false
10
10
  end
11
11
  end
12
+
13
+ def nullified_owner_attributes
14
+ Hash.new.tap do |attrs|
15
+ attrs[reflection.foreign_key] = nil
16
+ attrs[reflection.type] = nil if reflection.type.present?
17
+ end
18
+ end
12
19
  end
13
20
  end
@@ -92,7 +92,7 @@ module ActiveRecord
92
92
  if method == :delete_all
93
93
  scope.delete_all
94
94
  else
95
- scope.update_all(reflection.foreign_key => nil)
95
+ scope.update_all(nullified_owner_attributes)
96
96
  end
97
97
  end
98
98
 
@@ -21,20 +21,6 @@ module ActiveRecord
21
21
  super
22
22
  end
23
23
 
24
- def concat_records(records)
25
- ensure_not_nested
26
-
27
- records = super(records, true)
28
-
29
- if owner.new_record? && records
30
- records.flatten.each do |record|
31
- build_through_record(record)
32
- end
33
- end
34
-
35
- records
36
- end
37
-
38
24
  def insert_record(record, validate = true, raise = false)
39
25
  ensure_not_nested
40
26
 
@@ -48,6 +34,20 @@ module ActiveRecord
48
34
  end
49
35
 
50
36
  private
37
+ def concat_records(records)
38
+ ensure_not_nested
39
+
40
+ records = super(records, true)
41
+
42
+ if owner.new_record? && records
43
+ records.flatten.each do |record|
44
+ build_through_record(record)
45
+ end
46
+ end
47
+
48
+ records
49
+ end
50
+
51
51
  # The through record (built with build_record) is temporarily cached
52
52
  # so that it may be reused if insert_record is subsequently called.
53
53
  #
@@ -57,14 +57,21 @@ module ActiveRecord
57
57
  @through_records[record.object_id] ||= begin
58
58
  ensure_mutable
59
59
 
60
- attributes = through_scope_attributes
61
- attributes[source_reflection.name] = record
62
- attributes[source_reflection.foreign_type] = options[:source_type] if options[:source_type]
60
+ through_record = through_association.build(*options_for_through_record)
61
+ through_record.send("#{source_reflection.name}=", record)
62
+
63
+ if options[:source_type]
64
+ through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
65
+ end
63
66
 
64
- through_association.build(attributes)
67
+ through_record
65
68
  end
66
69
  end
67
70
 
71
+ def options_for_through_record
72
+ [through_scope_attributes]
73
+ end
74
+
68
75
  def through_scope_attributes
69
76
  scope.where_values_hash(through_association.reflection.name.to_s).
70
77
  except!(through_association.reflection.foreign_key,
@@ -23,35 +23,6 @@ module ActiveRecord
23
23
  end
24
24
  end
25
25
 
26
- def replace(record, save = true)
27
- raise_on_type_mismatch!(record) if record
28
- load_target
29
-
30
- return target unless target || record
31
-
32
- assigning_another_record = target != record
33
- if assigning_another_record || record.has_changes_to_save?
34
- save &&= owner.persisted?
35
-
36
- transaction_if(save) do
37
- remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
38
-
39
- if record
40
- set_owner_attributes(record)
41
- set_inverse_instance(record)
42
-
43
- if save && !record.save
44
- nullify_owner_attributes(record)
45
- set_owner_attributes(target) if target
46
- raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
47
- end
48
- end
49
- end
50
- end
51
-
52
- self.target = record
53
- end
54
-
55
26
  def delete(method = options[:dependent])
56
27
  if load_target
57
28
  case method
@@ -62,12 +33,39 @@ module ActiveRecord
62
33
  target.destroy
63
34
  throw(:abort) unless target.destroyed?
64
35
  when :nullify
65
- target.update_columns(reflection.foreign_key => nil) if target.persisted?
36
+ target.update_columns(nullified_owner_attributes) if target.persisted?
66
37
  end
67
38
  end
68
39
  end
69
40
 
70
41
  private
42
+ def replace(record, save = true)
43
+ raise_on_type_mismatch!(record) if record
44
+
45
+ return target unless load_target || record
46
+
47
+ assigning_another_record = target != record
48
+ if assigning_another_record || record.has_changes_to_save?
49
+ save &&= owner.persisted?
50
+
51
+ transaction_if(save) do
52
+ remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
53
+
54
+ if record
55
+ set_owner_attributes(record)
56
+ set_inverse_instance(record)
57
+
58
+ if save && !record.save
59
+ nullify_owner_attributes(record)
60
+ set_owner_attributes(target) if target
61
+ raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ self.target = record
68
+ end
71
69
 
72
70
  # The reason that the save param for replace is false, if for create (not just build),
73
71
  # is because the setting of the foreign keys is actually handled by the scoping when
@@ -6,12 +6,12 @@ module ActiveRecord
6
6
  class HasOneThroughAssociation < HasOneAssociation #:nodoc:
7
7
  include ThroughAssociation
8
8
 
9
- def replace(record, save = true)
10
- create_through_record(record, save)
11
- self.target = record
12
- end
13
-
14
9
  private
10
+ def replace(record, save = true)
11
+ create_through_record(record, save)
12
+ self.target = record
13
+ end
14
+
15
15
  def create_through_record(record, save)
16
16
  ensure_not_nested
17
17