activerecord 4.1.0.beta2 → 4.1.0.rc1

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +622 -9
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_record.rb +1 -1
  5. data/lib/active_record/associations.rb +10 -7
  6. data/lib/active_record/associations/alias_tracker.rb +39 -29
  7. data/lib/active_record/associations/association.rb +1 -1
  8. data/lib/active_record/associations/association_scope.rb +56 -31
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +5 -0
  10. data/lib/active_record/associations/builder/association.rb +6 -0
  11. data/lib/active_record/associations/builder/belongs_to.rb +1 -1
  12. data/lib/active_record/associations/collection_association.rb +33 -9
  13. data/lib/active_record/associations/collection_proxy.rb +53 -5
  14. data/lib/active_record/associations/has_many_association.rb +1 -1
  15. data/lib/active_record/associations/join_dependency.rb +5 -5
  16. data/lib/active_record/associations/join_dependency/join_association.rb +8 -8
  17. data/lib/active_record/associations/preloader.rb +1 -1
  18. data/lib/active_record/associations/singular_association.rb +1 -1
  19. data/lib/active_record/attribute_methods.rb +28 -5
  20. data/lib/active_record/attribute_methods/dirty.rb +27 -4
  21. data/lib/active_record/attribute_methods/read.rb +1 -1
  22. data/lib/active_record/attribute_methods/serialization.rb +18 -0
  23. data/lib/active_record/autosave_association.rb +1 -1
  24. data/lib/active_record/base.rb +1 -1
  25. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -2
  26. data/lib/active_record/connection_adapters/abstract/database_statements.rb +16 -9
  27. data/lib/active_record/connection_adapters/abstract/quoting.rb +3 -1
  28. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +8 -8
  29. data/lib/active_record/connection_adapters/abstract/transaction.rb +4 -0
  30. data/lib/active_record/connection_adapters/abstract_adapter.rb +15 -5
  31. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +2 -6
  32. data/lib/active_record/connection_adapters/connection_specification.rb +200 -43
  33. data/lib/active_record/connection_adapters/mysql2_adapter.rb +7 -1
  34. data/lib/active_record/connection_adapters/mysql_adapter.rb +8 -2
  35. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +3 -2
  36. data/lib/active_record/connection_adapters/postgresql/cast.rb +7 -7
  37. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -2
  38. data/lib/active_record/connection_adapters/postgresql/quoting.rb +1 -1
  39. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +13 -0
  40. data/lib/active_record/connection_adapters/postgresql_adapter.rb +32 -17
  41. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +25 -3
  42. data/lib/active_record/connection_handling.rb +64 -3
  43. data/lib/active_record/core.rb +28 -24
  44. data/lib/active_record/dynamic_matchers.rb +6 -2
  45. data/lib/active_record/enum.rb +111 -17
  46. data/lib/active_record/errors.rb +12 -0
  47. data/lib/active_record/fixtures.rb +13 -15
  48. data/lib/active_record/inheritance.rb +29 -9
  49. data/lib/active_record/integration.rb +4 -2
  50. data/lib/active_record/migration.rb +20 -7
  51. data/lib/active_record/migration/command_recorder.rb +18 -6
  52. data/lib/active_record/persistence.rb +10 -5
  53. data/lib/active_record/querying.rb +1 -0
  54. data/lib/active_record/railtie.rb +11 -8
  55. data/lib/active_record/railties/databases.rake +24 -38
  56. data/lib/active_record/relation.rb +3 -2
  57. data/lib/active_record/relation/batches.rb +24 -9
  58. data/lib/active_record/relation/finder_methods.rb +100 -11
  59. data/lib/active_record/relation/query_methods.rb +39 -27
  60. data/lib/active_record/result.rb +1 -1
  61. data/lib/active_record/sanitization.rb +7 -5
  62. data/lib/active_record/scoping.rb +5 -0
  63. data/lib/active_record/scoping/named.rb +6 -0
  64. data/lib/active_record/store.rb +1 -1
  65. data/lib/active_record/tasks/database_tasks.rb +45 -23
  66. data/lib/active_record/timestamp.rb +2 -2
  67. data/lib/active_record/transactions.rb +7 -7
  68. data/lib/active_record/validations/presence.rb +1 -1
  69. data/lib/active_record/version.rb +1 -1
  70. metadata +5 -6
  71. data/lib/active_record/associations/join_helper.rb +0 -36
@@ -181,6 +181,7 @@ module ActiveRecord
181
181
  became = klass.new
182
182
  became.instance_variable_set("@attributes", @attributes)
183
183
  became.instance_variable_set("@attributes_cache", @attributes_cache)
184
+ became.instance_variable_set("@changed_attributes", @changed_attributes) if defined?(@changed_attributes)
184
185
  became.instance_variable_set("@new_record", new_record?)
185
186
  became.instance_variable_set("@destroyed", destroyed?)
186
187
  became.instance_variable_set("@errors", errors)
@@ -195,7 +196,11 @@ module ActiveRecord
195
196
  # share the same set of attributes.
196
197
  def becomes!(klass)
197
198
  became = becomes(klass)
198
- became.public_send("#{klass.inheritance_column}=", klass.sti_name) unless self.class.descends_from_active_record?
199
+ sti_type = nil
200
+ if !klass.descends_from_active_record?
201
+ sti_type = klass.sti_name
202
+ end
203
+ became.public_send("#{klass.inheritance_column}=", sti_type)
199
204
  became
200
205
  end
201
206
 
@@ -264,7 +269,7 @@ module ActiveRecord
264
269
  # This method raises an +ActiveRecord::ActiveRecordError+ when called on new
265
270
  # objects, or when at least one of the attributes is marked as readonly.
266
271
  def update_columns(attributes)
267
- raise ActiveRecordError, "can not update on a new record object" unless persisted?
272
+ raise ActiveRecordError, "cannot update on a new record object" unless persisted?
268
273
 
269
274
  attributes.each_key do |key|
270
275
  verify_readonly_attribute(key.to_s)
@@ -361,7 +366,7 @@ module ActiveRecord
361
366
  # assert_equal 25, account.credit # check it is updated in memory
362
367
  # assert_equal 25, account.reload.credit # check it is also persisted
363
368
  #
364
- # Another commom use case is optimistic locking handling:
369
+ # Another common use case is optimistic locking handling:
365
370
  #
366
371
  # def with_optimistic_retry
367
372
  # begin
@@ -384,7 +389,7 @@ module ActiveRecord
384
389
 
385
390
  fresh_object =
386
391
  if options && options[:lock]
387
- self.class.unscoped { self.class.lock.find(id) }
392
+ self.class.unscoped { self.class.lock(options[:lock]).find(id) }
388
393
  else
389
394
  self.class.unscoped { self.class.find(id) }
390
395
  end
@@ -426,7 +431,7 @@ module ActiveRecord
426
431
  # ball.touch(:updated_at) # => raises ActiveRecordError
427
432
  #
428
433
  def touch(name = nil)
429
- raise ActiveRecordError, "can not touch on a new record object" unless persisted?
434
+ raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
430
435
 
431
436
  attributes = timestamp_attributes_for_update_in_model
432
437
  attributes << name if name
@@ -1,6 +1,7 @@
1
1
  module ActiveRecord
2
2
  module Querying
3
3
  delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, to: :all
4
+ delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, to: :all
4
5
  delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all
5
6
  delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all
6
7
  delegate :find_by, :find_by!, to: :all
@@ -31,22 +31,16 @@ module ActiveRecord
31
31
 
32
32
 
33
33
  config.active_record.use_schema_cache_dump = true
34
+ config.active_record.maintain_test_schema = true
34
35
 
35
36
  config.eager_load_namespaces << ActiveRecord
36
37
 
37
38
  rake_tasks do
38
39
  require "active_record/base"
39
40
 
40
- ActiveRecord::Tasks::DatabaseTasks.seed_loader = Rails.application
41
- ActiveRecord::Tasks::DatabaseTasks.env = Rails.env
42
-
43
41
  namespace :db do
44
42
  task :load_config do
45
- ActiveRecord::Tasks::DatabaseTasks.db_dir = Rails.application.config.paths["db"].first
46
43
  ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration
47
- ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a
48
- ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures'
49
- ActiveRecord::Tasks::DatabaseTasks.root = Rails.root
50
44
 
51
45
  if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
52
46
  if engine.paths['db/migrate'].existent
@@ -122,7 +116,16 @@ module ActiveRecord
122
116
  # and then establishes the connection.
123
117
  initializer "active_record.initialize_database" do |app|
124
118
  ActiveSupport.on_load(:active_record) do
125
- self.configurations = app.config.database_configuration || {}
119
+
120
+ class ActiveRecord::NoDatabaseError
121
+ remove_possible_method :extend_message
122
+ def extend_message(message)
123
+ message << "Run `$ bin/rake db:create db:migrate` to create your database"
124
+ message
125
+ end
126
+ end
127
+
128
+ self.configurations = Rails.application.config.database_configuration
126
129
  establish_connection
127
130
  end
128
131
  end
@@ -2,7 +2,7 @@ require 'active_record'
2
2
 
3
3
  db_namespace = namespace :db do
4
4
  task :load_config do
5
- ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {}
5
+ ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {}
6
6
  ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
7
7
  end
8
8
 
@@ -12,13 +12,9 @@ db_namespace = namespace :db do
12
12
  end
13
13
  end
14
14
 
15
- desc 'Create the database from DATABASE_URL or config/database.yml for the current Rails.env (use db:create:all to create all databases in the config)'
15
+ desc 'Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV it defaults to creating the development and test databases.'
16
16
  task :create => [:load_config] do
17
- if ENV['DATABASE_URL']
18
- ActiveRecord::Tasks::DatabaseTasks.create_database_url
19
- else
20
- ActiveRecord::Tasks::DatabaseTasks.create_current
21
- end
17
+ ActiveRecord::Tasks::DatabaseTasks.create_current
22
18
  end
23
19
 
24
20
  namespace :drop do
@@ -27,13 +23,9 @@ db_namespace = namespace :db do
27
23
  end
28
24
  end
29
25
 
30
- desc 'Drops the database using DATABASE_URL or the current Rails.env (use db:drop:all to drop all databases)'
26
+ desc 'Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV it defaults to dropping the development and test databases.'
31
27
  task :drop => [:load_config] do
32
- if ENV['DATABASE_URL']
33
- ActiveRecord::Tasks::DatabaseTasks.drop_database_url
34
- else
35
- ActiveRecord::Tasks::DatabaseTasks.drop_current
36
- end
28
+ ActiveRecord::Tasks::DatabaseTasks.drop_current
37
29
  end
38
30
 
39
31
  desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
@@ -42,7 +34,7 @@ db_namespace = namespace :db do
42
34
  ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration|
43
35
  ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope)
44
36
  end
45
- db_namespace['_dump'].invoke
37
+ db_namespace['_dump'].invoke if ActiveRecord::Base.dump_schema_after_migration
46
38
  end
47
39
 
48
40
  task :_dump do
@@ -83,7 +75,7 @@ db_namespace = namespace :db do
83
75
  # desc 'Runs the "down" for a given migration VERSION.'
84
76
  task :down => [:environment, :load_config] do
85
77
  version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
86
- raise 'VERSION is required' unless version
78
+ raise 'VERSION is required - To go down one migration, run db:rollback' unless version
87
79
  ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version)
88
80
  db_namespace['_dump'].invoke
89
81
  end
@@ -187,9 +179,6 @@ db_namespace = namespace :db do
187
179
  require 'active_record/fixtures'
188
180
 
189
181
  base_dir = if ENV['FIXTURES_PATH']
190
- STDERR.puts "Using FIXTURES_PATH env variable is deprecated, please use " +
191
- "ActiveRecord::Tasks::DatabaseTasks.fixtures_path = '/path/to/fixtures' " +
192
- "instead."
193
182
  File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
194
183
  else
195
184
  ActiveRecord::Tasks::DatabaseTasks.fixtures_path
@@ -212,9 +201,6 @@ db_namespace = namespace :db do
212
201
  puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::FixtureSet.identify(label)}.) if label
213
202
 
214
203
  base_dir = if ENV['FIXTURES_PATH']
215
- STDERR.puts "Using FIXTURES_PATH env variable is deprecated, please use " +
216
- "ActiveRecord::Tasks::DatabaseTasks.fixtures_path = '/path/to/fixtures' " +
217
- "instead."
218
204
  File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
219
205
  else
220
206
  ActiveRecord::Tasks::DatabaseTasks.fixtures_path
@@ -248,9 +234,7 @@ db_namespace = namespace :db do
248
234
 
249
235
  desc 'Load a schema.rb file into the database'
250
236
  task :load => [:environment, :load_config] do
251
- file = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'schema.rb')
252
- ActiveRecord::Tasks::DatabaseTasks.check_schema_file(file)
253
- load(file)
237
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(:ruby, ENV['SCHEMA'])
254
238
  end
255
239
 
256
240
  task :load_if_ruby => ['db:create', :environment] do
@@ -295,10 +279,7 @@ db_namespace = namespace :db do
295
279
 
296
280
  # desc "Recreate the databases from the structure.sql file"
297
281
  task :load => [:environment, :load_config] do
298
- filename = ENV['DB_STRUCTURE'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
299
- ActiveRecord::Tasks::DatabaseTasks.check_schema_file(filename)
300
- current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
301
- ActiveRecord::Tasks::DatabaseTasks.structure_load(current_config, filename)
282
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(:sql, ENV['DB_STRUCTURE'])
302
283
  end
303
284
 
304
285
  task :load_if_sql => ['db:create', :environment] do
@@ -308,8 +289,15 @@ db_namespace = namespace :db do
308
289
 
309
290
  namespace :test do
310
291
 
292
+ task :deprecated do
293
+ Rake.application.top_level_tasks.grep(/^db:test:/).each do |task|
294
+ $stderr.puts "WARNING: #{task} is deprecated. The Rails test helper now maintains " \
295
+ "your test schema automatically, see the release notes for details."
296
+ end
297
+ end
298
+
311
299
  # desc "Recreate the test database from the current schema"
312
- task :load => 'db:test:purge' do
300
+ task :load => %w(db:test:deprecated db:test:purge) do
313
301
  case ActiveRecord::Base.schema_format
314
302
  when :ruby
315
303
  db_namespace["test:load_schema"].invoke
@@ -319,7 +307,7 @@ db_namespace = namespace :db do
319
307
  end
320
308
 
321
309
  # desc "Recreate the test database from an existent schema.rb file"
322
- task :load_schema => 'db:test:purge' do
310
+ task :load_schema => %w(db:test:deprecated db:test:purge) do
323
311
  begin
324
312
  should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
325
313
  ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
@@ -333,7 +321,7 @@ db_namespace = namespace :db do
333
321
  end
334
322
 
335
323
  # desc "Recreate the test database from an existent structure.sql file"
336
- task :load_structure => 'db:test:purge' do
324
+ task :load_structure => %w(db:test:deprecated db:test:purge) do
337
325
  begin
338
326
  ActiveRecord::Tasks::DatabaseTasks.current_config(:config => ActiveRecord::Base.configurations['test'])
339
327
  db_namespace["structure:load"].invoke
@@ -343,7 +331,7 @@ db_namespace = namespace :db do
343
331
  end
344
332
 
345
333
  # desc "Recreate the test database from a fresh schema"
346
- task :clone => :environment do
334
+ task :clone => %w(db:test:deprecated environment) do
347
335
  case ActiveRecord::Base.schema_format
348
336
  when :ruby
349
337
  db_namespace["test:clone_schema"].invoke
@@ -353,18 +341,18 @@ db_namespace = namespace :db do
353
341
  end
354
342
 
355
343
  # desc "Recreate the test database from a fresh schema.rb file"
356
- task :clone_schema => ["db:schema:dump", "db:test:load_schema"]
344
+ task :clone_schema => %w(db:test:deprecated db:schema:dump db:test:load_schema)
357
345
 
358
346
  # desc "Recreate the test database from a fresh structure.sql file"
359
- task :clone_structure => [ "db:structure:dump", "db:test:load_structure" ]
347
+ task :clone_structure => %w(db:test:deprecated db:structure:dump db:test:load_structure)
360
348
 
361
349
  # desc "Empty the test database"
362
- task :purge => [:environment, :load_config] do
350
+ task :purge => %w(db:test:deprecated environment load_config) do
363
351
  ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations['test']
364
352
  end
365
353
 
366
354
  # desc 'Check for pending migrations and load the test schema'
367
- task :prepare => [:environment, :load_config] do
355
+ task :prepare => %w(db:test:deprecated environment load_config) do
368
356
  unless ActiveRecord::Base.configurations.blank?
369
357
  db_namespace['test:load'].invoke
370
358
  end
@@ -399,5 +387,3 @@ namespace :railties do
399
387
  end
400
388
  end
401
389
  end
402
-
403
- task 'test:prepare' => ['db:test:prepare', 'db:test:load', 'db:abort_if_pending_migrations']
@@ -24,6 +24,7 @@ module ActiveRecord
24
24
  @klass = klass
25
25
  @table = table
26
26
  @values = values
27
+ @offsets = {}
27
28
  @loaded = false
28
29
  end
29
30
 
@@ -495,9 +496,10 @@ module ActiveRecord
495
496
  end
496
497
 
497
498
  def reset
498
- @first = @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
499
+ @last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
499
500
  @should_eager_load = @join_dependency = nil
500
501
  @records = []
502
+ @offsets = {}
501
503
  self
502
504
  end
503
505
 
@@ -533,7 +535,6 @@ module ActiveRecord
533
535
  }
534
536
 
535
537
  binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
536
- binds.merge!(Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }])
537
538
 
538
539
  Hash[equalities.map { |where|
539
540
  name = where.left.name
@@ -52,7 +52,9 @@ module ActiveRecord
52
52
  records.each { |record| yield record }
53
53
  end
54
54
  else
55
- enum_for :find_each, options
55
+ enum_for :find_each, options do
56
+ options[:start] ? where(table[primary_key].gteq(options[:start])).size : size
57
+ end
56
58
  end
57
59
  end
58
60
 
@@ -64,6 +66,16 @@ module ActiveRecord
64
66
  # group.each { |person| person.party_all_night! }
65
67
  # end
66
68
  #
69
+ # If you do not provide a block to #find_in_batches, it will return an Enumerator
70
+ # for chaining with other methods:
71
+ #
72
+ # Person.find_in_batches.with_index do |group, batch|
73
+ # puts "Processing group ##{batch}"
74
+ # group.each(&:recover_from_last_night!)
75
+ # end
76
+ #
77
+ # To be yielded each record one by one, use #find_each instead.
78
+ #
67
79
  # ==== Options
68
80
  # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
69
81
  # * <tt>:start</tt> - Specifies the starting point for the batch processing.
@@ -88,30 +100,33 @@ module ActiveRecord
88
100
  options.assert_valid_keys(:start, :batch_size)
89
101
 
90
102
  relation = self
103
+ start = options[:start]
104
+ batch_size = options[:batch_size] || 1000
105
+
106
+ unless block_given?
107
+ return to_enum(:find_in_batches, options) do
108
+ total = start ? where(table[primary_key].gteq(start)).size : size
109
+ (total - 1).div(batch_size) + 1
110
+ end
111
+ end
91
112
 
92
113
  if logger && (arel.orders.present? || arel.taken.present?)
93
114
  logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
94
115
  end
95
116
 
96
- start = options.delete(:start)
97
- batch_size = options.delete(:batch_size) || 1000
98
-
99
117
  relation = relation.reorder(batch_order).limit(batch_size)
100
118
  records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
101
119
 
102
120
  while records.any?
103
121
  records_size = records.size
104
122
  primary_key_offset = records.last.id
123
+ raise "Primary key not included in the custom select clause" unless primary_key_offset
105
124
 
106
125
  yield records
107
126
 
108
127
  break if records_size < batch_size
109
128
 
110
- if primary_key_offset
111
- records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
112
- else
113
- raise "Primary key not included in the custom select clause"
114
- end
129
+ records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
115
130
  end
116
131
  end
117
132
 
@@ -127,9 +127,9 @@ module ActiveRecord
127
127
  #
128
128
  def first(limit = nil)
129
129
  if limit
130
- find_first_with_limit(limit)
130
+ find_nth_with_limit(offset_value, limit)
131
131
  else
132
- find_first
132
+ find_nth(:first, offset_value)
133
133
  end
134
134
  end
135
135
 
@@ -172,6 +172,86 @@ module ActiveRecord
172
172
  last or raise RecordNotFound
173
173
  end
174
174
 
175
+ # Find the second record.
176
+ # If no order is defined it will order by primary key.
177
+ #
178
+ # Person.second # returns the second object fetched by SELECT * FROM people
179
+ # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
180
+ # Person.where(["user_name = :u", { u: user_name }]).second
181
+ def second
182
+ find_nth(:second, offset_value ? offset_value + 1 : 1)
183
+ end
184
+
185
+ # Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
186
+ # is found.
187
+ def second!
188
+ second or raise RecordNotFound
189
+ end
190
+
191
+ # Find the third record.
192
+ # If no order is defined it will order by primary key.
193
+ #
194
+ # Person.third # returns the third object fetched by SELECT * FROM people
195
+ # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
196
+ # Person.where(["user_name = :u", { u: user_name }]).third
197
+ def third
198
+ find_nth(:third, offset_value ? offset_value + 2 : 2)
199
+ end
200
+
201
+ # Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
202
+ # is found.
203
+ def third!
204
+ third or raise RecordNotFound
205
+ end
206
+
207
+ # Find the fourth record.
208
+ # If no order is defined it will order by primary key.
209
+ #
210
+ # Person.fourth # returns the fourth object fetched by SELECT * FROM people
211
+ # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
212
+ # Person.where(["user_name = :u", { u: user_name }]).fourth
213
+ def fourth
214
+ find_nth(:fourth, offset_value ? offset_value + 3 : 3)
215
+ end
216
+
217
+ # Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
218
+ # is found.
219
+ def fourth!
220
+ fourth or raise RecordNotFound
221
+ end
222
+
223
+ # Find the fifth record.
224
+ # If no order is defined it will order by primary key.
225
+ #
226
+ # Person.fifth # returns the fifth object fetched by SELECT * FROM people
227
+ # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
228
+ # Person.where(["user_name = :u", { u: user_name }]).fifth
229
+ def fifth
230
+ find_nth(:fifth, offset_value ? offset_value + 4 : 4)
231
+ end
232
+
233
+ # Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
234
+ # is found.
235
+ def fifth!
236
+ fifth or raise RecordNotFound
237
+ end
238
+
239
+ # Find the forty-second record. Also known as accessing "the reddit".
240
+ # If no order is defined it will order by primary key.
241
+ #
242
+ # 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)
244
+ # Person.where(["user_name = :u", { u: user_name }]).forty_two
245
+ def forty_two
246
+ find_nth(:forty_two, offset_value ? offset_value + 41 : 41)
247
+ end
248
+
249
+ # Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
250
+ # is found.
251
+ def forty_two!
252
+ forty_two or raise RecordNotFound
253
+ end
254
+
175
255
  # Returns +true+ if a record exists in the table that matches the +id+ or
176
256
  # conditions given, or +false+ otherwise. The argument can take six forms:
177
257
  #
@@ -195,6 +275,7 @@ module ActiveRecord
195
275
  # Person.exists?(5)
196
276
  # Person.exists?('5')
197
277
  # Person.exists?(['name LIKE ?', "%#{query}%"])
278
+ # Person.exists?(id: [1, 4, 8])
198
279
  # Person.exists?(name: 'David')
199
280
  # Person.exists?(false)
200
281
  # Person.exists?
@@ -230,9 +311,9 @@ module ActiveRecord
230
311
  conditions = " [#{conditions}]" if conditions
231
312
 
232
313
  if Array(ids).size == 1
233
- error = "Couldn't find #{@klass.name} with #{primary_key}=#{ids}#{conditions}"
314
+ error = "Couldn't find #{@klass.name} with '#{primary_key}'=#{ids}#{conditions}"
234
315
  else
235
- error = "Couldn't find all #{@klass.name.pluralize} with IDs "
316
+ error = "Couldn't find all #{@klass.name.pluralize} with '#{primary_key}': "
236
317
  error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
237
318
  end
238
319
 
@@ -266,7 +347,15 @@ module ActiveRecord
266
347
  end
267
348
 
268
349
  def construct_relation_for_association_calculations
269
- apply_join_dependency(self, construct_join_dependency(arel.froms.first))
350
+ from = arel.froms.first
351
+ if Arel::Table === from
352
+ apply_join_dependency(self, construct_join_dependency)
353
+ else
354
+ # FIXME: as far as I can tell, `from` will always be an Arel::Table.
355
+ # There are no tests that test this branch, but presumably it's
356
+ # possible for `from` to be a list?
357
+ apply_join_dependency(self, construct_join_dependency(from))
358
+ end
270
359
  end
271
360
 
272
361
  def apply_join_dependency(relation, join_dependency)
@@ -363,19 +452,19 @@ module ActiveRecord
363
452
  end
364
453
  end
365
454
 
366
- def find_first
455
+ def find_nth(ordinal, offset)
367
456
  if loaded?
368
- @records.first
457
+ @records.send(ordinal)
369
458
  else
370
- @first ||= find_first_with_limit(1).first
459
+ @offsets[offset] ||= find_nth_with_limit(offset, 1).first
371
460
  end
372
461
  end
373
462
 
374
- def find_first_with_limit(limit)
463
+ def find_nth_with_limit(offset, limit)
375
464
  if order_values.empty? && primary_key
376
- order(arel_table[primary_key].asc).limit(limit).to_a
465
+ order(arel_table[primary_key].asc).limit(limit).offset(offset).to_a
377
466
  else
378
- limit(limit).to_a
467
+ limit(limit).offset(offset).to_a
379
468
  end
380
469
  end
381
470