50
votes

Is there a compact way with ActiveRecord to query for what id it's going to use next if an object was going to be persisted to the database? In SQL, a query like this would look something like:

SELECT max(id) + 1 FROM some_table;
13
This depends on the database you're using, and if records have been deleted..... EXAMPLE: delete the last 1000 records in a postgresql table and do an insert. The id is not the max + 1. In a paradox DB the next record could get an earlier id, because it re-uses them. It really really depends on your DB.baash05
Also... if you have two people logged in and both are entering products/purchases_orders/jobs/events. This would return the same ID for both of them.baash05
The simple answer is you don't do it like that, you create an id generator/provider model.ocodo

13 Answers

104
votes

Here is slightly modified Taryn East's version:

Model.maximum(:id).next
32
votes

While accepting fig's answer I might want to draw your attention to a small thing. If you are getting the next ID to set to a particular record before saving, I think its not a good idea.

because as an example in a web based system

  1. you get the last id as 10
  2. you set the next id as 11
  3. before you save the record someone else has saved the record, now the last id should be 12 likewise..

I'm not sure you want the last id to do what I'm thinking here, But if so this is just to draw your attention.

24
votes

If your database is Postgres, you can get the next id with this (example for a table called 'users'):

ActiveRecord::Base.connection.execute("select last_value from users_id_seq").first["last_value"]

Unlike the other answers, this value is not affected by the deletion of records.

There's probably a mySQL equivalent, but I don't have one set up to confirm.

If you have imported data into your postgresql database, there's a good chance that the next id value after the import is not set to the next integer greater than the largest one you imported. So you will run into problems trying to save the activerecord model instances.

In this scenario, you will need to set the next id value manually like this:

ActiveRecord::Base.connection.execute("alter sequence users_id_seq restart with 54321;") #or whatever value you need
13
votes

Slightly better than the accepted answer:

YourModel.maximum(:id) + 1

Still prone to race-conditions etc, but at least it will take note of skipped ids and is slightly more efficient than, say, ordering the table by id then returning the last.

9
votes

This is an old question, but none of the other answers work if you have deleted the last record:

Model.last.id #=> 10
Model.last.destroy
Model.last.id #=> 9, so (Model.last.id + 1) would be 10... but...
Model.create  #=> 11, your next id was actually 11

I solved the problem using the following approach:

current_value = ActiveRecord::Base.connection.execute("SELECT currval('models_id_seq')").first['currval'].to_i
Model.last.id #=> 10
Model.last.destroy
Model.last.id #=> 9
current_value + 1 #=> 11
3
votes

If no one else is using the table (otherwise you would have to use locking), you could get the autoincrement value from MySQL, the SQL command is

SELECT auto_increment FROM information_schema.tables 
WHERE table_schema = 'db_name' AND table_name = 'table_name';

and the Rails command would be

ActiveRecord::Base.connection.execute("SELECT auto_increment 
     FROM information_schema.tables 
     WHERE table_schema = 'db_name' AND table_name = 'table_name';").first[0]
3
votes

Get the largest id on the table

YourModel.maximum(:id)

This will run the following sql

SELECT MAX("your_models"."id") AS max_id FROM "your_models"

Convert the result to an integer by calling to_i. This is important as for an empty table the above max command will return nil. Fortunately nil.to_i returns 0.

Now to get the next available id, just add 1 +1`

The final result:

YourModal.maximum(:id).to_i+1
2
votes

I don't think there's a generic answer to this since you may not want to assume one database over another. Oracle, for example, may assign id's by a sequence or, worse, by a trigger.

As far as other databases are concerned, one may set them up with non sequential or random id allocation.

May I ask what the use case is? Why would you need to anticipate the next id? What does 'next' mean? Relative to when? What about race conditions (multiuser environments)?

2
votes

If you want to be sure no one else can take the 'new' index, you should lock the table. By using something like:

ActiveRecord::Base.connection.execute("LOCK TABLES table_name WRITE")

and

ActiveRecord::Base.connection.execute("UNLOCK TABLES")

But this is specific for each database engine.

The only correct answer for a sequential id column is:

YourModel.maximum(:id)+1

If you sort your model in a default scope, last and first will depend on that order.

1
votes

There seems to be need of a answer here that removes the race conditions, and addresses the actual problem of pre-providing unique identifiers.

To solve the problem you maintain a second model, which serves unique ID's for the primary model. This way you don't have any race condition.

If you need to make these ID's secure you should hash them with SHA256 (or SHA512) and store the hash as an indexed column on identifier model when they are generated.

They can then be associated and validated when used on the primary model. If you don't hash them you still associate them, to provide validation.

I'll post some example code a bit later.

0
votes

Don't get the intent but you might wanna think of using GUID

0
votes

You should never assume what the next id will be in the sequence. If you have more than 1 user you run the risk of the id being in use by the time the new object is created.

Instead, the safe approach would be to create the new object and update it. Making 2 hits to your database but with an absolute id for the object you're working with.

This method takes the guess work out of the equation.

0
votes

I came to this SO question because I wanted to be able to predict the id of a model created in my test suite (the id was then used a REST request to an external service and I needed to predict the exact value to mock the request).

I found that Model.maximum(:id).next, although elegant, doesn't work in a rails testing environment with transactional fixtures since there are usually no records in the db so it will simply return nil.

Transactional fixtures make the issue extra tricky since the auto increment field ascends even when there aren't any records in the db. Furthermore using an ALTER TABLE ***your_table_name*** AUTO_INCREMENT = 100 breaks the transaction your tests are in because it requires its own transaction.

What I did to solve this was to create a new object and add 1 to its id:

let!(:my_model_next_id) { FactoryBot.create(:my_model).id + 1 }

Although somewhat hacky (and slightly inefficient on your db since you create an extra object for the sake of its id), it doesn't do anything goofy to the transaction and works reliably in a testing environment with no records (unless your tests run in parallel with access to the same db...in which case: race conditions...maybe?).