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.
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