activerecord 3.2.19 → 5.0.0

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