2013-03-17 9 views
11

In un'applicazione Ruby on Rails su cui sto lavorando Consento agli utenti di caricare file e di dare a questi file un nome alfanumerico casuale e breve. (Ad es. 'G7jf8' o '3bp76'). Qual è il modo migliore per farlo?Come posso generare una stringa casuale e univoca in Ruby?

Sto pensando di generare una stringa hash/crittografata dal nome file originale e dal timestamp. Quindi interrogare il database per verificare che non esista. Se lo fa, genera un altro e ripeti.

Il problema che vedo con questo approccio è se c'è un'elevata propilità di stringhe duplicate, potrebbe aggiungere un sacco di carico del database.

+1

C'è anche la potenziale (se improbabile) condizione di gara di due richieste che cercano di aggiungere lo stesso nome allo stesso tempo.Il database dovrebbe avere un vincolo univoco su quella colonna e dovresti essere pronto a prendere 'ActiveRecord :: RecordNotUnique'. – mpartel

+0

controlla http://stackoverflow.com/questions/5966910/generate-unique-random-string-with-letters-and-numbers-in-lower-case – sameera207

+0

Il nome "casuale" ha uno scopo di sicurezza? In caso contrario, hai più opzioni. –

risposta

9

Io uso questo :)

def generate_token(column, length = 64) 
    begin 
    self[column] = SecureRandom.urlsafe_base64 length 
    end while Model.exists?(column => self[column]) 
end 

Sostituire Model per il nome del modello

5

Utilizzare la funzione SecureRandom.hex di Ruby con il numero facoltativo di carattere che si desidera generare.

+3

Il mio preferito per questa domanda potrebbe essere 'SecureRandom.urlsafe_base64'. –

0

È possibile assegnare un ID univoco incrementandolo ogni volta che viene aggiunto un nuovo file e convertirlo in una stringa crittografata utilizzando OpenSSL::Cipher con una chiave costante salvata da qualche parte.

0

Se si finisce per generare un digest esadecimale o numerico, è possibile mantenere il codice più breve rappresentando il numero come ad es. Base 62:

# This is a lightweight base62 encoding for Ruby integers. 
B62CHARS = ('0'..'9').to_a + ('a'..'z').to_a + ('A'..'Z').to_a 

def base62_string nbr 
    b62 = '' 
    while nbr > 0 
    b62 << B62CHARS[nbr % 62] 
    nbr /= 62 
    end 
    b62.reverse 
end 

Se è importante per voi per limitare il set di caratteri utilizzato (per esempio, non ha caratteri maiuscoli nei nomi di file), allora questo codice può essere facilmente adattato, a condizione che si può trovare un modo di alimentazione in un numero casuale adatto.

Se i nomi dei file devono essere semi-sicuri, è necessario disporre che ci siano molti più nomi possibili dei nomi effettivi nella memoria.

+0

Questo non genera nulla di unico, che è quello che l'OP chiedeva. Dato lo stesso 'nbr' questo restituirà sempre la stessa stringa, e dovrai passare un numero ridicolmente grande per restituire una stringa di qualsiasi dimensione significativa. Es: '> base62_string 99999999999999999999 # =>" 1V973MbJYWoT "' –

+0

@ Chrisbloom7: d'accordo, questo ha bisogno di inserire un numero, che non spiegherò come ottenerlo (ma menziono nel testo che è necessario). Generare un numero casuale di dimensioni adatte è banale in Ruby: 'SecureRandom.random_number (2 ** 128)'. L'approccio funziona anche con sequenze, hash, ecc. Il fatto che sia necessario un enorme input per creare una stringa breve è effettivamente auspicabile per l'OP, hanno richiesto una stringa breve –

8
SecureRandom.uuid 

Vi darà una stringa unica al mondo. http://en.m.wikipedia.org/wiki/Universally_unique_identifier

SecureRandom.hex 32 

darà una stringa casuale, ma è l'algoritmo non è ottimizzato per l'unicità. Naturalmente la possibilità di collisione con 32 cifre, assumendo la casualità vera, è fondamentalmente teorica. Potresti guadagnare 1 miliardo al secondo per 100 anni e avere solo il 50% di possibilità di collisione.

-1

Sembra che tu abbia effettivamente bisogno di un nome file unico, giusto? Perché non dimenticare soluzioni complesse e utilizzare semplicemente Time#nsec?

t = Time.now  #=> 2007-11-17 15:18:03 +0900 
"%10.9f" % t.to_f #=> "1195280283.536151409" 
+0

Se l'app è ampia e occupata e ha diversi server indipendenti , alla fine due elaboreranno un file allo stesso tempo e otterranno una collisione. –

+0

Concat _nanosecs_ con il nome del server per essere completamente sicuro. – mudasobwa

+1

A quel punto, SecureRandom.uuid è una soluzione più semplice, credo. –

0

Questo produrrà sempre nuova stringa alfanumerica uniq 40 dimensioni, perché ha anche data e ora.

ciclo do

random_token = Digest::SHA1.hexdigest([Time.now, rand(111..999)].join) 

    break random_token unless Model.exists?(column_name: random_token) 

fine

Nota: Sostituire Modello dal nome_modello e nome_colonna da qualsiasi colonna esistente del modello.

Problemi correlati