activerecord 4.2.0 → 5.2.8.1

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 +5 -5
  2. data/CHANGELOG.md +640 -928
  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 +264 -247
  8. data/lib/active_record/association_relation.rb +24 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +87 -41
  11. data/lib/active_record/associations/association_scope.rb +106 -132
  12. data/lib/active_record/associations/belongs_to_association.rb +55 -36
  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 +14 -23
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +50 -39
  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 +145 -266
  22. data/lib/active_record/associations/collection_proxy.rb +242 -138
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +35 -75
  25. data/lib/active_record/associations/has_many_through_association.rb +51 -69
  26. data/lib/active_record/associations/has_one_association.rb +39 -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 -81
  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 +134 -154
  32. data/lib/active_record/associations/preloader/association.rb +85 -116
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +83 -93
  35. data/lib/active_record/associations/singular_association.rb +27 -40
  36. data/lib/active_record/associations/through_association.rb +48 -23
  37. data/lib/active_record/associations.rb +1732 -1596
  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 +12 -5
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -125
  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 +62 -36
  47. data/lib/active_record/attribute_methods/write.rb +31 -46
  48. data/lib/active_record/attribute_methods.rb +170 -117
  49. data/lib/active_record/attributes.rb +201 -74
  50. data/lib/active_record/autosave_association.rb +118 -45
  51. data/lib/active_record/base.rb +60 -48
  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 +37 -13
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +254 -87
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +72 -22
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -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 +617 -212
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +139 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +332 -191
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +567 -563
  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 +42 -195
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -115
  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 -57
  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 +5 -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 -13
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -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 -1
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +65 -51
  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 +466 -280
  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 +439 -330
  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 -324
  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 +205 -202
  129. data/lib/active_record/counter_cache.rb +80 -37
  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 +136 -90
  133. data/lib/active_record/errors.rb +180 -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 +11 -6
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +193 -135
  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 +48 -0
  144. data/lib/active_record/locale/en.yml +3 -2
  145. data/lib/active_record/locking/optimistic.rb +92 -98
  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 +594 -267
  152. data/lib/active_record/model_schema.rb +292 -111
  153. data/lib/active_record/nested_attributes.rb +266 -214
  154. data/lib/active_record/no_touching.rb +8 -2
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +350 -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 +117 -35
  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 +160 -174
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +447 -288
  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 +259 -244
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +290 -253
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +91 -68
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -23
  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 +118 -92
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +446 -389
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +18 -16
  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 -339
  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 -19
  193. data/lib/active_record/scoping/default.rb +102 -84
  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 +136 -95
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +59 -89
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -31
  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 +208 -123
  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 -41
  212. data/lib/active_record/type/date_time.rb +4 -38
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
  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 +30 -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 +41 -32
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +36 -21
  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 -6
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -7
  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.rb +7 -5
  243. metadata +77 -53
  244. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  245. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  246. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  247. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  248. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  249. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  250. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  251. data/lib/active_record/attribute.rb +0 -149
  252. data/lib/active_record/attribute_set/builder.rb +0 -86
  253. data/lib/active_record/attribute_set.rb +0 -77
  254. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  255. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  256. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  257. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  258. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  259. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  260. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  261. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  262. data/lib/active_record/type/big_integer.rb +0 -13
  263. data/lib/active_record/type/binary.rb +0 -50
  264. data/lib/active_record/type/boolean.rb +0 -30
  265. data/lib/active_record/type/decimal.rb +0 -40
  266. data/lib/active_record/type/decorator.rb +0 -14
  267. data/lib/active_record/type/float.rb +0 -19
  268. data/lib/active_record/type/integer.rb +0 -55
  269. data/lib/active_record/type/mutable.rb +0 -16
  270. data/lib/active_record/type/numeric.rb +0 -36
  271. data/lib/active_record/type/string.rb +0 -36
  272. data/lib/active_record/type/time_value.rb +0 -38
  273. data/lib/active_record/type/value.rb +0 -101
  274. /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -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,107 +44,75 @@ 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_type = reflection.primary_key_type
65
- ids = Array(ids).reject { |id| id.blank? }
66
- ids.map! { |i| pk_type.type_cast_from_user(i) }
67
- replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
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) }
61
+
62
+ records = klass.where(primary_key => ids).index_by do |r|
63
+ r.public_send(primary_key)
64
+ end.values_at(*ids).compact
65
+
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)
70
+ else
71
+ replace(records)
72
+ end
68
73
  end
69
74
 
70
75
  def reset
71
76
  super
72
77
  @target = []
73
- end
74
-
75
- def select(*fields)
76
- if block_given?
77
- load_target.select.each { |e| yield e }
78
- else
79
- scope.select(*fields)
80
- end
78
+ @association_ids = nil
81
79
  end
82
80
 
83
81
  def find(*args)
84
- if block_given?
85
- load_target.find(*args) { |*block_args| yield(*block_args) }
86
- else
87
- if options[:inverse_of] && loaded?
88
- args_flatten = args.flatten
89
- raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
90
- result = find_by_scan(*args)
91
-
92
- result_size = Array(result).size
93
- if !result || result_size != args_flatten.size
94
- scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
95
- else
96
- result
97
- end
98
- else
99
- scope.find(*args)
100
- end
101
- end
102
- end
103
-
104
- def first(*args)
105
- first_nth_or_last(:first, *args)
106
- end
107
-
108
- def second(*args)
109
- first_nth_or_last(:second, *args)
110
- end
111
-
112
- def third(*args)
113
- first_nth_or_last(:third, *args)
114
- end
82
+ if options[:inverse_of] && loaded?
83
+ args_flatten = args.flatten
84
+ model = scope.klass
115
85
 
116
- def fourth(*args)
117
- first_nth_or_last(:fourth, *args)
118
- 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
119
90
 
120
- def fifth(*args)
121
- first_nth_or_last(:fifth, *args)
122
- end
91
+ result = find_by_scan(*args)
123
92
 
124
- def forty_two(*args)
125
- first_nth_or_last(:forty_two, *args)
126
- end
127
-
128
- def last(*args)
129
- first_nth_or_last(:last, *args)
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
98
+ end
99
+ else
100
+ scope.find(*args)
101
+ end
130
102
  end
131
103
 
132
104
  def build(attributes = {}, &block)
133
105
  if attributes.is_a?(Array)
134
106
  attributes.collect { |attr| build(attr, &block) }
135
107
  else
136
- add_to_target(build_record(attributes)) do |record|
137
- yield(record) if block_given?
138
- end
108
+ add_to_target(build_record(attributes, &block))
139
109
  end
140
110
  end
141
111
 
142
- def create(attributes = {}, &block)
143
- _create_record(attributes, &block)
144
- end
145
-
146
- def create!(attributes = {}, &block)
147
- _create_record(attributes, true, &block)
148
- end
149
-
150
- # Add +records+ to this association. Returns +self+ so method calls may
151
- # be chained. Since << flattens its argument list and inserts each record,
152
- # +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.
153
114
  def concat(*records)
115
+ records = records.flatten
154
116
  if owner.new_record?
155
117
  load_target
156
118
  concat_records(records)
@@ -193,12 +155,12 @@ module ActiveRecord
193
155
  end
194
156
 
195
157
  dependent = if dependent
196
- dependent
197
- elsif options[:dependent] == :destroy
198
- :delete_all
199
- else
200
- options[:dependent]
201
- end
158
+ dependent
159
+ elsif options[:dependent] == :destroy
160
+ :delete_all
161
+ else
162
+ options[:dependent]
163
+ end
202
164
 
203
165
  delete_or_nullify_all_records(dependent).tap do
204
166
  reset
@@ -216,32 +178,6 @@ module ActiveRecord
216
178
  end
217
179
  end
218
180
 
219
- # Count all records using SQL. Construct options and pass them with
220
- # scope to the target class's +count+.
221
- def count(column_name = nil, count_options = {})
222
- # TODO: Remove count_options argument as soon we remove support to
223
- # activerecord-deprecated_finders.
224
- column_name, count_options = nil, column_name if column_name.is_a?(Hash)
225
-
226
- relation = scope
227
- if association_scope.distinct_value
228
- # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
229
- column_name ||= reflection.klass.primary_key
230
- relation = relation.distinct
231
- end
232
-
233
- value = relation.count(column_name)
234
-
235
- limit = options[:limit]
236
- offset = options[:offset]
237
-
238
- if limit || offset
239
- [ [value - offset.to_i, 0].max, limit.to_i ].min
240
- else
241
- value
242
- end
243
- end
244
-
245
181
  # Removes +records+ from this association calling +before_remove+ and
246
182
  # +after_remove+ callbacks.
247
183
  #
@@ -250,12 +186,7 @@ module ActiveRecord
250
186
  # are actually removed from the database, that depends precisely on
251
187
  # +delete_records+. They are in any case removed from the collection.
252
188
  def delete(*records)
253
- return if records.empty?
254
- _options = records.extract_options!
255
- dependent = _options[:dependent] || options[:dependent]
256
-
257
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
258
- delete_or_destroy(records, dependent)
189
+ delete_or_destroy(records, options[:dependent])
259
190
  end
260
191
 
261
192
  # Deletes the +records+ and removes them from this association calling
@@ -264,8 +195,6 @@ module ActiveRecord
264
195
  # Note that this method removes records from the database ignoring the
265
196
  # +:dependent+ option.
266
197
  def destroy(*records)
267
- return if records.empty?
268
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
269
198
  delete_or_destroy(records, :destroy)
270
199
  end
271
200
 
@@ -281,30 +210,17 @@ module ActiveRecord
281
210
  # +count_records+, which is a method descendants have to provide.
282
211
  def size
283
212
  if !find_target? || loaded?
284
- if association_scope.distinct_value
285
- target.uniq.size
286
- else
287
- target.size
288
- end
289
- elsif !loaded? && !association_scope.group_values.empty?
213
+ target.size
214
+ elsif !association_scope.group_values.empty?
290
215
  load_target.size
291
- elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
292
- 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?)
293
218
  unsaved_records.size + count_records
294
219
  else
295
220
  count_records
296
221
  end
297
222
  end
298
223
 
299
- # Returns the size of the collection calling +size+ on the target.
300
- #
301
- # If the collection has been already loaded +length+ and +size+ are
302
- # equivalent. If not and you are going to need the records anyway this
303
- # method will take one less query. Otherwise +size+ is more efficient.
304
- def length
305
- load_target.size
306
- end
307
-
308
224
  # Returns true if the collection is empty.
309
225
  #
310
226
  # If the collection has been loaded
@@ -321,34 +237,6 @@ module ActiveRecord
321
237
  end
322
238
  end
323
239
 
324
- # Returns true if the collections is not empty.
325
- # Equivalent to +!collection.empty?+.
326
- def any?
327
- if block_given?
328
- load_target.any? { |*block_args| yield(*block_args) }
329
- else
330
- !empty?
331
- end
332
- end
333
-
334
- # Returns true if the collection has more than 1 record.
335
- # Equivalent to +collection.size > 1+.
336
- def many?
337
- if block_given?
338
- load_target.many? { |*block_args| yield(*block_args) }
339
- else
340
- size > 1
341
- end
342
- end
343
-
344
- def distinct
345
- seen = {}
346
- load_target.find_all do |record|
347
- seen[record.id] = true unless seen.key?(record.id)
348
- end
349
- end
350
- alias uniq distinct
351
-
352
240
  # Replace this collection with +other_array+. This will perform a diff
353
241
  # and delete/add only records that have changed.
354
242
  def replace(other_array)
@@ -361,6 +249,8 @@ module ActiveRecord
361
249
  replace_common_records_in_memory(other_array, original_target)
362
250
  if other_array != original_target
363
251
  transaction { replace_records(other_array, original_target) }
252
+ else
253
+ other_array
364
254
  end
365
255
  end
366
256
  end
@@ -393,25 +283,9 @@ module ActiveRecord
393
283
  replace_on_target(record, index, skip_callbacks, &block)
394
284
  end
395
285
 
396
- def replace_on_target(record, index, skip_callbacks)
397
- callback(:before_add, record) unless skip_callbacks
398
- yield(record) if block_given?
399
-
400
- if index
401
- @target[index] = record
402
- else
403
- @target << record
404
- end
405
-
406
- callback(:after_add, record) unless skip_callbacks
407
- set_inverse_instance(record)
408
-
409
- record
410
- end
411
-
412
- def scope(opts = {})
413
- scope = super()
414
- scope.none! if opts.fetch(:nullify, true) && null_scope?
286
+ def scope
287
+ scope = super
288
+ scope.none! if null_scope?
415
289
  scope
416
290
  end
417
291
 
@@ -419,32 +293,28 @@ module ActiveRecord
419
293
  owner.new_record? && !foreign_key_present?
420
294
  end
421
295
 
296
+ def find_from_target?
297
+ loaded? ||
298
+ owner.new_record? ||
299
+ target.any? { |record| record.new_record? || record.changed? }
300
+ end
301
+
422
302
  private
423
- def get_records
424
- if reflection.scope_chain.any?(&:any?) ||
425
- scope.eager_loading? ||
426
- klass.current_scope ||
427
- klass.default_scopes.any?
428
303
 
429
- return scope.to_a
430
- end
304
+ def find_target
305
+ scope = self.scope
306
+ return scope.to_a if skip_statement_cache?(scope)
431
307
 
432
- conn = klass.connection
433
- sc = reflection.association_scope_cache(conn, owner) do
434
- StatementCache.create(conn) { |params|
308
+ conn = klass.connection
309
+ sc = reflection.association_scope_cache(conn, owner) do |params|
435
310
  as = AssociationScope.create { params.bind }
436
- target_scope.merge as.scope(self, conn)
437
- }
438
- end
439
-
440
- binds = AssociationScope.get_bind_values(owner, reflection.chain)
441
- sc.execute binds, klass, klass.connection
442
- end
311
+ target_scope.merge!(as.scope(self))
312
+ end
443
313
 
444
- def find_target
445
- records = get_records
446
- records.each { |record| set_inverse_instance(record) }
447
- records
314
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
315
+ sc.execute(binds, conn) do |record|
316
+ set_inverse_instance(record)
317
+ end
448
318
  end
449
319
 
450
320
  # We have some records loaded from the database (persisted) and some that are
@@ -464,7 +334,7 @@ module ActiveRecord
464
334
  persisted.map! do |record|
465
335
  if mem_record = memory.delete(record)
466
336
 
467
- ((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|
468
338
  mem_record[name] = record[name]
469
339
  end
470
340
 
@@ -485,28 +355,35 @@ module ActiveRecord
485
355
  if attributes.is_a?(Array)
486
356
  attributes.collect { |attr| _create_record(attr, raise, &block) }
487
357
  else
358
+ record = build_record(attributes, &block)
488
359
  transaction do
489
- add_to_target(build_record(attributes)) do |record|
490
- yield(record) if block_given?
491
- 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
+ }
492
365
  end
366
+ raise ActiveRecord::Rollback unless result
493
367
  end
368
+ record
494
369
  end
495
370
  end
496
371
 
497
372
  # Do the relevant stuff to insert the given record into the association collection.
498
- def insert_record(record, validate = true, raise = false)
499
- raise NotImplementedError
500
- end
501
-
502
- def create_scope
503
- 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
504
379
  end
505
380
 
506
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) }
507
384
  records = records.flatten
508
385
  records.each { |record| raise_on_type_mismatch!(record) }
509
- existing_records = records.reject { |r| r.new_record? }
386
+ existing_records = records.reject(&:new_record?)
510
387
 
511
388
  if existing_records.empty?
512
389
  remove_records(existing_records, records, method)
@@ -520,20 +397,22 @@ module ActiveRecord
520
397
 
521
398
  delete_records(existing_records, method) if existing_records.any?
522
399
  records.each { |record| target.delete(record) }
400
+ @association_ids = nil
523
401
 
524
402
  records.each { |record| callback(:after_remove, record) }
525
403
  end
526
404
 
527
- # Delete the given records from the association, using one of the methods :destroy,
528
- # :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).
529
408
  def delete_records(records, method)
530
409
  raise NotImplementedError
531
410
  end
532
411
 
533
412
  def replace_records(new_target, original_target)
534
- delete(target - new_target)
413
+ delete(difference(target, new_target))
535
414
 
536
- unless concat(new_target - target)
415
+ unless concat(difference(new_target, target))
537
416
  @target = original_target
538
417
  raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
539
418
  "new records could not be saved."
@@ -543,24 +422,53 @@ module ActiveRecord
543
422
  end
544
423
 
545
424
  def replace_common_records_in_memory(new_target, original_target)
546
- common_records = new_target & original_target
425
+ common_records = intersection(new_target, original_target)
547
426
  common_records.each do |record|
548
427
  skip_callbacks = true
549
428
  replace_on_target(record, @target.index(record), skip_callbacks)
550
429
  end
551
430
  end
552
431
 
553
- def concat_records(records, should_raise = false)
432
+ def concat_records(records, raise = false)
554
433
  result = true
555
434
 
556
- records.flatten.each do |record|
435
+ records.each do |record|
557
436
  raise_on_type_mismatch!(record)
558
- add_to_target(record) do |rec|
559
- 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
560
443
  end
561
444
  end
562
445
 
563
- 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
564
472
  end
565
473
 
566
474
  def callback(method, record)
@@ -574,31 +482,12 @@ module ActiveRecord
574
482
  owner.class.send(full_callback_name)
575
483
  end
576
484
 
577
- # Should we deal with assoc.first or assoc.last by issuing an independent query to
578
- # the database, or by getting the target, and then taking the first/last item from that?
579
- #
580
- # If the args is just a non-empty options hash, go to the database.
581
- #
582
- # Otherwise, go to the database only if none of the following are true:
583
- # * target already loaded
584
- # * owner is new record
585
- # * target contains new or changed record(s)
586
- def fetch_first_nth_or_last_using_find?(args)
587
- if args.first.is_a?(Hash)
588
- true
589
- else
590
- !(loaded? ||
591
- owner.new_record? ||
592
- target.any? { |record| record.new_record? || record.changed? })
593
- end
594
- end
595
-
596
485
  def include_in_memory?(record)
597
486
  if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
598
487
  assoc = owner.association(reflection.through_reflection.name)
599
488
  assoc.reader.any? { |source|
600
- target = source.send(reflection.source_reflection.name)
601
- target.respond_to?(:include?) ? target.include?(record) : target == record
489
+ target_reflection = source.send(reflection.source_reflection.name)
490
+ target_reflection.respond_to?(:include?) ? target_reflection.include?(record) : target_reflection == record
602
491
  } || target.include?(record)
603
492
  else
604
493
  target.include?(record)
@@ -609,7 +498,7 @@ module ActiveRecord
609
498
  # specified, then #find scans the entire collection.
610
499
  def find_by_scan(*args)
611
500
  expects_array = args.first.kind_of?(Array)
612
- ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq
501
+ ids = args.flatten.compact.map(&:to_s).uniq
613
502
 
614
503
  if ids.size == 1
615
504
  id = ids.first
@@ -619,16 +508,6 @@ module ActiveRecord
619
508
  load_target.select { |r| ids.include?(r.id.to_s) }
620
509
  end
621
510
  end
622
-
623
- # Fetches the first/last using SQL if possible, otherwise from the target array.
624
- def first_nth_or_last(type, *args)
625
- args.shift if args.first.is_a?(Hash) && args.first.empty?
626
-
627
- collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
628
- collection.send(type, *args).tap do |record|
629
- set_inverse_instance record if record.is_a? ActiveRecord::Base
630
- end
631
- end
632
511
  end
633
512
  end
634
513
  end