activerecord 3.2.22.5 → 5.2.8

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 (275) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -621
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +41 -46
  5. data/examples/performance.rb +55 -42
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +264 -236
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +127 -75
  11. data/lib/active_record/associations/association_scope.rb +126 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +78 -27
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
  14. data/lib/active_record/associations/builder/association.rb +117 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +135 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -54
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
  18. data/lib/active_record/associations/builder/has_many.rb +10 -64
  19. data/lib/active_record/associations/builder/has_one.rb +19 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +28 -18
  21. data/lib/active_record/associations/collection_association.rb +226 -293
  22. data/lib/active_record/associations/collection_proxy.rb +1067 -69
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +83 -47
  25. data/lib/active_record/associations/has_many_through_association.rb +98 -65
  26. data/lib/active_record/associations/has_one_association.rb +57 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +212 -164
  32. data/lib/active_record/associations/preloader/association.rb +95 -89
  33. data/lib/active_record/associations/preloader/through_association.rb +84 -44
  34. data/lib/active_record/associations/preloader.rb +123 -111
  35. data/lib/active_record/associations/singular_association.rb +33 -24
  36. data/lib/active_record/associations/through_association.rb +60 -26
  37. data/lib/active_record/associations.rb +1759 -1506
  38. data/lib/active_record/attribute_assignment.rb +60 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +113 -74
  42. data/lib/active_record/attribute_methods/primary_key.rb +106 -77
  43. data/lib/active_record/attribute_methods/query.rb +8 -5
  44. data/lib/active_record/attribute_methods/read.rb +63 -114
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -90
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
  47. data/lib/active_record/attribute_methods/write.rb +43 -45
  48. data/lib/active_record/attribute_methods.rb +366 -149
  49. data/lib/active_record/attributes.rb +266 -0
  50. data/lib/active_record/autosave_association.rb +312 -225
  51. data/lib/active_record/base.rb +114 -505
  52. data/lib/active_record/callbacks.rb +145 -67
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
  69. data/lib/active_record/connection_adapters/column.rb +50 -255
  70. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  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 +59 -210
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  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 +92 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  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 +41 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
  117. data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
  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 +545 -27
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +145 -0
  128. data/lib/active_record/core.rb +559 -0
  129. data/lib/active_record/counter_cache.rb +200 -105
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +107 -69
  132. data/lib/active_record/enum.rb +244 -0
  133. data/lib/active_record/errors.rb +245 -60
  134. data/lib/active_record/explain.rb +35 -71
  135. data/lib/active_record/explain_registry.rb +32 -0
  136. data/lib/active_record/explain_subscriber.rb +18 -9
  137. data/lib/active_record/fixture_set/file.rb +82 -0
  138. data/lib/active_record/fixtures.rb +418 -275
  139. data/lib/active_record/gem_version.rb +17 -0
  140. data/lib/active_record/inheritance.rb +209 -100
  141. data/lib/active_record/integration.rb +116 -21
  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 +9 -1
  145. data/lib/active_record/locking/optimistic.rb +107 -94
  146. data/lib/active_record/locking/pessimistic.rb +20 -8
  147. data/lib/active_record/log_subscriber.rb +99 -34
  148. data/lib/active_record/migration/command_recorder.rb +199 -64
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +17 -0
  151. data/lib/active_record/migration.rb +893 -296
  152. data/lib/active_record/model_schema.rb +328 -175
  153. data/lib/active_record/nested_attributes.rb +338 -242
  154. data/lib/active_record/no_touching.rb +58 -0
  155. data/lib/active_record/null_relation.rb +68 -0
  156. data/lib/active_record/persistence.rb +557 -170
  157. data/lib/active_record/query_cache.rb +14 -43
  158. data/lib/active_record/querying.rb +36 -24
  159. data/lib/active_record/railtie.rb +147 -52
  160. data/lib/active_record/railties/console_sandbox.rb +5 -4
  161. data/lib/active_record/railties/controller_runtime.rb +13 -6
  162. data/lib/active_record/railties/databases.rake +206 -488
  163. data/lib/active_record/readonly_attributes.rb +4 -6
  164. data/lib/active_record/reflection.rb +734 -228
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +249 -52
  167. data/lib/active_record/relation/calculations.rb +330 -284
  168. data/lib/active_record/relation/delegation.rb +135 -37
  169. data/lib/active_record/relation/finder_methods.rb +450 -287
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +193 -0
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  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 +19 -0
  179. data/lib/active_record/relation/predicate_builder.rb +132 -43
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +1037 -221
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +48 -151
  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 +451 -359
  187. data/lib/active_record/result.rb +129 -20
  188. data/lib/active_record/runtime_registry.rb +24 -0
  189. data/lib/active_record/sanitization.rb +164 -136
  190. data/lib/active_record/schema.rb +31 -19
  191. data/lib/active_record/schema_dumper.rb +154 -107
  192. data/lib/active_record/schema_migration.rb +56 -0
  193. data/lib/active_record/scoping/default.rb +108 -98
  194. data/lib/active_record/scoping/named.rb +125 -112
  195. data/lib/active_record/scoping.rb +77 -123
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +10 -6
  198. data/lib/active_record/statement_cache.rb +121 -0
  199. data/lib/active_record/store.rb +175 -16
  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 +337 -0
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  206. data/lib/active_record/timestamp.rb +80 -41
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +240 -119
  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 +9 -0
  212. data/lib/active_record/type/date_time.rb +9 -0
  213. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  214. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  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 +71 -0
  218. data/lib/active_record/type/text.rb +11 -0
  219. data/lib/active_record/type/time.rb +21 -0
  220. data/lib/active_record/type/type_map.rb +62 -0
  221. data/lib/active_record/type/unsigned_integer.rb +17 -0
  222. data/lib/active_record/type.rb +79 -0
  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 +35 -18
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +68 -0
  230. data/lib/active_record/validations/uniqueness.rb +133 -75
  231. data/lib/active_record/validations.rb +53 -43
  232. data/lib/active_record/version.rb +7 -7
  233. data/lib/active_record.rb +89 -57
  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 +61 -8
  237. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  238. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  239. data/lib/rails/generators/active_record/migration.rb +28 -8
  240. data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
  241. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  243. data/lib/rails/generators/active_record.rb +10 -16
  244. metadata +141 -62
  245. data/examples/associations.png +0 -0
  246. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  247. data/lib/active_record/associations/join_helper.rb +0 -55
  248. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  249. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  250. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  251. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  252. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  253. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  254. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  255. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  256. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  257. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  258. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  259. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  260. data/lib/active_record/dynamic_finder_match.rb +0 -68
  261. data/lib/active_record/dynamic_scope_match.rb +0 -23
  262. data/lib/active_record/fixtures/file.rb +0 -65
  263. data/lib/active_record/identity_map.rb +0 -162
  264. data/lib/active_record/observer.rb +0 -121
  265. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  266. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  267. data/lib/active_record/session_store.rb +0 -360
  268. data/lib/active_record/test_case.rb +0 -73
  269. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  270. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  271. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  272. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  273. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  274. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  275. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,4 +1,4 @@
1
- require 'active_support/core_ext/array/wrap'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
4
  module Associations
@@ -6,7 +6,15 @@ module ActiveRecord
6
6
  #
7
7
  # CollectionAssociation is an abstract class that provides common stuff to
8
8
  # ease the implementation of association proxies that represent
9
- # collections. See the class hierarchy in AssociationProxy.
9
+ # collections. See the class hierarchy in Association.
10
+ #
11
+ # CollectionAssociation:
12
+ # HasManyAssociation => has_many
13
+ # HasManyThroughAssociation + ThroughAssociation => has_many :through
14
+ #
15
+ # The CollectionAssociation class provides common methods to the collections
16
+ # defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
17
+ # the +:through association+ option.
10
18
  #
11
19
  # You need to be careful with assumptions regarding the target: The proxy
12
20
  # does not fetch records from the database until it needs them, but new
@@ -18,22 +26,14 @@ module ActiveRecord
18
26
  # If you need to work on all current children, new and existing records,
19
27
  # +load_target+ and the +loaded+ flag are your friends.
20
28
  class CollectionAssociation < Association #:nodoc:
21
- attr_reader :proxy
22
-
23
- def initialize(owner, reflection)
24
- super
25
- @proxy = CollectionProxy.new(self)
26
- end
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
- proxy
35
+ @proxy ||= CollectionProxy.create(klass, self)
36
+ @proxy.reset_scope
37
37
  end
38
38
 
39
39
  # Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -43,92 +43,78 @@ module ActiveRecord
43
43
 
44
44
  # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
45
45
  def ids_reader
46
- if owner.new_record? || loaded? || options[:finder_sql]
47
- load_target.map do |record|
48
- record.send(reflection.association_primary_key)
49
- end
46
+ if loaded?
47
+ target.pluck(reflection.association_primary_key)
48
+ elsif !target.empty?
49
+ load_target.pluck(reflection.association_primary_key)
50
50
  else
51
- column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
52
- relation = scoped
53
-
54
- including = (relation.eager_load_values + relation.includes_values).uniq
55
-
56
- if including.any?
57
- join_dependency = ActiveRecord::Associations::JoinDependency.new(reflection.klass, including, [])
58
- relation = join_dependency.join_associations.inject(relation) do |r, association|
59
- association.join_relation(r)
60
- end
61
- end
62
-
63
- relation.pluck(column)
51
+ @association_ids ||= scope.pluck(reflection.association_primary_key)
64
52
  end
65
53
  end
66
54
 
67
55
  # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
68
56
  def ids_writer(ids)
69
- pk_column = reflection.primary_key_column
70
- ids = Array.wrap(ids).reject { |id| id.blank? }
71
- ids.map! { |i| pk_column.type_cast(i) }
72
- 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
73
73
  end
74
74
 
75
75
  def reset
76
- @loaded = false
76
+ super
77
77
  @target = []
78
- end
79
-
80
- def select(select = nil)
81
- if block_given?
82
- load_target.select.each { |e| yield e }
83
- else
84
- scoped.select(select)
85
- end
78
+ @association_ids = nil
86
79
  end
87
80
 
88
81
  def find(*args)
89
- if block_given?
90
- load_target.find(*args) { |*block_args| yield(*block_args) }
91
- else
92
- if options[:finder_sql]
93
- find_by_scan(*args)
94
- else
95
- scoped.find(*args)
82
+ if options[:inverse_of] && loaded?
83
+ args_flatten = args.flatten
84
+ model = scope.klass
85
+
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)
96
89
  end
97
- end
98
- end
99
90
 
100
- def first(*args)
101
- first_or_last(:first, *args)
102
- end
91
+ result = find_by_scan(*args)
103
92
 
104
- def last(*args)
105
- first_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
106
102
  end
107
103
 
108
- def build(attributes = {}, options = {}, &block)
104
+ def build(attributes = {}, &block)
109
105
  if attributes.is_a?(Array)
110
- attributes.collect { |attr| build(attr, options, &block) }
106
+ attributes.collect { |attr| build(attr, &block) }
111
107
  else
112
- add_to_target(build_record(attributes, options)) do |record|
113
- yield(record) if block_given?
114
- end
108
+ add_to_target(build_record(attributes, &block))
115
109
  end
116
110
  end
117
111
 
118
- def create(attributes = {}, options = {}, &block)
119
- create_record(attributes, options, &block)
120
- end
121
-
122
- def create!(attributes = {}, options = {}, &block)
123
- create_record(attributes, options, true, &block)
124
- end
125
-
126
- # Add +records+ to this association. Returns +self+ so method calls may be chained.
127
- # Since << flattens its argument list and inserts each record, +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.
128
114
  def concat(*records)
129
- load_target if owner.new_record?
130
-
115
+ records = records.flatten
131
116
  if owner.new_record?
117
+ load_target
132
118
  concat_records(records)
133
119
  else
134
120
  transaction { concat_records(records) }
@@ -150,23 +136,38 @@ module ActiveRecord
150
136
  end
151
137
  end
152
138
 
153
- # Remove all records from this association
139
+ # Removes all records from the association without calling callbacks
140
+ # on the associated records. It honors the +:dependent+ option. However
141
+ # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
142
+ # deletion strategy for the association is applied.
143
+ #
144
+ # You can force a particular deletion strategy by passing a parameter.
145
+ #
146
+ # Example:
147
+ #
148
+ # @author.books.delete_all(:nullify)
149
+ # @author.books.delete_all(:delete_all)
154
150
  #
155
151
  # See delete for more info.
156
- def delete_all
157
- delete(load_target).tap do
152
+ def delete_all(dependent = nil)
153
+ if dependent && ![:nullify, :delete_all].include?(dependent)
154
+ raise ArgumentError, "Valid values are :nullify or :delete_all"
155
+ end
156
+
157
+ dependent = if dependent
158
+ dependent
159
+ elsif options[:dependent] == :destroy
160
+ :delete_all
161
+ else
162
+ options[:dependent]
163
+ end
164
+
165
+ delete_or_nullify_all_records(dependent).tap do
158
166
  reset
159
167
  loaded!
160
168
  end
161
169
  end
162
170
 
163
- # Called when the association is declared as :dependent => :delete_all. This is
164
- # an optimised version which avoids loading the records into memory. Not really
165
- # for public consumption.
166
- def delete_all_on_destroy
167
- scoped.delete_all
168
- end
169
-
170
171
  # Destroy all the records from this association.
171
172
  #
172
173
  # See destroy for more info.
@@ -177,49 +178,6 @@ module ActiveRecord
177
178
  end
178
179
  end
179
180
 
180
- # Calculate sum using SQL, not Enumerable
181
- def sum(*args)
182
- if block_given?
183
- scoped.sum(*args) { |*block_args| yield(*block_args) }
184
- else
185
- scoped.sum(*args)
186
- end
187
- end
188
-
189
- # Count all records using SQL. If the +:counter_sql+ or +:finder_sql+ option is set for the
190
- # association, it will be used for the query. Otherwise, construct options and pass them with
191
- # scope to the target class's +count+.
192
- def count(column_name = nil, count_options = {})
193
- return 0 if owner.new_record?
194
-
195
- column_name, count_options = nil, column_name if column_name.is_a?(Hash)
196
-
197
- if options[:counter_sql] || options[:finder_sql]
198
- unless count_options.blank?
199
- raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed"
200
- end
201
-
202
- reflection.klass.count_by_sql(custom_counter_sql)
203
- else
204
- if options[:uniq]
205
- # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
206
- column_name ||= reflection.klass.primary_key
207
- count_options.merge!(:distinct => true)
208
- end
209
-
210
- value = scoped.count(column_name, count_options)
211
-
212
- limit = options[:limit]
213
- offset = options[:offset]
214
-
215
- if limit || offset
216
- [ [value - offset.to_i, 0].max, limit.to_i ].min
217
- else
218
- value
219
- end
220
- end
221
- end
222
-
223
181
  # Removes +records+ from this association calling +before_remove+ and
224
182
  # +after_remove+ callbacks.
225
183
  #
@@ -231,13 +189,12 @@ module ActiveRecord
231
189
  delete_or_destroy(records, options[:dependent])
232
190
  end
233
191
 
234
- # Destroy +records+ and remove them from this association calling
235
- # +before_remove+ and +after_remove+ callbacks.
192
+ # Deletes the +records+ and removes them from this association calling
193
+ # +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
236
194
  #
237
- # Note that this method will _always_ remove records from the database
238
- # ignoring the +:dependent+ option.
195
+ # Note that this method removes records from the database ignoring the
196
+ # +:dependent+ option.
239
197
  def destroy(*records)
240
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
241
198
  delete_or_destroy(records, :destroy)
242
199
  end
243
200
 
@@ -252,68 +209,49 @@ module ActiveRecord
252
209
  # This method is abstract in the sense that it relies on
253
210
  # +count_records+, which is a method descendants have to provide.
254
211
  def size
255
- if !find_target? || (loaded? && !options[:uniq])
212
+ if !find_target? || loaded?
256
213
  target.size
257
- elsif !loaded? && options[:group]
214
+ elsif !association_scope.group_values.empty?
258
215
  load_target.size
259
- elsif !loaded? && !options[:uniq] && target.is_a?(Array)
260
- 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?)
261
218
  unsaved_records.size + count_records
262
219
  else
263
220
  count_records
264
221
  end
265
222
  end
266
223
 
267
- # Returns the size of the collection calling +size+ on the target.
224
+ # Returns true if the collection is empty.
268
225
  #
269
- # If the collection has been already loaded +length+ and +size+ are
270
- # equivalent. If not and you are going to need the records anyway this
271
- # method will take one less query. Otherwise +size+ is more efficient.
272
- def length
273
- load_target.size
274
- end
275
-
276
- # Equivalent to <tt>collection.size.zero?</tt>. If the collection has
277
- # not been already loaded and you are going to fetch the records anyway
278
- # it is better to check <tt>collection.length.zero?</tt>.
226
+ # If the collection has been loaded
227
+ # it is equivalent to <tt>collection.size.zero?</tt>. If the
228
+ # collection has not been loaded, it is equivalent to
229
+ # <tt>collection.exists?</tt>. If the collection has not already been
230
+ # loaded and you are going to fetch the records anyway it is better to
231
+ # check <tt>collection.length.zero?</tt>.
279
232
  def empty?
280
- size.zero?
281
- end
282
-
283
- def any?
284
- if block_given?
285
- load_target.any? { |*block_args| yield(*block_args) }
286
- else
287
- !empty?
288
- end
289
- end
290
-
291
- # Returns true if the collection has more than 1 record. Equivalent to collection.size > 1.
292
- def many?
293
- if block_given?
294
- load_target.many? { |*block_args| yield(*block_args) }
233
+ if loaded?
234
+ size.zero?
295
235
  else
296
- size > 1
297
- end
298
- end
299
-
300
- def uniq(collection = load_target)
301
- seen = {}
302
- collection.find_all do |record|
303
- seen[record.id] = true unless seen.key?(record.id)
236
+ @target.blank? && !scope.exists?
304
237
  end
305
238
  end
306
239
 
307
- # Replace this collection with +other_array+
308
- # This will perform a diff and delete/add only records that have changed.
240
+ # Replace this collection with +other_array+. This will perform a diff
241
+ # and delete/add only records that have changed.
309
242
  def replace(other_array)
310
- other_array.each { |val| raise_on_type_mismatch(val) }
243
+ other_array.each { |val| raise_on_type_mismatch!(val) }
311
244
  original_target = load_target.dup
312
245
 
313
246
  if owner.new_record?
314
247
  replace_records(other_array, original_target)
315
248
  else
316
- transaction { replace_records(other_array, original_target) }
249
+ replace_common_records_in_memory(other_array, original_target)
250
+ if other_array != original_target
251
+ transaction { replace_records(other_array, original_target) }
252
+ else
253
+ other_array
254
+ end
317
255
  end
318
256
  end
319
257
 
@@ -322,8 +260,7 @@ module ActiveRecord
322
260
  if record.new_record?
323
261
  include_in_memory?(record)
324
262
  else
325
- load_target if options[:finder_sql]
326
- loaded? ? target.include?(record) : scoped.exists?(record)
263
+ loaded? ? target.include?(record) : scope.exists?(record.id)
327
264
  end
328
265
  else
329
266
  false
@@ -339,52 +276,45 @@ module ActiveRecord
339
276
  target
340
277
  end
341
278
 
342
- def add_to_target(record)
343
- callback(:before_add, record)
344
- yield(record) if block_given?
345
-
346
- if options[:uniq] && index = @target.index(record)
347
- @target[index] = record
348
- else
349
- @target << record
279
+ def add_to_target(record, skip_callbacks = false, &block)
280
+ if association_scope.distinct_value
281
+ index = @target.index(record)
350
282
  end
283
+ replace_on_target(record, index, skip_callbacks, &block)
284
+ end
351
285
 
352
- callback(:after_add, record)
353
- set_inverse_instance(record)
354
-
355
- record
286
+ def scope
287
+ scope = super
288
+ scope.none! if null_scope?
289
+ scope
356
290
  end
357
291
 
358
- private
292
+ def null_scope?
293
+ owner.new_record? && !foreign_key_present?
294
+ end
359
295
 
360
- def custom_counter_sql
361
- if options[:counter_sql]
362
- interpolate(options[:counter_sql])
363
- else
364
- # replace the SELECT clause with COUNT(SELECTS), preserving any hints within /* ... */
365
- interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) do
366
- count_with = $2.to_s
367
- count_with = '*' if count_with.blank? || count_with =~ /,/ || count_with =~ /\.\*/
368
- "SELECT #{$1}COUNT(#{count_with}) FROM"
369
- end
370
- end
371
- end
296
+ def find_from_target?
297
+ loaded? ||
298
+ owner.new_record? ||
299
+ target.any? { |record| record.new_record? || record.changed? }
300
+ end
372
301
 
373
- def custom_finder_sql
374
- interpolate(options[:finder_sql])
375
- end
302
+ private
376
303
 
377
304
  def find_target
378
- records =
379
- if options[:finder_sql]
380
- reflection.klass.find_by_sql(custom_finder_sql)
381
- else
382
- scoped.all
383
- end
305
+ scope = self.scope
306
+ return scope.to_a if skip_statement_cache?(scope)
384
307
 
385
- records = options[:uniq] ? uniq(records) : records
386
- records.each { |record| set_inverse_instance(record) }
387
- records
308
+ conn = klass.connection
309
+ sc = reflection.association_scope_cache(conn, owner) do |params|
310
+ as = AssociationScope.create { params.bind }
311
+ target_scope.merge!(as.scope(self))
312
+ end
313
+
314
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
315
+ sc.execute(binds, conn) do |record|
316
+ set_inverse_instance(record)
317
+ end
388
318
  end
389
319
 
390
320
  # We have some records loaded from the database (persisted) and some that are
@@ -402,14 +332,9 @@ module ActiveRecord
402
332
  return memory if persisted.empty?
403
333
 
404
334
  persisted.map! do |record|
405
- # Unfortunately we cannot simply do memory.delete(record) since on 1.8 this returns
406
- # record rather than memory.at(memory.index(record)). The behavior is fixed in 1.9.
407
- mem_index = memory.index(record)
408
-
409
- if mem_index
410
- mem_record = memory.delete_at(mem_index)
335
+ if mem_record = memory.delete(record)
411
336
 
412
- ((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|
413
338
  mem_record[name] = record[name]
414
339
  end
415
340
 
@@ -422,36 +347,43 @@ module ActiveRecord
422
347
  persisted + memory
423
348
  end
424
349
 
425
- def create_record(attributes, options, raise = false, &block)
350
+ def _create_record(attributes, raise = false, &block)
426
351
  unless owner.persisted?
427
352
  raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
428
353
  end
429
354
 
430
355
  if attributes.is_a?(Array)
431
- attributes.collect { |attr| create_record(attr, options, raise, &block) }
356
+ attributes.collect { |attr| _create_record(attr, raise, &block) }
432
357
  else
358
+ record = build_record(attributes, &block)
433
359
  transaction do
434
- add_to_target(build_record(attributes, options)) do |record|
435
- yield(record) if block_given?
436
- 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
+ }
437
365
  end
366
+ raise ActiveRecord::Rollback unless result
438
367
  end
368
+ record
439
369
  end
440
370
  end
441
371
 
442
372
  # Do the relevant stuff to insert the given record into the association collection.
443
- def insert_record(record, validate = true, raise = false)
444
- raise NotImplementedError
445
- end
446
-
447
- def create_scope
448
- scoped.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
449
379
  end
450
380
 
451
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) }
452
384
  records = records.flatten
453
- records.each { |record| raise_on_type_mismatch(record) }
454
- existing_records = records.reject { |r| r.new_record? }
385
+ records.each { |record| raise_on_type_mismatch!(record) }
386
+ existing_records = records.reject(&:new_record?)
455
387
 
456
388
  if existing_records.empty?
457
389
  remove_records(existing_records, records, method)
@@ -465,20 +397,22 @@ module ActiveRecord
465
397
 
466
398
  delete_records(existing_records, method) if existing_records.any?
467
399
  records.each { |record| target.delete(record) }
400
+ @association_ids = nil
468
401
 
469
402
  records.each { |record| callback(:after_remove, record) }
470
403
  end
471
404
 
472
- # Delete the given records from the association, using one of the methods :destroy,
473
- # :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).
474
408
  def delete_records(records, method)
475
409
  raise NotImplementedError
476
410
  end
477
411
 
478
412
  def replace_records(new_target, original_target)
479
- delete(target - new_target)
413
+ delete(difference(target, new_target))
480
414
 
481
- unless concat(new_target - target)
415
+ unless concat(difference(new_target, target))
482
416
  @target = original_target
483
417
  raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
484
418
  "new records could not be saved."
@@ -487,92 +421,91 @@ module ActiveRecord
487
421
  target
488
422
  end
489
423
 
490
- def concat_records(records)
424
+ def replace_common_records_in_memory(new_target, original_target)
425
+ common_records = intersection(new_target, original_target)
426
+ common_records.each do |record|
427
+ skip_callbacks = true
428
+ replace_on_target(record, @target.index(record), skip_callbacks)
429
+ end
430
+ end
431
+
432
+ def concat_records(records, raise = false)
491
433
  result = true
492
434
 
493
- records.flatten.each do |record|
494
- raise_on_type_mismatch(record)
495
- add_to_target(record) do |r|
496
- result &&= insert_record(record) unless owner.new_record?
435
+ records.each do |record|
436
+ raise_on_type_mismatch!(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
497
443
  end
498
444
  end
499
445
 
500
- 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
501
472
  end
502
473
 
503
474
  def callback(method, record)
504
475
  callbacks_for(method).each do |callback|
505
- case callback
506
- when Symbol
507
- owner.send(callback, record)
508
- when Proc
509
- callback.call(owner, record)
510
- else
511
- callback.send(method, owner, record)
512
- end
476
+ callback.call(method, owner, record)
513
477
  end
514
478
  end
515
479
 
516
480
  def callbacks_for(callback_name)
517
481
  full_callback_name = "#{callback_name}_for_#{reflection.name}"
518
- owner.class.send(full_callback_name.to_sym) || []
519
- end
520
-
521
- # Should we deal with assoc.first or assoc.last by issuing an independent query to
522
- # the database, or by getting the target, and then taking the first/last item from that?
523
- #
524
- # If the args is just a non-empty options hash, go to the database.
525
- #
526
- # Otherwise, go to the database only if none of the following are true:
527
- # * target already loaded
528
- # * owner is new record
529
- # * custom :finder_sql exists
530
- # * target contains new or changed record(s)
531
- # * the first arg is an integer (which indicates the number of records to be returned)
532
- def fetch_first_or_last_using_find?(args)
533
- if args.first.is_a?(Hash)
534
- true
535
- else
536
- !(loaded? ||
537
- owner.new_record? ||
538
- options[:finder_sql] ||
539
- target.any? { |record| record.new_record? || record.changed? } ||
540
- args.first.kind_of?(Integer))
541
- end
482
+ owner.class.send(full_callback_name)
542
483
  end
543
484
 
544
485
  def include_in_memory?(record)
545
486
  if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
546
- owner.send(reflection.through_reflection.name).any? { |source|
547
- target = source.send(reflection.source_reflection.name)
548
- target.respond_to?(:include?) ? target.include?(record) : target == record
487
+ assoc = owner.association(reflection.through_reflection.name)
488
+ assoc.reader.any? { |source|
489
+ target_reflection = source.send(reflection.source_reflection.name)
490
+ target_reflection.respond_to?(:include?) ? target_reflection.include?(record) : target_reflection == record
549
491
  } || target.include?(record)
550
492
  else
551
493
  target.include?(record)
552
494
  end
553
495
  end
554
496
 
555
- # If using a custom finder_sql, #find scans the entire collection.
497
+ # If the :inverse_of option has been
498
+ # specified, then #find scans the entire collection.
556
499
  def find_by_scan(*args)
557
500
  expects_array = args.first.kind_of?(Array)
558
- ids = args.flatten.compact.uniq.map { |arg| arg.to_i }
501
+ ids = args.flatten.compact.map(&:to_s).uniq
559
502
 
560
503
  if ids.size == 1
561
504
  id = ids.first
562
- record = load_target.detect { |r| id == r.id }
505
+ record = load_target.detect { |r| id == r.id.to_s }
563
506
  expects_array ? [ record ] : record
564
507
  else
565
- load_target.select { |r| ids.include?(r.id) }
566
- end
567
- end
568
-
569
- # Fetches the first/last using SQL if possible, otherwise from the target array.
570
- def first_or_last(type, *args)
571
- args.shift if args.first.is_a?(Hash) && args.first.empty?
572
-
573
- collection = fetch_first_or_last_using_find?(args) ? scoped : load_target
574
- collection.send(type, *args).tap do |record|
575
- set_inverse_instance record if record.is_a? ActiveRecord::Base
508
+ load_target.select { |r| ids.include?(r.id.to_s) }
576
509
  end
577
510
  end
578
511
  end