2010-01-19 3 views
36

J'ai trouvé les réponses "pur SQL" à cette question. Y at-il un moyen, dans Rails, pour réinitialiser le champ id pour une table spécifique?
Pourquoi est-ce que je veux faire ceci? Parce que j'ai des tables avec des données en mouvement constant - rarement plus de 100 lignes, mais toujours différentes. C'est jusqu'à 25k maintenant, et il n'y a juste aucun point dans cela. J'ai l'intention d'utiliser un planificateur interne à l'application Rails (rufus-scheduler) pour exécuter la réinitialisation du champ d'identification tous les mois.Rails façon de réinitialiser la graine sur le champ id

+2

Je déteste quand les gens disent cela, mais les commentaires sont bien ... il n'y a vraiment aucun moyen de faire ceci dans les rails, et, dans l'ensemble, ce n'est pas nécessaire du tout. Si vous évitez simplement d'obtenir de grands nombres parce que vous voulez l'afficher pour les utilisateurs, vous devriez juste avoir une colonne PID, et l'utiliser pour votre affichage. – aronchick

+0

Et comment pourrais-je créer une colonne PID? Est-ce la même chose que Jonas suggérée ci-dessous dans sa réponse? – Trevoke

Répondre

51

Je suis sorti avec une solution basée sur la réponse de hgimenez et this other one.

Depuis que je travaille habituellement avec Sqlite ou PostgreSQL, je n'ai développé que pour ceux-ci; mais l'étendre à, disons MySQL, ne devrait pas être trop gênant.

Mettre cela à l'intérieur lib/et nécessitent sur un initialiseur:

# lib/active_record/add_reset_pk_sequence_to_base.rb 
module ActiveRecord 
    class Base 
    def self.reset_pk_sequence 
     case ActiveRecord::Base.connection.adapter_name 
     when 'SQLite' 
     new_max = maximum(primary_key) || 0 
     update_seq_sql = "update sqlite_sequence set seq = #{new_max} where name = '#{table_name}';" 
     ActiveRecord::Base.connection.execute(update_seq_sql) 
     when 'PostgreSQL' 
     ActiveRecord::Base.connection.reset_pk_sequence!(table_name) 
     else 
     raise "Task not implemented for this DB adapter" 
     end 
    end  
    end 
end 

Utilisation:

Client.count # 10 
Client.destroy_all 
Client.reset_pk_sequence 
Client.create(:name => 'Peter') # this client will have id=1 

EDIT: Comme le cas le plus courant dans lequel vous voulez faire est après avoir terminé une table de base de données, je recommande de donner un coup d'oeil à database_cleaner. Il gère la réinitialisation de l'ID automatiquement. Vous pouvez lui dire de supprimer des tables sélectionnées simplement comme ceci:

DatabaseCleaner.clean_with(:truncation, :only => %w[clients employees]) 
+2

Si vous êtes nouveau sur Rails comme moi, [ce blog] (http://blog.chrisblunt.com/rails-3-how-to-autoload-and-autorequire-your-custom -library-code /) m'a aidé à configurer ce code dans un initialiseur. –

1

Un problème est que ces types de champs sont mis en œuvre différemment pour différentes séquences databases-, incrémente automatiquement, etc.

Vous pouvez toujours déposer et rajoutez la table.

1

Il n'y a rien de tel dans Rails. Si vous avez besoin d'un ID sympa pour montrer les utilisateurs, stockez-les dans une table séparée et réutilisez-les.

+3

C'est intelligent! Je ne le montre pas à l'utilisateur cependant. Je veux juste éviter de déborder en 10 000 ans. :-) – Trevoke

1

Vous pouvez uniquement faire cela dans les rails si les _ids sont définis par des rails. Tant que les _ids sont définis par votre base de données, vous ne pourrez pas les contrôler sans utiliser SQL. Note: Je suppose que l'utilisation de rails pour appeler régulièrement une procédure SQL qui réinitialise ou supprime et recrée une séquence ne serait pas une solution purement SQL, mais je ne pense pas que ce soit ce que vous demandez ...

EDIT:

Disclaimer: Je ne sais pas beaucoup sur les rails.

Du point de vue SQL, si vous avez une table avec des colonnes id first_name last_name et vous habituellement insert into table (first_name, last_name) values ('bob', 'smith') vous pouvez simplement changer vos questions à insert into table (id, first_name, last_name) values ([variable set by rails], 'bob', 'smith') De cette façon, le _id est fixé par une variable, au lieu d'être réglée automatiquement par SQL. À ce stade, les rails ont tout le contrôle sur ce que sont les _ids (bien que si c'est un PK, vous devez vous assurer que vous n'utilisez pas la même valeur alors qu'il est encore là).

Si vous allez quitter l'affectation à la base de données, vous devez avoir rails cheminent (quel qu'en soit le temps horaire) quelque chose comme:

DROP SEQUENCE MY_SEQ; 
CREATE SEQUENCE MY_SEQ START WITH 1 INCREMENT BY 1 MINVALUE 1; 

à quelque séquence contrôle les ids pour votre table. Cela va se débarrasser de la séquence actuelle, et en créer une nouvelle. C'est la manière la plus simple que je connaisse de vous 'réinitialiser' une séquence.

+0

Eh bien, je vais devoir utiliser Rails pour appeler régulièrement une instruction SQL pour re-semer, je pense :) Par curiosité, comment pourrais-je faire Rails définir le _id? – Trevoke

114

Vous n'avez jamais mentionné le SGBD que vous utilisez. Si cela est postgreSQL, l'adaptateur postgres ActiveRecord a une méthode reset_pk_sequences! que vous pouvez utiliser:

ActiveRecord::Base.connection.reset_pk_sequence!('table_name') 
+0

Merci, Harold. C'est sauver mon cul en ce moment. –

+0

Bonnes affaires Harold! – RubyFanatic

+11

Le nom de la table peut être fléchi par Rails avec 'Class.table_name', ce qui peut le rendre encore plus agréable. Merci! –

5

Je suppose que vous ne vous souciez pas les données:

def self.truncate! 
    connection.execute("truncate table #{quoted_table_name}") 
end 

Ou si vous le faites, mais pas trop (il y a une tranche de temps où les données existent uniquement en mémoire):

def self.truncate_preserving_data! 
    data = all.map(&:clone).each{|r| raise "Record would not be able to be saved" unless r.valid? } 
    connection.execute("truncate table #{quoted_table_name}") 
    data.each(&:save) 
end 

Cela donnera de nouveaux records, avec les mêmes attributs, mais id à partir de 1.

Quelque chose belongs_to cette table pourrait se visse.

+0

C'est une réponse soignée, mais tronquer n'est pas une option pour moi. J'ai appris quelque chose si :) – Trevoke

1

Sur la base de la réponse de @hgmnz, je fait cette méthode qui définira la séquence à une valeur quelconque ... (testé uniquement avec les Postgres adaptateur.)

# change the database sequence to force the next record to have a given id 
def set_next_id table_name, next_id 
    connection = ActiveRecord::Base.connection 
    def connection.set_next_id table, next_id 
    pk, sequence = pk_and_sequence_for(table) 
    quoted_sequence = quote_table_name(sequence) 
    select_value <<-end_sql, 'SCHEMA' 
     SELECT setval('#{quoted_sequence}', #{next_id}, false) 
    end_sql 
    end 
    connection.set_next_id(table_name, next_id) 
end 
+0

Cela fonctionne hors de la boîte ... merci! – ChaosFreak

0

Rails chemin pour par exemple MySQL, mais avec toutes les données perdues dans le tableau users:

ActiveRecord::Base.connection.execute('TRUNCATE TABLE users;') 

aide Peut-être quelqu'un;)

+0

Voici la même réponse que https://stackoverflow.com/a/2098639/234025. – Trevoke

Questions connexes