web-dev-qa-db-fra.com

Comment faire en sorte qu'ActiveRecord affiche le prochain identifiant (dernier + 1) dans Ruby on Rails?

Existe-t-il un moyen compact avec ActiveRecord de demander quel identifiant il utilisera ensuite si un objet devait être conservé dans la base de données? En SQL, une requête comme celle-ci ressemblerait à quelque chose comme:

SELECT max(id) + 1 FROM some_table;
36
randombits

Voici une version légèrement modifiée de Taryn East :

Model.maximum(:id).next
81
kimerseen

Tout en acceptant la réponse de figue je voudrais peut-être attirer votre attention sur une petite chose. Si vous voulez que l'ID suivant soit défini sur un enregistrement particulier avant de sauvegarder, je pense que ce n'est pas une bonne idée. 

parce que, par exemple, dans un système basé sur le Web 

  1. vous obtenez le dernier identifiant comme 10
  2. vous définissez le prochain identifiant comme 11
  3. avant de sauvegarder l'enregistrement, quelqu'un d'autre a sauvegardé l'enregistrement, maintenant le dernier identifiant devrait être 12 de même ..

Je ne suis pas sûr que vous souhaitiez que le dernier identifiant fasse ce que je pense ici, mais si c'est le cas, c'est simplement pour attirer votre attention.

30
sameera207

Si votre base de données est Postgres, vous pouvez obtenir le prochain identifiant avec ceci (exemple pour une table appelée 'utilisateurs'):

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

Contrairement aux autres réponses, cette valeur n'est pas affectée par la suppression des enregistrements.

Il existe probablement un équivalent mySQL, mais je n’en ai pas configuré pour le confirmer.

Si vous avez importé des données dans votre base de données postgresql, il est fort probable que la valeur d'ID suivante après l'importation ne soit pas définie sur l'entier supérieur supérieur à celui le plus grand que vous avez importé. Vous rencontrerez donc des problèmes en essayant de sauvegarder les instances du modèle activerecord.

Dans ce scénario, vous devrez définir manuellement la valeur de l'id suivant comme suit:

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

Un peu mieux que la réponse acceptée:

YourModel.maximum(:id) + 1

Toujours sujet aux conditions de course, etc., mais au moins, il prendra note des identifiants sautés et est légèrement plus efficace que, par exemple, commander la table par identifiant puis renvoyer le dernier.

11
Taryn East

C'est une vieille question, mais aucune des autres réponses ne fonctionne si vous avez supprimé le dernier enregistrement:

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

J'ai résolu le problème en utilisant l'approche suivante:

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
gabrielhilal

Si personne d'autre n'utilise la table (sinon vous devriez utiliser un verrouillage), vous pouvez obtenir la valeur d'auto-incrémentation de MySQL, la commande SQL est

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

et la commande Rails serait

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

Si vous voulez être sûr que personne ne peut utiliser le "nouvel" index, vous devez verrouiller la table ..__ en utilisant quelque chose comme:

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

et

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

Mais ceci est spécifique à chaque moteur de base de données.

La seule réponse correcte pour une colonne d'identifiant séquentiel est:

YourModel.maximum(:id)+1

Si vous triez votre modèle dans une étendue par défaut, last et first dépendront de cet ordre.

2
Herman verschooten

Il semble y avoir un besoin d'une réponse ici qui supprime les conditions de concurrence et s'attaque au problème réel de pré-fournir des identifiants uniques.

Pour résoudre le problème, maintenez un deuxième modèle, qui sert des identifiants uniques pour le modèle principal. De cette façon, vous n'avez aucune condition de concurrence.

Si vous devez sécuriser ces ID, vous devez les hacher avec SHA256 (ou SHA512) et les stocker en tant que colonne indexée sur le modèle d'identificateur lors de leur génération. 

Ils peuvent ensuite être associés et validés lorsqu'ils sont utilisés sur le modèle principal. Si vous ne les hachez pas, vous les associez quand même, pour fournir une validation.

Je posterai un exemple de code un peu plus tard.

1
ocodo

Je ne pense pas qu'il existe une réponse générique à cette question car vous ne voudrez peut-être pas supposer une base de données plutôt qu'une autre . Oracle, par exemple, peut attribuer des identifiants par une séquence ou, pire, par un déclencheur.

En ce qui concerne les autres bases de données, on peut les configurer avec une allocation d'identifiant non séquentielle ou aléatoire.

Puis-je demander quel est le cas d'utilisation? Pourquoi auriez-vous besoin d'anticiper le prochain identifiant? Que veut dire 'next'? Par rapport à quand? Qu'en est-il des conditions de concurrence (environnements multi-utilisateurs)?

1
dynex

Ne comprenez pas l'intention, mais vous voudrez peut-être utiliser un GUID

0
Ryo

Vous ne devez jamais supposer quelle sera la prochaine identité dans la séquence. Si vous avez plus d'un utilisateur, vous courez le risque que l'id soit utilisé au moment de la création du nouvel objet.

Au lieu de cela, l'approche sûre serait de créer le nouvel objet et de le mettre à jour. Faire 2 hits dans votre base de données mais avec un identifiant absolu pour l'objet avec lequel vous travaillez.

Cette méthode supprime les devinettes de l'équation. 

0
AnthonyM.