activerecord 3.2.22.5 → 4.2.11.3

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