activerecord 3.1.10 → 4.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  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 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  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 -102
  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 +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  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 +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  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 +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  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 +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  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 +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  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 +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  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 +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  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 +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  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 +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  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 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  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 +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -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,37 +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 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
-
53
- scoped.select(column).except(:includes).map! do |record|
54
- record.send(reflection.association_primary_key)
55
- end
58
+ scope.pluck(column)
56
59
  end
57
60
  end
58
61
 
59
62
  # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
60
63
  def ids_writer(ids)
61
- pk_column = reflection.primary_key_column
62
- ids = Array.wrap(ids).reject { |id| id.blank? }
63
- ids.map! { |i| pk_column.type_cast(i) }
64
- 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
65
79
  end
66
80
 
67
81
  def reset
68
- @loaded = false
82
+ super
69
83
  @target = []
70
84
  end
71
85
 
72
- def select(select = nil)
86
+ def select(*fields)
73
87
  if block_given?
74
88
  load_target.select.each { |e| yield e }
75
89
  else
76
- scoped.select(select)
90
+ scope.select(*fields)
77
91
  end
78
92
  end
79
93
 
@@ -81,56 +95,89 @@ module ActiveRecord
81
95
  if block_given?
82
96
  load_target.find(*args) { |*block_args| yield(*block_args) }
83
97
  else
84
- if options[:finder_sql]
85
- 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
86
109
  else
87
- scoped.find(*args)
110
+ scope.find(*args)
88
111
  end
89
112
  end
90
113
  end
91
114
 
92
115
  def first(*args)
93
- 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)
94
137
  end
95
138
 
96
139
  def last(*args)
97
- first_or_last(:last, *args)
140
+ first_nth_or_last(:last, *args)
98
141
  end
99
142
 
100
- def build(attributes = {}, options = {}, &block)
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
151
+ end
152
+
153
+ def build(attributes = {}, &block)
101
154
  if attributes.is_a?(Array)
102
- attributes.collect { |attr| build(attr, options, &block) }
155
+ attributes.collect { |attr| build(attr, &block) }
103
156
  else
104
- add_to_target(build_record(attributes, options)) do |record|
157
+ add_to_target(build_record(attributes)) do |record|
105
158
  yield(record) if block_given?
106
159
  end
107
160
  end
108
161
  end
109
162
 
110
- def create(attributes = {}, options = {}, &block)
111
- create_record(attributes, options, &block)
163
+ def create(attributes = {}, &block)
164
+ _create_record(attributes, &block)
112
165
  end
113
166
 
114
- def create!(attributes = {}, options = {}, &block)
115
- create_record(attributes, options, true, &block)
167
+ def create!(attributes = {}, &block)
168
+ _create_record(attributes, true, &block)
116
169
  end
117
170
 
118
- # Add +records+ to this association. Returns +self+ so method calls may be chained.
119
- # 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.
120
174
  def concat(*records)
121
- result = true
122
- load_target if owner.new_record?
123
-
124
- transaction do
125
- records.flatten.each do |record|
126
- raise_on_type_mismatch(record)
127
- add_to_target(record) do |r|
128
- result &&= insert_record(record) unless owner.new_record?
129
- end
130
- end
175
+ if owner.new_record?
176
+ load_target
177
+ concat_records(records)
178
+ else
179
+ transaction { concat_records(records) }
131
180
  end
132
-
133
- result && records
134
181
  end
135
182
 
136
183
  # Starts a transaction in the association class's database connection.
@@ -148,23 +195,38 @@ module ActiveRecord
148
195
  end
149
196
  end
150
197
 
151
- # 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)
152
209
  #
153
210
  # See delete for more info.
154
- def delete_all
155
- 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
156
225
  reset
157
226
  loaded!
158
227
  end
159
228
  end
160
229
 
161
- # Called when the association is declared as :dependent => :delete_all. This is
162
- # an optimised version which avoids loading the records into memory. Not really
163
- # for public consumption.
164
- def delete_all_on_destroy
165
- scoped.delete_all
166
- end
167
-
168
230
  # Destroy all the records from this association.
169
231
  #
170
232
  # See destroy for more info.
@@ -175,44 +237,29 @@ module ActiveRecord
175
237
  end
176
238
  end
177
239
 
178
- # Calculate sum using SQL, not Enumerable
179
- def sum(*args)
180
- if block_given?
181
- scoped.sum(*args) { |*block_args| yield(*block_args) }
182
- else
183
- scoped.sum(*args)
184
- end
185
- end
186
-
187
- # Count all records using SQL. If the +:counter_sql+ or +:finder_sql+ option is set for the
188
- # 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
189
241
  # scope to the target class's +count+.
190
242
  def count(column_name = nil, count_options = {})
243
+ # TODO: Remove count_options argument as soon we remove support to
244
+ # activerecord-deprecated_finders.
191
245
  column_name, count_options = nil, column_name if column_name.is_a?(Hash)
192
246
 
193
- if options[:counter_sql] || options[:finder_sql]
194
- unless count_options.blank?
195
- raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed"
196
- end
197
-
198
- reflection.klass.count_by_sql(custom_counter_sql)
199
- else
200
- if options[:uniq]
201
- # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
202
- column_name ||= reflection.klass.primary_key
203
- count_options.merge!(:distinct => true)
204
- 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
205
253
 
206
- value = scoped.count(column_name, count_options)
254
+ value = relation.count(column_name)
207
255
 
208
- limit = options[:limit]
209
- offset = options[:offset]
256
+ limit = options[:limit]
257
+ offset = options[:offset]
210
258
 
211
- if limit || offset
212
- [ [value - offset.to_i, 0].max, limit.to_i ].min
213
- else
214
- value
215
- end
259
+ if limit || offset
260
+ [ [value - offset.to_i, 0].max, limit.to_i ].min
261
+ else
262
+ value
216
263
  end
217
264
  end
218
265
 
@@ -224,16 +271,22 @@ module ActiveRecord
224
271
  # are actually removed from the database, that depends precisely on
225
272
  # +delete_records+. They are in any case removed from the collection.
226
273
  def delete(*records)
227
- 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)
228
280
  end
229
281
 
230
- # Destroy +records+ and remove them from this association calling
231
- # +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.
232
284
  #
233
- # Note that this method will _always_ remove records from the database
234
- # ignoring the +:dependent+ option.
285
+ # Note that this method removes records from the database ignoring the
286
+ # +:dependent+ option.
235
287
  def destroy(*records)
236
- 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) }
237
290
  delete_or_destroy(records, :destroy)
238
291
  end
239
292
 
@@ -248,11 +301,15 @@ module ActiveRecord
248
301
  # This method is abstract in the sense that it relies on
249
302
  # +count_records+, which is a method descendants have to provide.
250
303
  def size
251
- if owner.new_record? || (loaded? && !options[:uniq])
252
- target.size
253
- 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?
254
311
  load_target.size
255
- elsif !loaded? && !options[:uniq] && target.is_a?(Array)
312
+ elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
256
313
  unsaved_records = target.select { |r| r.new_record? }
257
314
  unsaved_records.size + count_records
258
315
  else
@@ -269,13 +326,24 @@ module ActiveRecord
269
326
  load_target.size
270
327
  end
271
328
 
272
- # Equivalent to <tt>collection.size.zero?</tt>. If the collection has
273
- # not been already loaded and you are going to fetch the records anyway
274
- # 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>.
275
337
  def empty?
276
- size.zero?
338
+ if loaded?
339
+ size.zero?
340
+ else
341
+ @target.blank? && !scope.exists?
342
+ end
277
343
  end
278
344
 
345
+ # Returns true if the collections is not empty.
346
+ # Equivalent to +!collection.empty?+.
279
347
  def any?
280
348
  if block_given?
281
349
  load_target.any? { |*block_args| yield(*block_args) }
@@ -284,7 +352,8 @@ module ActiveRecord
284
352
  end
285
353
  end
286
354
 
287
- # 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+.
288
357
  def many?
289
358
  if block_given?
290
359
  load_target.many? { |*block_args| yield(*block_args) }
@@ -293,26 +362,26 @@ module ActiveRecord
293
362
  end
294
363
  end
295
364
 
296
- def uniq(collection = load_target)
365
+ def distinct
297
366
  seen = {}
298
- collection.find_all do |record|
367
+ load_target.find_all do |record|
299
368
  seen[record.id] = true unless seen.key?(record.id)
300
369
  end
301
370
  end
371
+ alias uniq distinct
302
372
 
303
- # Replace this collection with +other_array+
304
- # 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.
305
375
  def replace(other_array)
306
- other_array.each { |val| raise_on_type_mismatch(val) }
376
+ other_array.each { |val| raise_on_type_mismatch!(val) }
307
377
  original_target = load_target.dup
308
378
 
309
- transaction do
310
- delete(target - other_array)
311
-
312
- unless concat(other_array - target)
313
- @target = original_target
314
- raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
315
- "new records could not be saved."
379
+ if owner.new_record?
380
+ replace_records(other_array, original_target)
381
+ else
382
+ replace_common_records_in_memory(other_array, original_target)
383
+ if other_array != original_target
384
+ transaction { replace_records(other_array, original_target) }
316
385
  end
317
386
  end
318
387
  end
@@ -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 =~ /,/
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,14 +477,9 @@ 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)
480
+ if mem_record = memory.delete(record)
408
481
 
409
- if mem_index
410
- mem_record = memory.delete_at(mem_index)
411
-
412
- (record.attribute_names - mem_record.changes.keys).each do |name|
482
+ ((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
413
483
  mem_record[name] = record[name]
414
484
  end
415
485
 
@@ -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,22 +515,28 @@ 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
- transaction do
457
- records.each { |record| callback(:before_remove, record) }
526
+ if existing_records.empty?
527
+ remove_records(existing_records, records, method)
528
+ else
529
+ transaction { remove_records(existing_records, records, method) }
530
+ end
531
+ end
458
532
 
459
- delete_records(existing_records, method) if existing_records.any?
460
- records.each { |record| target.delete(record) }
533
+ def remove_records(existing_records, records, method)
534
+ records.each { |record| callback(:before_remove, record) }
461
535
 
462
- records.each { |record| callback(:after_remove, record) }
463
- end
536
+ delete_records(existing_records, method) if existing_records.any?
537
+ records.each { |record| target.delete(record) }
538
+
539
+ records.each { |record| callback(:after_remove, record) }
464
540
  end
465
541
 
466
542
  # Delete the given records from the association, using one of the methods :destroy,
@@ -469,22 +545,48 @@ module ActiveRecord
469
545
  raise NotImplementedError
470
546
  end
471
547
 
548
+ def replace_records(new_target, original_target)
549
+ delete(target - new_target)
550
+
551
+ unless concat(new_target - target)
552
+ @target = original_target
553
+ raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
554
+ "new records could not be saved."
555
+ end
556
+
557
+ target
558
+ end
559
+
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)
569
+ result = true
570
+
571
+ records.flatten.each do |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?
575
+ end
576
+ end
577
+
578
+ result && records
579
+ end
580
+
472
581
  def callback(method, record)
473
582
  callbacks_for(method).each do |callback|
474
- case callback
475
- when Symbol
476
- owner.send(callback, record)
477
- when Proc
478
- callback.call(owner, record)
479
- else
480
- callback.send(method, owner, record)
481
- end
583
+ callback.call(method, owner, record)
482
584
  end
483
585
  end
484
586
 
485
587
  def callbacks_for(callback_name)
486
588
  full_callback_name = "#{callback_name}_for_#{reflection.name}"
487
- owner.class.send(full_callback_name.to_sym) || []
589
+ owner.class.send(full_callback_name)
488
590
  end
489
591
 
490
592
  # Should we deal with assoc.first or assoc.last by issuing an independent query to
@@ -495,52 +597,57 @@ module ActiveRecord
495
597
  # Otherwise, go to the database only if none of the following are true:
496
598
  # * target already loaded
497
599
  # * owner is new record
498
- # * custom :finder_sql exists
499
600
  # * target contains new or changed record(s)
500
- # * the first arg is an integer (which indicates the number of records to be returned)
501
- def fetch_first_or_last_using_find?(args)
601
+ def fetch_first_nth_or_last_using_find?(args)
502
602
  if args.first.is_a?(Hash)
503
603
  true
504
604
  else
505
605
  !(loaded? ||
506
606
  owner.new_record? ||
507
- options[:finder_sql] ||
508
- target.any? { |record| record.new_record? || record.changed? } ||
509
- args.first.kind_of?(Integer))
607
+ target.any? { |record| record.new_record? || record.changed? })
510
608
  end
511
609
  end
512
610
 
513
611
  def include_in_memory?(record)
514
612
  if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
515
- owner.send(reflection.through_reflection.name).any? { |source|
516
- target = source.send(reflection.source_reflection.name)
517
- 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
518
622
  } || target.include?(record)
519
623
  else
520
624
  target.include?(record)
521
625
  end
522
626
  end
523
627
 
524
- # 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.
525
630
  def find_by_scan(*args)
526
631
  expects_array = args.first.kind_of?(Array)
527
- ids = args.flatten.compact.uniq.map { |arg| arg.to_i }
632
+ ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq
528
633
 
529
634
  if ids.size == 1
530
635
  id = ids.first
531
- record = load_target.detect { |r| id == r.id }
636
+ record = load_target.detect { |r| id == r.id.to_s }
532
637
  expects_array ? [ record ] : record
533
638
  else
534
- load_target.select { |r| ids.include?(r.id) }
639
+ load_target.select { |r| ids.include?(r.id.to_s) }
535
640
  end
536
641
  end
537
642
 
538
643
  # Fetches the first/last using SQL if possible, otherwise from the target array.
539
- def first_or_last(type, *args)
644
+ def first_nth_or_last(type, *args)
540
645
  args.shift if args.first.is_a?(Hash) && args.first.empty?
541
646
 
542
- collection = fetch_first_or_last_using_find?(args) ? scoped : load_target
543
- collection.send(type, *args)
647
+ collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
648
+ collection.send(type, *args).tap do |record|
649
+ set_inverse_instance record if record.is_a? ActiveRecord::Base
650
+ end
544
651
  end
545
652
  end
546
653
  end