activerecord 4.1.0 → 4.2.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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +776 -1330
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +12 -8
  5. data/lib/active_record/association_relation.rb +4 -0
  6. data/lib/active_record/associations/alias_tracker.rb +14 -13
  7. data/lib/active_record/associations/association.rb +2 -2
  8. data/lib/active_record/associations/association_scope.rb +83 -43
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  10. data/lib/active_record/associations/builder/association.rb +15 -4
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -6
  13. data/lib/active_record/associations/builder/has_many.rb +1 -1
  14. data/lib/active_record/associations/builder/has_one.rb +2 -2
  15. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  16. data/lib/active_record/associations/collection_association.rb +66 -29
  17. data/lib/active_record/associations/collection_proxy.rb +22 -26
  18. data/lib/active_record/associations/has_many_association.rb +65 -18
  19. data/lib/active_record/associations/has_many_through_association.rb +55 -27
  20. data/lib/active_record/associations/has_one_association.rb +0 -1
  21. data/lib/active_record/associations/join_dependency/join_association.rb +19 -15
  22. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  23. data/lib/active_record/associations/join_dependency.rb +20 -12
  24. data/lib/active_record/associations/preloader/association.rb +34 -11
  25. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  26. data/lib/active_record/associations/preloader.rb +49 -59
  27. data/lib/active_record/associations/singular_association.rb +25 -4
  28. data/lib/active_record/associations/through_association.rb +23 -14
  29. data/lib/active_record/associations.rb +171 -42
  30. data/lib/active_record/attribute.rb +149 -0
  31. data/lib/active_record/attribute_assignment.rb +18 -10
  32. data/lib/active_record/attribute_decorators.rb +66 -0
  33. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  34. data/lib/active_record/attribute_methods/dirty.rb +98 -44
  35. data/lib/active_record/attribute_methods/primary_key.rb +14 -8
  36. data/lib/active_record/attribute_methods/query.rb +1 -1
  37. data/lib/active_record/attribute_methods/read.rb +22 -59
  38. data/lib/active_record/attribute_methods/serialization.rb +37 -147
  39. data/lib/active_record/attribute_methods/time_zone_conversion.rb +34 -28
  40. data/lib/active_record/attribute_methods/write.rb +14 -21
  41. data/lib/active_record/attribute_methods.rb +67 -94
  42. data/lib/active_record/attribute_set/builder.rb +86 -0
  43. data/lib/active_record/attribute_set.rb +77 -0
  44. data/lib/active_record/attributes.rb +139 -0
  45. data/lib/active_record/autosave_association.rb +45 -38
  46. data/lib/active_record/base.rb +10 -20
  47. data/lib/active_record/callbacks.rb +7 -7
  48. data/lib/active_record/coders/json.rb +13 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +78 -52
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +38 -59
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -55
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -5
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +126 -54
  55. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +198 -64
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +126 -114
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -55
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +240 -135
  60. data/lib/active_record/connection_adapters/column.rb +28 -239
  61. data/lib/active_record/connection_adapters/connection_specification.rb +16 -25
  62. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -22
  63. data/lib/active_record/connection_adapters/mysql_adapter.rb +65 -149
  64. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  65. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  66. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -27
  67. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -374
  93. data/lib/active_record/connection_adapters/postgresql/quoting.rb +55 -135
  94. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  96. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +127 -38
  97. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  98. data/lib/active_record/connection_adapters/postgresql_adapter.rb +220 -466
  99. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  100. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -61
  101. data/lib/active_record/connection_handling.rb +3 -3
  102. data/lib/active_record/core.rb +143 -32
  103. data/lib/active_record/counter_cache.rb +60 -7
  104. data/lib/active_record/enum.rb +10 -11
  105. data/lib/active_record/errors.rb +49 -27
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/fixtures.rb +56 -70
  108. data/lib/active_record/gem_version.rb +2 -2
  109. data/lib/active_record/inheritance.rb +35 -10
  110. data/lib/active_record/integration.rb +4 -4
  111. data/lib/active_record/locking/optimistic.rb +35 -17
  112. data/lib/active_record/log_subscriber.rb +1 -1
  113. data/lib/active_record/migration/command_recorder.rb +19 -2
  114. data/lib/active_record/migration/join_table.rb +1 -1
  115. data/lib/active_record/migration.rb +52 -49
  116. data/lib/active_record/model_schema.rb +49 -57
  117. data/lib/active_record/nested_attributes.rb +7 -7
  118. data/lib/active_record/null_relation.rb +19 -5
  119. data/lib/active_record/persistence.rb +50 -31
  120. data/lib/active_record/query_cache.rb +3 -3
  121. data/lib/active_record/querying.rb +10 -7
  122. data/lib/active_record/railtie.rb +14 -11
  123. data/lib/active_record/railties/databases.rake +56 -54
  124. data/lib/active_record/readonly_attributes.rb +0 -1
  125. data/lib/active_record/reflection.rb +286 -102
  126. data/lib/active_record/relation/batches.rb +0 -1
  127. data/lib/active_record/relation/calculations.rb +39 -31
  128. data/lib/active_record/relation/delegation.rb +2 -2
  129. data/lib/active_record/relation/finder_methods.rb +80 -36
  130. data/lib/active_record/relation/merger.rb +25 -30
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +31 -13
  132. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  133. data/lib/active_record/relation/predicate_builder.rb +11 -10
  134. data/lib/active_record/relation/query_methods.rb +141 -55
  135. data/lib/active_record/relation/spawn_methods.rb +3 -0
  136. data/lib/active_record/relation.rb +69 -30
  137. data/lib/active_record/result.rb +18 -7
  138. data/lib/active_record/sanitization.rb +12 -2
  139. data/lib/active_record/schema.rb +0 -1
  140. data/lib/active_record/schema_dumper.rb +58 -26
  141. data/lib/active_record/schema_migration.rb +11 -0
  142. data/lib/active_record/scoping/default.rb +8 -7
  143. data/lib/active_record/scoping/named.rb +4 -0
  144. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  145. data/lib/active_record/statement_cache.rb +95 -10
  146. data/lib/active_record/store.rb +19 -10
  147. data/lib/active_record/tasks/database_tasks.rb +73 -7
  148. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -2
  149. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  151. data/lib/active_record/timestamp.rb +11 -9
  152. data/lib/active_record/transactions.rb +37 -21
  153. data/lib/active_record/type/big_integer.rb +13 -0
  154. data/lib/active_record/type/binary.rb +50 -0
  155. data/lib/active_record/type/boolean.rb +30 -0
  156. data/lib/active_record/type/date.rb +46 -0
  157. data/lib/active_record/type/date_time.rb +43 -0
  158. data/lib/active_record/type/decimal.rb +40 -0
  159. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  160. data/lib/active_record/type/decorator.rb +14 -0
  161. data/lib/active_record/type/float.rb +19 -0
  162. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  163. data/lib/active_record/type/integer.rb +55 -0
  164. data/lib/active_record/type/mutable.rb +16 -0
  165. data/lib/active_record/type/numeric.rb +36 -0
  166. data/lib/active_record/type/serialized.rb +56 -0
  167. data/lib/active_record/type/string.rb +36 -0
  168. data/lib/active_record/type/text.rb +11 -0
  169. data/lib/active_record/type/time.rb +26 -0
  170. data/lib/active_record/type/time_value.rb +38 -0
  171. data/lib/active_record/type/type_map.rb +64 -0
  172. data/lib/active_record/type/unsigned_integer.rb +15 -0
  173. data/lib/active_record/type/value.rb +101 -0
  174. data/lib/active_record/type.rb +23 -0
  175. data/lib/active_record/validations/associated.rb +5 -3
  176. data/lib/active_record/validations/presence.rb +6 -4
  177. data/lib/active_record/validations/uniqueness.rb +11 -17
  178. data/lib/active_record/validations.rb +25 -19
  179. data/lib/active_record.rb +3 -0
  180. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  181. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +4 -1
  182. data/lib/rails/generators/active_record/migration/templates/migration.rb +6 -0
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +65 -10
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -1,4 +1,3 @@
1
-
2
1
  module ActiveRecord
3
2
  module Batches
4
3
  # Looping through a collection of records from the database
@@ -19,6 +19,22 @@ module ActiveRecord
19
19
  #
20
20
  # Person.group(:city).count
21
21
  # # => { 'Rome' => 5, 'Paris' => 3 }
22
+ #
23
+ # If +count+ is used with +group+ for multiple columns, it returns a Hash whose
24
+ # keys are an array containing the individual values of each column and the value
25
+ # of each key would be the +count+.
26
+ #
27
+ # Article.group(:status, :category).count
28
+ # # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
29
+ # ["published", "business"]=>0, ["published", "technology"]=>2}
30
+ #
31
+ # If +count+ is used with +select+, it will count the selected columns:
32
+ #
33
+ # Person.select(:age).count
34
+ # # => counts the number of different age values
35
+ #
36
+ # Note: not all valid +select+ expressions are valid +count+ expressions. The specifics differ
37
+ # between databases. In invalid cases, an error from the database is thrown.
22
38
  def count(column_name = nil, options = {})
23
39
  # TODO: Remove options argument as soon we remove support to
24
40
  # activerecord-deprecated_finders.
@@ -161,19 +177,8 @@ module ActiveRecord
161
177
  relation.select_values = column_names.map { |cn|
162
178
  columns_hash.key?(cn) ? arel_table[cn] : cn
163
179
  }
164
- result = klass.connection.select_all(relation.arel, nil, bind_values)
165
- columns = result.columns.map do |key|
166
- klass.column_types.fetch(key) {
167
- result.column_types.fetch(key) { result.identity_type }
168
- }
169
- end
170
-
171
- result = result.map do |attributes|
172
- values = klass.initialize_attributes(attributes).values
173
-
174
- columns.zip(values).map { |column, value| column.type_cast value }
175
- end
176
- columns.one? ? result.map!(&:first) : result
180
+ result = klass.connection.select_all(relation.arel, nil, relation.arel.bind_values + bind_values)
181
+ result.cast_values(klass.column_types)
177
182
  end
178
183
  end
179
184
 
@@ -188,7 +193,7 @@ module ActiveRecord
188
193
  private
189
194
 
190
195
  def has_include?(column_name)
191
- eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?))
196
+ eager_loading? || (includes_values.present? && ((column_name && column_name != :all) || references_eager_loaded_tables?))
192
197
  end
193
198
 
194
199
  def perform_calculation(operation, column_name, options = {})
@@ -235,27 +240,32 @@ module ActiveRecord
235
240
 
236
241
  column_alias = column_name
237
242
 
243
+ bind_values = nil
244
+
238
245
  if operation == "count" && (relation.limit_value || relation.offset_value)
239
246
  # Shortcut when limit is zero.
240
247
  return 0 if relation.limit_value == 0
241
248
 
242
249
  query_builder = build_count_subquery(relation, column_name, distinct)
250
+ bind_values = query_builder.bind_values + relation.bind_values
243
251
  else
244
252
  column = aggregate_column(column_name)
245
253
 
246
254
  select_value = operation_over_aggregate_column(column, operation, distinct)
247
255
 
248
256
  column_alias = select_value.alias
257
+ column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
249
258
  relation.select_values = [select_value]
250
259
 
251
260
  query_builder = relation.arel
261
+ bind_values = query_builder.bind_values + relation.bind_values
252
262
  end
253
263
 
254
- result = @klass.connection.select_all(query_builder, nil, relation.bind_values)
264
+ result = @klass.connection.select_all(query_builder, nil, bind_values)
255
265
  row = result.first
256
266
  value = row && row.values.first
257
267
  column = result.column_types.fetch(column_alias) do
258
- column_for(column_name)
268
+ type_for(column_name)
259
269
  end
260
270
 
261
271
  type_cast_calculated_value(value, column, operation)
@@ -265,8 +275,8 @@ module ActiveRecord
265
275
  group_attrs = group_values
266
276
 
267
277
  if group_attrs.first.respond_to?(:to_sym)
268
- association = @klass.reflect_on_association(group_attrs.first.to_sym)
269
- associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
278
+ association = @klass._reflect_on_association(group_attrs.first)
279
+ associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
270
280
  group_fields = Array(associated ? association.foreign_key : group_attrs)
271
281
  else
272
282
  group_fields = group_attrs
@@ -307,7 +317,7 @@ module ActiveRecord
307
317
  relation.group_values = group
308
318
  relation.select_values = select_values
309
319
 
310
- calculated_data = @klass.connection.select_all(relation, nil, bind_values)
320
+ calculated_data = @klass.connection.select_all(relation, nil, relation.arel.bind_values + bind_values)
311
321
 
312
322
  if association
313
323
  key_ids = calculated_data.collect { |row| row[group_aliases.first] }
@@ -318,14 +328,14 @@ module ActiveRecord
318
328
  Hash[calculated_data.map do |row|
319
329
  key = group_columns.map { |aliaz, col_name|
320
330
  column = calculated_data.column_types.fetch(aliaz) do
321
- column_for(col_name)
331
+ type_for(col_name)
322
332
  end
323
333
  type_cast_calculated_value(row[aliaz], column)
324
334
  }
325
335
  key = key.first if key.size == 1
326
336
  key = key_records[key] if associated
327
337
 
328
- column_type = calculated_data.column_types.fetch(aggregate_alias) { column_for(column_name) }
338
+ column_type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
329
339
  [key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)]
330
340
  end]
331
341
  end
@@ -352,24 +362,20 @@ module ActiveRecord
352
362
  @klass.connection.table_alias_for(table_name)
353
363
  end
354
364
 
355
- def column_for(field)
365
+ def type_for(field)
356
366
  field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
357
- @klass.columns_hash[field_name]
367
+ @klass.type_for_attribute(field_name)
358
368
  end
359
369
 
360
- def type_cast_calculated_value(value, column, operation = nil)
370
+ def type_cast_calculated_value(value, type, operation = nil)
361
371
  case operation
362
372
  when 'count' then value.to_i
363
- when 'sum' then type_cast_using_column(value || 0, column)
373
+ when 'sum' then type.type_cast_from_database(value || 0)
364
374
  when 'average' then value.respond_to?(:to_d) ? value.to_d : value
365
- else type_cast_using_column(value, column)
375
+ else type.type_cast_from_database(value)
366
376
  end
367
377
  end
368
378
 
369
- def type_cast_using_column(value, column)
370
- column ? column.type_cast(value) : value
371
- end
372
-
373
379
  # TODO: refactor to allow non-string `select_values` (eg. Arel nodes).
374
380
  def select_for_count
375
381
  if select_values.present?
@@ -385,9 +391,11 @@ module ActiveRecord
385
391
 
386
392
  aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
387
393
  relation.select_values = [aliased_column]
388
- subquery = relation.arel.as(subquery_alias)
394
+ arel = relation.arel
395
+ subquery = arel.as(subquery_alias)
389
396
 
390
397
  sm = Arel::SelectManager.new relation.engine
398
+ sm.bind_values = arel.bind_values
391
399
  select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
392
400
  sm.project(select_value).from(subquery)
393
401
  end
@@ -40,10 +40,10 @@ module ActiveRecord
40
40
  BLACKLISTED_ARRAY_METHODS = [
41
41
  :compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!,
42
42
  :shuffle!, :slice!, :sort!, :sort_by!, :delete_if,
43
- :keep_if, :pop, :shift, :delete_at, :compact
43
+ :keep_if, :pop, :shift, :delete_at, :compact, :select!
44
44
  ].to_set # :nodoc:
45
45
 
46
- delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, to: :to_a
46
+ delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, to: :to_a
47
47
 
48
48
  delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
49
49
  :connection, :columns_hash, :to => :klass
@@ -1,3 +1,6 @@
1
+ require 'active_support/deprecation'
2
+ require 'active_support/core_ext/string/filters'
3
+
1
4
  module ActiveRecord
2
5
  module FinderMethods
3
6
  ONE_AS_ONE = '1 AS one'
@@ -63,7 +66,7 @@ module ActiveRecord
63
66
  # # returns an Array of the required fields, available since Rails 3.1.
64
67
  def find(*args)
65
68
  if block_given?
66
- to_a.find { |*block_args| yield(*block_args) }
69
+ to_a.find(*args) { |*block_args| yield(*block_args) }
67
70
  else
68
71
  find_with_ids(*args)
69
72
  end
@@ -79,12 +82,16 @@ module ActiveRecord
79
82
  # Post.find_by "published_at < ?", 2.weeks.ago
80
83
  def find_by(*args)
81
84
  where(*args).take
85
+ rescue RangeError
86
+ nil
82
87
  end
83
88
 
84
89
  # Like <tt>find_by</tt>, except that if no record is found, raises
85
90
  # an <tt>ActiveRecord::RecordNotFound</tt> error.
86
91
  def find_by!(*args)
87
92
  where(*args).take!
93
+ rescue RangeError
94
+ raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range value"
88
95
  end
89
96
 
90
97
  # Gives a record (or N records if a parameter is supplied) without any implied
@@ -101,7 +108,7 @@ module ActiveRecord
101
108
  # Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
102
109
  # is found. Note that <tt>take!</tt> accepts no arguments.
103
110
  def take!
104
- take or raise RecordNotFound
111
+ take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
105
112
  end
106
113
 
107
114
  # Find the first record (or first N records if a parameter is supplied).
@@ -127,16 +134,16 @@ module ActiveRecord
127
134
  #
128
135
  def first(limit = nil)
129
136
  if limit
130
- find_nth_with_limit(offset_value, limit)
137
+ find_nth_with_limit(offset_index, limit)
131
138
  else
132
- find_nth(:first, offset_value)
139
+ find_nth(0, offset_index)
133
140
  end
134
141
  end
135
142
 
136
143
  # Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
137
144
  # is found. Note that <tt>first!</tt> accepts no arguments.
138
145
  def first!
139
- first or raise RecordNotFound
146
+ find_nth! 0
140
147
  end
141
148
 
142
149
  # Find the last record (or last N records if a parameter is supplied).
@@ -169,7 +176,7 @@ module ActiveRecord
169
176
  # Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
170
177
  # is found. Note that <tt>last!</tt> accepts no arguments.
171
178
  def last!
172
- last or raise RecordNotFound
179
+ last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
173
180
  end
174
181
 
175
182
  # Find the second record.
@@ -179,13 +186,13 @@ module ActiveRecord
179
186
  # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
180
187
  # Person.where(["user_name = :u", { u: user_name }]).second
181
188
  def second
182
- find_nth(:second, offset_value ? offset_value + 1 : 1)
189
+ find_nth(1, offset_index)
183
190
  end
184
191
 
185
192
  # Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
186
193
  # is found.
187
194
  def second!
188
- second or raise RecordNotFound
195
+ find_nth! 1
189
196
  end
190
197
 
191
198
  # Find the third record.
@@ -195,13 +202,13 @@ module ActiveRecord
195
202
  # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
196
203
  # Person.where(["user_name = :u", { u: user_name }]).third
197
204
  def third
198
- find_nth(:third, offset_value ? offset_value + 2 : 2)
205
+ find_nth(2, offset_index)
199
206
  end
200
207
 
201
208
  # Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
202
209
  # is found.
203
210
  def third!
204
- third or raise RecordNotFound
211
+ find_nth! 2
205
212
  end
206
213
 
207
214
  # Find the fourth record.
@@ -211,13 +218,13 @@ module ActiveRecord
211
218
  # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
212
219
  # Person.where(["user_name = :u", { u: user_name }]).fourth
213
220
  def fourth
214
- find_nth(:fourth, offset_value ? offset_value + 3 : 3)
221
+ find_nth(3, offset_index)
215
222
  end
216
223
 
217
224
  # Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
218
225
  # is found.
219
226
  def fourth!
220
- fourth or raise RecordNotFound
227
+ find_nth! 3
221
228
  end
222
229
 
223
230
  # Find the fifth record.
@@ -227,29 +234,29 @@ module ActiveRecord
227
234
  # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
228
235
  # Person.where(["user_name = :u", { u: user_name }]).fifth
229
236
  def fifth
230
- find_nth(:fifth, offset_value ? offset_value + 4 : 4)
237
+ find_nth(4, offset_index)
231
238
  end
232
239
 
233
240
  # Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
234
241
  # is found.
235
242
  def fifth!
236
- fifth or raise RecordNotFound
243
+ find_nth! 4
237
244
  end
238
245
 
239
246
  # Find the forty-second record. Also known as accessing "the reddit".
240
247
  # If no order is defined it will order by primary key.
241
248
  #
242
249
  # Person.forty_two # returns the forty-second object fetched by SELECT * FROM people
243
- # Person.offset(3).forty_two # returns the fifth object from OFFSET 3 (which is OFFSET 44)
250
+ # Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44)
244
251
  # Person.where(["user_name = :u", { u: user_name }]).forty_two
245
252
  def forty_two
246
- find_nth(:forty_two, offset_value ? offset_value + 41 : 41)
253
+ find_nth(41, offset_index)
247
254
  end
248
255
 
249
256
  # Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
250
257
  # is found.
251
258
  def forty_two!
252
- forty_two or raise RecordNotFound
259
+ find_nth! 41
253
260
  end
254
261
 
255
262
  # Returns +true+ if a record exists in the table that matches the +id+ or
@@ -280,7 +287,14 @@ module ActiveRecord
280
287
  # Person.exists?(false)
281
288
  # Person.exists?
282
289
  def exists?(conditions = :none)
283
- conditions = conditions.id if Base === conditions
290
+ if Base === conditions
291
+ conditions = conditions.id
292
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
293
+ You are passing an instance of ActiveRecord::Base to `exists?`.
294
+ Please pass the id of the object by calling `.id`
295
+ MSG
296
+ end
297
+
284
298
  return false if !conditions
285
299
 
286
300
  relation = apply_join_dependency(self, construct_join_dependency)
@@ -292,10 +306,12 @@ module ActiveRecord
292
306
  when Array, Hash
293
307
  relation = relation.where(conditions)
294
308
  else
295
- relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
309
+ unless conditions == :none
310
+ relation = where(primary_key => conditions)
311
+ end
296
312
  end
297
313
 
298
- connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false
314
+ connection.select_value(relation, "#{name} Exists", relation.arel.bind_values + relation.bind_values) ? true : false
299
315
  end
300
316
 
301
317
  # This method is called whenever no records are found with either a single
@@ -322,8 +338,21 @@ module ActiveRecord
322
338
 
323
339
  private
324
340
 
341
+ def offset_index
342
+ offset_value || 0
343
+ end
344
+
325
345
  def find_with_associations
326
- join_dependency = construct_join_dependency
346
+ # NOTE: the JoinDependency constructed here needs to know about
347
+ # any joins already present in `self`, so pass them in
348
+ #
349
+ # failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136
350
+ # incorrect SQL is generated. In that case, the join dependency for
351
+ # SpecialCategorizations is constructed without knowledge of the
352
+ # preexisting join in joins_values to categorizations (by way of
353
+ # the `has_many :through` for categories).
354
+ #
355
+ join_dependency = construct_join_dependency(joins_values)
327
356
 
328
357
  aliases = join_dependency.aliases
329
358
  relation = select aliases.columns
@@ -335,7 +364,8 @@ module ActiveRecord
335
364
  if ActiveRecord::NullRelation === relation
336
365
  []
337
366
  else
338
- rows = connection.select_all(relation.arel, 'SQL', relation.bind_values.dup)
367
+ arel = relation.arel
368
+ rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
339
369
  join_dependency.instantiate(rows, aliases)
340
370
  end
341
371
  end
@@ -378,8 +408,9 @@ module ActiveRecord
378
408
  "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values)
379
409
 
380
410
  relation = relation.except(:select).select(values).distinct!
411
+ arel = relation.arel
381
412
 
382
- id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
413
+ id_rows = @klass.connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
383
414
  id_rows.map {|row| row[primary_key]}
384
415
  end
385
416
 
@@ -406,15 +437,20 @@ module ActiveRecord
406
437
  else
407
438
  find_some(ids)
408
439
  end
440
+ rescue RangeError
441
+ raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
409
442
  end
410
443
 
411
444
  def find_one(id)
412
- id = id.id if ActiveRecord::Base === id
445
+ if ActiveRecord::Base === id
446
+ id = id.id
447
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
448
+ You are passing an instance of ActiveRecord::Base to `find`.
449
+ Please pass the id of the object by calling `.id`
450
+ MSG
451
+ end
413
452
 
414
- column = columns_hash[primary_key]
415
- substitute = connection.substitute_at(column, bind_values.length)
416
- relation = where(table[primary_key].eq(substitute))
417
- relation.bind_values += [[column, id]]
453
+ relation = where(primary_key => id)
418
454
  record = relation.take
419
455
 
420
456
  raise_record_not_found_exception!(id, 0, 1) unless record
@@ -423,7 +459,7 @@ module ActiveRecord
423
459
  end
424
460
 
425
461
  def find_some(ids)
426
- result = where(table[primary_key].in(ids)).to_a
462
+ result = where(primary_key => ids).to_a
427
463
 
428
464
  expected_size =
429
465
  if limit_value && ids.size > limit_value
@@ -452,20 +488,28 @@ module ActiveRecord
452
488
  end
453
489
  end
454
490
 
455
- def find_nth(ordinal, offset)
491
+ def find_nth(index, offset)
456
492
  if loaded?
457
- @records.send(ordinal)
493
+ @records[index]
458
494
  else
495
+ offset += index
459
496
  @offsets[offset] ||= find_nth_with_limit(offset, 1).first
460
497
  end
461
498
  end
462
499
 
500
+ def find_nth!(index)
501
+ find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
502
+ end
503
+
463
504
  def find_nth_with_limit(offset, limit)
464
- if order_values.empty? && primary_key
465
- order(arel_table[primary_key].asc).limit(limit).offset(offset).to_a
466
- else
467
- limit(limit).offset(offset).to_a
468
- end
505
+ relation = if order_values.empty? && primary_key
506
+ order(arel_table[primary_key].asc)
507
+ else
508
+ self
509
+ end
510
+
511
+ relation = relation.offset(offset) unless offset.zero?
512
+ relation.limit(limit).to_a
469
513
  end
470
514
 
471
515
  def find_last
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  @hash = hash
14
14
  end
15
15
 
16
- def merge
16
+ def merge #:nodoc:
17
17
  Merger.new(relation, other).merge
18
18
  end
19
19
 
@@ -30,6 +30,8 @@ module ActiveRecord
30
30
  else
31
31
  other.joins!(*v)
32
32
  end
33
+ elsif k == :select
34
+ other._select!(v)
33
35
  else
34
36
  other.send("#{k}!", v)
35
37
  end
@@ -62,7 +64,13 @@ module ActiveRecord
62
64
  # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
63
65
  # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
64
66
  # don't fall through the cracks.
65
- relation.send("#{name}!", *value) unless value.nil? || (value.blank? && false != value)
67
+ unless value.nil? || (value.blank? && false != value)
68
+ if name == :select
69
+ relation._select!(*value)
70
+ else
71
+ relation.send("#{name}!", *value)
72
+ end
73
+ end
66
74
  end
67
75
 
68
76
  merge_multi_values
@@ -75,12 +83,12 @@ module ActiveRecord
75
83
  private
76
84
 
77
85
  def merge_joins
78
- return if values[:joins].blank?
86
+ return if other.joins_values.blank?
79
87
 
80
88
  if other.klass == relation.klass
81
- relation.joins!(*values[:joins])
89
+ relation.joins!(*other.joins_values)
82
90
  else
83
- joins_dependency, rest = values[:joins].partition do |join|
91
+ joins_dependency, rest = other.joins_values.partition do |join|
84
92
  case join
85
93
  when Hash, Symbol, Array
86
94
  true
@@ -100,56 +108,43 @@ module ActiveRecord
100
108
 
101
109
  def merge_multi_values
102
110
  lhs_wheres = relation.where_values
103
- rhs_wheres = values[:where] || []
111
+ rhs_wheres = other.where_values
104
112
 
105
113
  lhs_binds = relation.bind_values
106
- rhs_binds = values[:bind] || []
114
+ rhs_binds = other.bind_values
107
115
 
108
116
  removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
109
117
 
110
118
  where_values = kept + rhs_wheres
111
119
  bind_values = filter_binds(lhs_binds, removed) + rhs_binds
112
120
 
113
- conn = relation.klass.connection
114
- bv_index = 0
115
- where_values.map! do |node|
116
- if Arel::Nodes::Equality === node && Arel::Nodes::BindParam === node.right
117
- substitute = conn.substitute_at(bind_values[bv_index].first, bv_index)
118
- bv_index += 1
119
- Arel::Nodes::Equality.new(node.left, substitute)
120
- else
121
- node
122
- end
123
- end
124
-
125
121
  relation.where_values = where_values
126
122
  relation.bind_values = bind_values
127
123
 
128
- if values[:reordering]
124
+ if other.reordering_value
129
125
  # override any order specified in the original relation
130
- relation.reorder! values[:order]
131
- elsif values[:order]
126
+ relation.reorder! other.order_values
127
+ elsif other.order_values
132
128
  # merge in order_values from relation
133
- relation.order! values[:order]
129
+ relation.order! other.order_values
134
130
  end
135
131
 
136
- relation.extend(*values[:extending]) unless values[:extending].blank?
132
+ relation.extend(*other.extending_values) unless other.extending_values.blank?
137
133
  end
138
134
 
139
135
  def merge_single_values
140
- relation.from_value = values[:from] unless relation.from_value
141
- relation.lock_value = values[:lock] unless relation.lock_value
142
- relation.reverse_order_value = values[:reverse_order]
136
+ relation.from_value = other.from_value unless relation.from_value
137
+ relation.lock_value = other.lock_value unless relation.lock_value
143
138
 
144
- unless values[:create_with].blank?
145
- relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with])
139
+ unless other.create_with_value.blank?
140
+ relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
146
141
  end
147
142
  end
148
143
 
149
144
  def filter_binds(lhs_binds, removed_wheres)
150
145
  return lhs_binds if removed_wheres.empty?
151
146
 
152
- set = Set.new removed_wheres.map { |x| x.left.name }
147
+ set = Set.new removed_wheres.map { |x| x.left.name.to_s }
153
148
  lhs_binds.dup.delete_if { |col,_| set.include? col.name }
154
149
  end
155
150
 
@@ -1,29 +1,47 @@
1
+ require 'active_support/core_ext/string/filters'
2
+
1
3
  module ActiveRecord
2
4
  class PredicateBuilder
3
5
  class ArrayHandler # :nodoc:
4
6
  def call(attribute, value)
5
7
  values = value.map { |x| x.is_a?(Base) ? x.id : x }
6
- ranges, values = values.partition { |v| v.is_a?(Range) }
8
+ nils, values = values.partition(&:nil?)
9
+
10
+ if values.any? { |val| val.is_a?(Array) }
11
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
12
+ Passing a nested array to Active Record finder methods is
13
+ deprecated and will be removed. Flatten your array before using
14
+ it for 'IN' conditions.
15
+ MSG
7
16
 
8
- values_predicate = if values.include?(nil)
9
- values = values.compact
17
+ values = values.flatten
18
+ end
19
+
20
+ return attribute.in([]) if values.empty? && nils.empty?
10
21
 
22
+ ranges, values = values.partition { |v| v.is_a?(Range) }
23
+
24
+ values_predicate =
11
25
  case values.length
12
- when 0
13
- attribute.eq(nil)
14
- when 1
15
- attribute.eq(values.first).or(attribute.eq(nil))
16
- else
17
- attribute.in(values).or(attribute.eq(nil))
26
+ when 0 then NullPredicate
27
+ when 1 then attribute.eq(values.first)
28
+ else attribute.in(values)
18
29
  end
19
- else
20
- attribute.in(values)
30
+
31
+ unless nils.empty?
32
+ values_predicate = values_predicate.or(attribute.eq(nil))
21
33
  end
22
34
 
23
- array_predicates = ranges.map { |range| attribute.in(range) }
24
- array_predicates << values_predicate
35
+ array_predicates = ranges.map { |range| attribute.between(range) }
36
+ array_predicates.unshift(values_predicate)
25
37
  array_predicates.inject { |composite, predicate| composite.or(predicate) }
26
38
  end
39
+
40
+ module NullPredicate
41
+ def self.or(other)
42
+ other
43
+ end
44
+ end
27
45
  end
28
46
  end
29
47
  end
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  value = value.select(value.klass.arel_table[value.klass.primary_key])
7
7
  end
8
8
 
9
- attribute.in(value.arel.ast)
9
+ attribute.in(value.arel)
10
10
  end
11
11
  end
12
12
  end