activerecord 4.2.11.1 → 5.2.4.5

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 (274) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +594 -1620
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +10 -11
  5. data/examples/performance.rb +32 -31
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +263 -249
  8. data/lib/active_record/association_relation.rb +11 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +77 -43
  11. data/lib/active_record/associations/association_scope.rb +106 -133
  12. data/lib/active_record/associations/belongs_to_association.rb +52 -41
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
  14. data/lib/active_record/associations/builder/association.rb +29 -38
  15. data/lib/active_record/associations/builder/belongs_to.rb +77 -30
  16. data/lib/active_record/associations/builder/collection_association.rb +9 -22
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +42 -35
  18. data/lib/active_record/associations/builder/has_many.rb +6 -4
  19. data/lib/active_record/associations/builder/has_one.rb +13 -6
  20. data/lib/active_record/associations/builder/singular_association.rb +15 -11
  21. data/lib/active_record/associations/collection_association.rb +139 -280
  22. data/lib/active_record/associations/collection_proxy.rb +231 -133
  23. data/lib/active_record/associations/foreign_association.rb +3 -1
  24. data/lib/active_record/associations/has_many_association.rb +34 -89
  25. data/lib/active_record/associations/has_many_through_association.rb +49 -76
  26. data/lib/active_record/associations/has_one_association.rb +38 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +40 -87
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
  31. data/lib/active_record/associations/join_dependency.rb +133 -159
  32. data/lib/active_record/associations/preloader/association.rb +85 -120
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +81 -91
  35. data/lib/active_record/associations/singular_association.rb +27 -34
  36. data/lib/active_record/associations/through_association.rb +38 -18
  37. data/lib/active_record/associations.rb +1732 -1597
  38. data/lib/active_record/attribute_assignment.rb +58 -182
  39. data/lib/active_record/attribute_decorators.rb +39 -15
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +10 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -135
  42. data/lib/active_record/attribute_methods/primary_key.rb +86 -71
  43. data/lib/active_record/attribute_methods/query.rb +4 -2
  44. data/lib/active_record/attribute_methods/read.rb +45 -63
  45. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +58 -36
  47. data/lib/active_record/attribute_methods/write.rb +30 -45
  48. data/lib/active_record/attribute_methods.rb +166 -109
  49. data/lib/active_record/attributes.rb +201 -82
  50. data/lib/active_record/autosave_association.rb +94 -36
  51. data/lib/active_record/base.rb +57 -44
  52. data/lib/active_record/callbacks.rb +97 -57
  53. data/lib/active_record/coders/json.rb +3 -1
  54. data/lib/active_record/coders/yaml_column.rb +24 -12
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -290
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +237 -90
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +71 -21
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +118 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +318 -217
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +570 -228
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +138 -70
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +325 -202
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +542 -601
  69. data/lib/active_record/connection_adapters/column.rb +50 -41
  70. data/lib/active_record/connection_adapters/connection_specification.rb +147 -135
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +41 -180
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +45 -114
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -58
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +10 -6
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -22
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  99. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +55 -53
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +107 -47
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +144 -90
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -284
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +432 -323
  117. data/lib/active_record/connection_adapters/schema_cache.rb +48 -24
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +269 -308
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +40 -27
  128. data/lib/active_record/core.rb +178 -198
  129. data/lib/active_record/counter_cache.rb +79 -36
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +87 -105
  132. data/lib/active_record/enum.rb +135 -88
  133. data/lib/active_record/errors.rb +179 -52
  134. data/lib/active_record/explain.rb +23 -11
  135. data/lib/active_record/explain_registry.rb +4 -2
  136. data/lib/active_record/explain_subscriber.rb +10 -5
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +188 -132
  139. data/lib/active_record/gem_version.rb +5 -3
  140. data/lib/active_record/inheritance.rb +148 -112
  141. data/lib/active_record/integration.rb +70 -28
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +21 -3
  144. data/lib/active_record/locale/en.yml +3 -2
  145. data/lib/active_record/locking/optimistic.rb +88 -96
  146. data/lib/active_record/locking/pessimistic.rb +15 -3
  147. data/lib/active_record/log_subscriber.rb +95 -33
  148. data/lib/active_record/migration/command_recorder.rb +133 -90
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +8 -6
  151. data/lib/active_record/migration.rb +581 -282
  152. data/lib/active_record/model_schema.rb +290 -111
  153. data/lib/active_record/nested_attributes.rb +264 -222
  154. data/lib/active_record/no_touching.rb +7 -1
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +347 -119
  157. data/lib/active_record/query_cache.rb +13 -24
  158. data/lib/active_record/querying.rb +19 -17
  159. data/lib/active_record/railtie.rb +94 -32
  160. data/lib/active_record/railties/console_sandbox.rb +2 -0
  161. data/lib/active_record/railties/controller_runtime.rb +9 -3
  162. data/lib/active_record/railties/databases.rake +149 -156
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +414 -267
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +204 -55
  167. data/lib/active_record/relation/calculations.rb +256 -248
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +288 -239
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +86 -86
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -24
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  179. data/lib/active_record/relation/predicate_builder.rb +116 -119
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +448 -393
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +11 -13
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +287 -340
  187. data/lib/active_record/result.rb +54 -36
  188. data/lib/active_record/runtime_registry.rb +6 -4
  189. data/lib/active_record/sanitization.rb +155 -124
  190. data/lib/active_record/schema.rb +30 -24
  191. data/lib/active_record/schema_dumper.rb +91 -87
  192. data/lib/active_record/schema_migration.rb +19 -16
  193. data/lib/active_record/scoping/default.rb +102 -85
  194. data/lib/active_record/scoping/named.rb +81 -32
  195. data/lib/active_record/scoping.rb +45 -26
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +5 -5
  198. data/lib/active_record/statement_cache.rb +45 -35
  199. data/lib/active_record/store.rb +42 -36
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +134 -96
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +56 -100
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +83 -41
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -16
  206. data/lib/active_record/timestamp.rb +70 -38
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +199 -124
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +4 -45
  212. data/lib/active_record/type/date_time.rb +4 -49
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +5 -3
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +24 -15
  218. data/lib/active_record/type/text.rb +2 -2
  219. data/lib/active_record/type/time.rb +11 -16
  220. data/lib/active_record/type/type_map.rb +15 -17
  221. data/lib/active_record/type/unsigned_integer.rb +9 -7
  222. data/lib/active_record/type.rb +79 -23
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +13 -4
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +14 -13
  230. data/lib/active_record/validations/uniqueness.rb +40 -41
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +34 -22
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -35
  237. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +8 -3
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -1
  239. data/lib/rails/generators/active_record/migration.rb +18 -1
  240. data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
  241. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +3 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  243. data/lib/rails/generators/active_record.rb +7 -5
  244. metadata +72 -49
  245. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  246. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  247. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  248. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  249. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  250. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  251. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  252. data/lib/active_record/attribute.rb +0 -163
  253. data/lib/active_record/attribute_set/builder.rb +0 -106
  254. data/lib/active_record/attribute_set.rb +0 -81
  255. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  256. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  257. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  258. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  259. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  260. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  261. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  262. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  263. data/lib/active_record/type/big_integer.rb +0 -13
  264. data/lib/active_record/type/binary.rb +0 -50
  265. data/lib/active_record/type/boolean.rb +0 -31
  266. data/lib/active_record/type/decimal.rb +0 -64
  267. data/lib/active_record/type/decorator.rb +0 -14
  268. data/lib/active_record/type/float.rb +0 -19
  269. data/lib/active_record/type/integer.rb +0 -59
  270. data/lib/active_record/type/mutable.rb +0 -16
  271. data/lib/active_record/type/numeric.rb +0 -36
  272. data/lib/active_record/type/string.rb +0 -40
  273. data/lib/active_record/type/time_value.rb +0 -38
  274. data/lib/active_record/type/value.rb +0 -110
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  # = Active Record Association Collection
@@ -10,9 +12,9 @@ module ActiveRecord
10
12
  # HasManyAssociation => has_many
11
13
  # HasManyThroughAssociation + ThroughAssociation => has_many :through
12
14
  #
13
- # CollectionAssociation class provides common methods to the collections
15
+ # The CollectionAssociation class provides common methods to the collections
14
16
  # defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
15
- # +:through association+ option.
17
+ # the +:through association+ option.
16
18
  #
17
19
  # You need to be careful with assumptions regarding the target: The proxy
18
20
  # does not fetch records from the database until it needs them, but new
@@ -24,22 +26,14 @@ module ActiveRecord
24
26
  # If you need to work on all current children, new and existing records,
25
27
  # +load_target+ and the +loaded+ flag are your friends.
26
28
  class CollectionAssociation < Association #:nodoc:
27
-
28
29
  # Implements the reader method, e.g. foo.items for Foo.has_many :items
29
- def reader(force_reload = false)
30
- if force_reload
31
- klass.uncached { reload }
32
- elsif stale_target?
30
+ def reader
31
+ if stale_target?
33
32
  reload
34
33
  end
35
34
 
36
- if owner.new_record?
37
- # Cache the proxy separately before the owner has an id
38
- # or else a post-save proxy will still lack the id
39
- @new_record_proxy ||= CollectionProxy.create(klass, self)
40
- else
41
- @proxy ||= CollectionProxy.create(klass, self)
42
- end
35
+ @proxy ||= CollectionProxy.create(klass, self)
36
+ @proxy.reset_scope
43
37
  end
44
38
 
45
39
  # Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -50,103 +44,60 @@ module ActiveRecord
50
44
  # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
51
45
  def ids_reader
52
46
  if loaded?
53
- load_target.map do |record|
54
- record.send(reflection.association_primary_key)
55
- end
47
+ target.pluck(reflection.association_primary_key)
48
+ elsif !target.empty?
49
+ load_target.pluck(reflection.association_primary_key)
56
50
  else
57
- column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
58
- scope.pluck(column)
51
+ @association_ids ||= scope.pluck(reflection.association_primary_key)
59
52
  end
60
53
  end
61
54
 
62
55
  # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
63
56
  def ids_writer(ids)
64
- pk_column = reflection.association_primary_key
65
- pk_type = klass.type_for_attribute(pk_column)
66
- ids = Array(ids).reject(&:blank?).map do |i|
67
- pk_type.type_cast_from_user(i)
68
- end
57
+ primary_key = reflection.association_primary_key
58
+ pk_type = klass.type_for_attribute(primary_key)
59
+ ids = Array(ids).reject(&:blank?)
60
+ ids.map! { |i| pk_type.cast(i) }
69
61
 
70
- objs = klass.where(pk_column => ids).index_by do |r|
71
- r.send(pk_column)
62
+ records = klass.where(primary_key => ids).index_by do |r|
63
+ r.public_send(primary_key)
72
64
  end.values_at(*ids).compact
73
65
 
74
- if objs.size == ids.size
75
- replace(objs.index_by { |r| r.send(pk_column) }.values_at(*ids))
66
+ if records.size != ids.size
67
+ found_ids = records.map { |record| record.public_send(primary_key) }
68
+ not_found_ids = ids - found_ids
69
+ klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, primary_key, not_found_ids)
76
70
  else
77
- klass.all.raise_record_not_found_exception!(ids, objs.size, ids.size)
71
+ replace(records)
78
72
  end
79
73
  end
80
74
 
81
75
  def reset
82
76
  super
83
77
  @target = []
84
- end
85
-
86
- def select(*fields)
87
- if block_given?
88
- load_target.select.each { |e| yield e }
89
- else
90
- scope.select(*fields)
91
- end
78
+ @association_ids = nil
92
79
  end
93
80
 
94
81
  def find(*args)
95
- if block_given?
96
- load_target.find(*args) { |*block_args| yield(*block_args) }
97
- else
98
- if options[:inverse_of] && loaded?
99
- args_flatten = args.flatten
100
- raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
101
- result = find_by_scan(*args)
102
-
103
- result_size = Array(result).size
104
- if !result || result_size != args_flatten.size
105
- scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
106
- else
107
- result
108
- end
109
- else
110
- scope.find(*args)
111
- end
112
- end
113
- end
114
-
115
- def first(*args)
116
- first_nth_or_last(:first, *args)
117
- end
118
-
119
- def second(*args)
120
- first_nth_or_last(:second, *args)
121
- end
82
+ if options[:inverse_of] && loaded?
83
+ args_flatten = args.flatten
84
+ model = scope.klass
122
85
 
123
- def third(*args)
124
- first_nth_or_last(:third, *args)
125
- end
126
-
127
- def fourth(*args)
128
- first_nth_or_last(:fourth, *args)
129
- end
130
-
131
- def fifth(*args)
132
- first_nth_or_last(:fifth, *args)
133
- end
86
+ if args_flatten.blank?
87
+ error_message = "Couldn't find #{model.name} without an ID"
88
+ raise RecordNotFound.new(error_message, model.name, model.primary_key, args)
89
+ end
134
90
 
135
- def forty_two(*args)
136
- first_nth_or_last(:forty_two, *args)
137
- end
91
+ result = find_by_scan(*args)
138
92
 
139
- def last(*args)
140
- first_nth_or_last(:last, *args)
141
- end
142
-
143
- def take(n = nil)
144
- if loaded?
145
- n ? target.take(n) : target.first
146
- else
147
- scope.take(n).tap do |record|
148
- set_inverse_instance record if record.is_a? ActiveRecord::Base
93
+ result_size = Array(result).size
94
+ if !result || result_size != args_flatten.size
95
+ scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
96
+ else
97
+ result
149
98
  end
99
+ else
100
+ scope.find(*args)
150
101
  end
151
102
  end
152
103
 
@@ -154,24 +105,14 @@ module ActiveRecord
154
105
  if attributes.is_a?(Array)
155
106
  attributes.collect { |attr| build(attr, &block) }
156
107
  else
157
- add_to_target(build_record(attributes)) do |record|
158
- yield(record) if block_given?
159
- end
108
+ add_to_target(build_record(attributes, &block))
160
109
  end
161
110
  end
162
111
 
163
- def create(attributes = {}, &block)
164
- _create_record(attributes, &block)
165
- end
166
-
167
- def create!(attributes = {}, &block)
168
- _create_record(attributes, true, &block)
169
- end
170
-
171
- # Add +records+ to this association. Returns +self+ so method calls may
172
- # be chained. Since << flattens its argument list and inserts each record,
173
- # +push+ and +concat+ behave identically.
112
+ # Add +records+ to this association. Since +<<+ flattens its argument list
113
+ # and inserts each record, +push+ and +concat+ behave identically.
174
114
  def concat(*records)
115
+ records = records.flatten
175
116
  if owner.new_record?
176
117
  load_target
177
118
  concat_records(records)
@@ -214,12 +155,12 @@ module ActiveRecord
214
155
  end
215
156
 
216
157
  dependent = if dependent
217
- dependent
218
- elsif options[:dependent] == :destroy
219
- :delete_all
220
- else
221
- options[:dependent]
222
- end
158
+ dependent
159
+ elsif options[:dependent] == :destroy
160
+ :delete_all
161
+ else
162
+ options[:dependent]
163
+ end
223
164
 
224
165
  delete_or_nullify_all_records(dependent).tap do
225
166
  reset
@@ -237,32 +178,6 @@ module ActiveRecord
237
178
  end
238
179
  end
239
180
 
240
- # Count all records using SQL. Construct options and pass them with
241
- # scope to the target class's +count+.
242
- def count(column_name = nil, count_options = {})
243
- # TODO: Remove count_options argument as soon we remove support to
244
- # activerecord-deprecated_finders.
245
- column_name, count_options = nil, column_name if column_name.is_a?(Hash)
246
-
247
- relation = scope
248
- if association_scope.distinct_value
249
- # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
250
- column_name ||= reflection.klass.primary_key
251
- relation = relation.distinct
252
- end
253
-
254
- value = relation.count(column_name)
255
-
256
- limit = options[:limit]
257
- offset = options[:offset]
258
-
259
- if limit || offset
260
- [ [value - offset.to_i, 0].max, limit.to_i ].min
261
- else
262
- value
263
- end
264
- end
265
-
266
181
  # Removes +records+ from this association calling +before_remove+ and
267
182
  # +after_remove+ callbacks.
268
183
  #
@@ -271,12 +186,7 @@ module ActiveRecord
271
186
  # are actually removed from the database, that depends precisely on
272
187
  # +delete_records+. They are in any case removed from the collection.
273
188
  def delete(*records)
274
- return if records.empty?
275
- _options = records.extract_options!
276
- dependent = _options[:dependent] || options[:dependent]
277
-
278
- records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
279
- delete_or_destroy(records, dependent)
189
+ delete_or_destroy(records, options[:dependent])
280
190
  end
281
191
 
282
192
  # Deletes the +records+ and removes them from this association calling
@@ -285,8 +195,6 @@ module ActiveRecord
285
195
  # Note that this method removes records from the database ignoring the
286
196
  # +:dependent+ option.
287
197
  def destroy(*records)
288
- return if records.empty?
289
- records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
290
198
  delete_or_destroy(records, :destroy)
291
199
  end
292
200
 
@@ -302,30 +210,17 @@ module ActiveRecord
302
210
  # +count_records+, which is a method descendants have to provide.
303
211
  def size
304
212
  if !find_target? || loaded?
305
- if association_scope.distinct_value
306
- target.uniq.size
307
- else
308
- target.size
309
- end
310
- elsif !loaded? && !association_scope.group_values.empty?
213
+ target.size
214
+ elsif !association_scope.group_values.empty?
311
215
  load_target.size
312
- elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
313
- unsaved_records = target.select { |r| r.new_record? }
216
+ elsif !association_scope.distinct_value && target.is_a?(Array)
217
+ unsaved_records = target.select(&:new_record?)
314
218
  unsaved_records.size + count_records
315
219
  else
316
220
  count_records
317
221
  end
318
222
  end
319
223
 
320
- # Returns the size of the collection calling +size+ on the target.
321
- #
322
- # If the collection has been already loaded +length+ and +size+ are
323
- # equivalent. If not and you are going to need the records anyway this
324
- # method will take one less query. Otherwise +size+ is more efficient.
325
- def length
326
- load_target.size
327
- end
328
-
329
224
  # Returns true if the collection is empty.
330
225
  #
331
226
  # If the collection has been loaded
@@ -342,34 +237,6 @@ module ActiveRecord
342
237
  end
343
238
  end
344
239
 
345
- # Returns true if the collections is not empty.
346
- # Equivalent to +!collection.empty?+.
347
- def any?
348
- if block_given?
349
- load_target.any? { |*block_args| yield(*block_args) }
350
- else
351
- !empty?
352
- end
353
- end
354
-
355
- # Returns true if the collection has more than 1 record.
356
- # Equivalent to +collection.size > 1+.
357
- def many?
358
- if block_given?
359
- load_target.many? { |*block_args| yield(*block_args) }
360
- else
361
- size > 1
362
- end
363
- end
364
-
365
- def distinct
366
- seen = {}
367
- load_target.find_all do |record|
368
- seen[record.id] = true unless seen.key?(record.id)
369
- end
370
- end
371
- alias uniq distinct
372
-
373
240
  # Replace this collection with +other_array+. This will perform a diff
374
241
  # and delete/add only records that have changed.
375
242
  def replace(other_array)
@@ -382,6 +249,8 @@ module ActiveRecord
382
249
  replace_common_records_in_memory(other_array, original_target)
383
250
  if other_array != original_target
384
251
  transaction { replace_records(other_array, original_target) }
252
+ else
253
+ other_array
385
254
  end
386
255
  end
387
256
  end
@@ -414,25 +283,9 @@ module ActiveRecord
414
283
  replace_on_target(record, index, skip_callbacks, &block)
415
284
  end
416
285
 
417
- def replace_on_target(record, index, skip_callbacks)
418
- callback(:before_add, record) unless skip_callbacks
419
- yield(record) if block_given?
420
-
421
- if index
422
- @target[index] = record
423
- else
424
- @target << record
425
- end
426
-
427
- callback(:after_add, record) unless skip_callbacks
428
- set_inverse_instance(record)
429
-
430
- record
431
- end
432
-
433
- def scope(opts = {})
434
- scope = super()
435
- scope.none! if opts.fetch(:nullify, true) && null_scope?
286
+ def scope
287
+ scope = super
288
+ scope.none! if null_scope?
436
289
  scope
437
290
  end
438
291
 
@@ -440,26 +293,28 @@ module ActiveRecord
440
293
  owner.new_record? && !foreign_key_present?
441
294
  end
442
295
 
296
+ def find_from_target?
297
+ loaded? ||
298
+ owner.new_record? ||
299
+ target.any? { |record| record.new_record? || record.changed? }
300
+ end
301
+
443
302
  private
444
- def get_records
445
- return scope.to_a if skip_statement_cache?
446
303
 
447
- conn = klass.connection
448
- sc = reflection.association_scope_cache(conn, owner) do
449
- StatementCache.create(conn) { |params|
450
- as = AssociationScope.create { params.bind }
451
- target_scope.merge as.scope(self, conn)
452
- }
453
- end
304
+ def find_target
305
+ scope = self.scope
306
+ return scope.to_a if skip_statement_cache?(scope)
454
307
 
455
- binds = AssociationScope.get_bind_values(owner, reflection.chain)
456
- sc.execute binds, klass, klass.connection
457
- end
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
458
313
 
459
- def find_target
460
- records = get_records
461
- records.each { |record| set_inverse_instance(record) }
462
- records
314
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
315
+ sc.execute(binds, conn) do |record|
316
+ set_inverse_instance(record)
317
+ end
463
318
  end
464
319
 
465
320
  # We have some records loaded from the database (persisted) and some that are
@@ -479,7 +334,7 @@ module ActiveRecord
479
334
  persisted.map! do |record|
480
335
  if mem_record = memory.delete(record)
481
336
 
482
- ((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
337
+ ((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save).each do |name|
483
338
  mem_record[name] = record[name]
484
339
  end
485
340
 
@@ -500,28 +355,35 @@ module ActiveRecord
500
355
  if attributes.is_a?(Array)
501
356
  attributes.collect { |attr| _create_record(attr, raise, &block) }
502
357
  else
358
+ record = build_record(attributes, &block)
503
359
  transaction do
504
- add_to_target(build_record(attributes)) do |record|
505
- yield(record) if block_given?
506
- insert_record(record, true, raise)
360
+ result = nil
361
+ add_to_target(record) do
362
+ result = insert_record(record, true, raise) {
363
+ @_was_loaded = loaded?
364
+ }
507
365
  end
366
+ raise ActiveRecord::Rollback unless result
508
367
  end
368
+ record
509
369
  end
510
370
  end
511
371
 
512
372
  # Do the relevant stuff to insert the given record into the association collection.
513
- def insert_record(record, validate = true, raise = false)
514
- raise NotImplementedError
515
- end
516
-
517
- def create_scope
518
- scope.scope_for_create.stringify_keys
373
+ def insert_record(record, validate = true, raise = false, &block)
374
+ if raise
375
+ record.save!(validate: validate, &block)
376
+ else
377
+ record.save(validate: validate, &block)
378
+ end
519
379
  end
520
380
 
521
381
  def delete_or_destroy(records, method)
382
+ return if records.empty?
383
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
522
384
  records = records.flatten
523
385
  records.each { |record| raise_on_type_mismatch!(record) }
524
- existing_records = records.reject { |r| r.new_record? }
386
+ existing_records = records.reject(&:new_record?)
525
387
 
526
388
  if existing_records.empty?
527
389
  remove_records(existing_records, records, method)
@@ -535,20 +397,22 @@ module ActiveRecord
535
397
 
536
398
  delete_records(existing_records, method) if existing_records.any?
537
399
  records.each { |record| target.delete(record) }
400
+ @association_ids = nil
538
401
 
539
402
  records.each { |record| callback(:after_remove, record) }
540
403
  end
541
404
 
542
- # Delete the given records from the association, using one of the methods :destroy,
543
- # :delete_all or :nullify (or nil, in which case a default is used).
405
+ # Delete the given records from the association,
406
+ # using one of the methods +:destroy+, +:delete_all+
407
+ # or +:nullify+ (or +nil+, in which case a default is used).
544
408
  def delete_records(records, method)
545
409
  raise NotImplementedError
546
410
  end
547
411
 
548
412
  def replace_records(new_target, original_target)
549
- delete(target - new_target)
413
+ delete(difference(target, new_target))
550
414
 
551
- unless concat(new_target - target)
415
+ unless concat(difference(new_target, target))
552
416
  @target = original_target
553
417
  raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
554
418
  "new records could not be saved."
@@ -558,24 +422,53 @@ module ActiveRecord
558
422
  end
559
423
 
560
424
  def replace_common_records_in_memory(new_target, original_target)
561
- common_records = new_target & original_target
425
+ common_records = intersection(new_target, original_target)
562
426
  common_records.each do |record|
563
427
  skip_callbacks = true
564
428
  replace_on_target(record, @target.index(record), skip_callbacks)
565
429
  end
566
430
  end
567
431
 
568
- def concat_records(records, should_raise = false)
432
+ def concat_records(records, raise = false)
569
433
  result = true
570
434
 
571
- records.flatten.each do |record|
435
+ records.each do |record|
572
436
  raise_on_type_mismatch!(record)
573
- add_to_target(record) do |rec|
574
- result &&= insert_record(rec, true, should_raise) unless owner.new_record?
437
+ add_to_target(record) do
438
+ unless owner.new_record?
439
+ result &&= insert_record(record, true, raise) {
440
+ @_was_loaded = loaded?
441
+ }
442
+ end
575
443
  end
576
444
  end
577
445
 
578
- result && records
446
+ raise ActiveRecord::Rollback unless result
447
+
448
+ records
449
+ end
450
+
451
+ def replace_on_target(record, index, skip_callbacks)
452
+ callback(:before_add, record) unless skip_callbacks
453
+
454
+ set_inverse_instance(record)
455
+
456
+ @_was_loaded = true
457
+
458
+ yield(record) if block_given?
459
+
460
+ if index
461
+ target[index] = record
462
+ elsif @_was_loaded || !loaded?
463
+ @association_ids = nil
464
+ target << record
465
+ end
466
+
467
+ callback(:after_add, record) unless skip_callbacks
468
+
469
+ record
470
+ ensure
471
+ @_was_loaded = nil
579
472
  end
580
473
 
581
474
  def callback(method, record)
@@ -589,36 +482,12 @@ module ActiveRecord
589
482
  owner.class.send(full_callback_name)
590
483
  end
591
484
 
592
- # Should we deal with assoc.first or assoc.last by issuing an independent query to
593
- # the database, or by getting the target, and then taking the first/last item from that?
594
- #
595
- # If the args is just a non-empty options hash, go to the database.
596
- #
597
- # Otherwise, go to the database only if none of the following are true:
598
- # * target already loaded
599
- # * owner is new record
600
- # * target contains new or changed record(s)
601
- def fetch_first_nth_or_last_using_find?(args)
602
- if args.first.is_a?(Hash)
603
- true
604
- else
605
- !(loaded? ||
606
- owner.new_record? ||
607
- target.any? { |record| record.new_record? || record.changed? })
608
- end
609
- end
610
-
611
485
  def include_in_memory?(record)
612
486
  if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
613
487
  assoc = owner.association(reflection.through_reflection.name)
614
488
  assoc.reader.any? { |source|
615
- target_association = source.send(reflection.source_reflection.name)
616
-
617
- if target_association.respond_to?(:include?)
618
- target_association.include?(record)
619
- else
620
- target_association == record
621
- end
489
+ target_reflection = source.send(reflection.source_reflection.name)
490
+ target_reflection.respond_to?(:include?) ? target_reflection.include?(record) : target_reflection == record
622
491
  } || target.include?(record)
623
492
  else
624
493
  target.include?(record)
@@ -629,7 +498,7 @@ module ActiveRecord
629
498
  # specified, then #find scans the entire collection.
630
499
  def find_by_scan(*args)
631
500
  expects_array = args.first.kind_of?(Array)
632
- ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq
501
+ ids = args.flatten.compact.map(&:to_s).uniq
633
502
 
634
503
  if ids.size == 1
635
504
  id = ids.first
@@ -639,16 +508,6 @@ module ActiveRecord
639
508
  load_target.select { |r| ids.include?(r.id.to_s) }
640
509
  end
641
510
  end
642
-
643
- # Fetches the first/last using SQL if possible, otherwise from the target array.
644
- def first_nth_or_last(type, *args)
645
- args.shift if args.first.is_a?(Hash) && args.first.empty?
646
-
647
- collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
648
- collection.send(type, *args).tap do |record|
649
- set_inverse_instance record if record.is_a? ActiveRecord::Base
650
- end
651
- end
652
511
  end
653
512
  end
654
513
  end