1

I'd like to add a new field in a table.

My new "secret_code" field in my User model should be equal to Digest::SHA1.hexdigest([Time.now, rand].join)[1..12].

What I'm trying to do is generate a migration that would add the field to the table and also populate my existing users with a (sort of) unique "secret_code".

class AddSecretCodeToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :secret_code, :string
    User.update_all ["secret_code =?", Digest::SHA1.hexdigest([Time.now, rand].join)[1..12]]
  end

  def self.down
    remove_column :users, :secret_code
  end
end

The problem is that this migration populate all the existing users with the same secret code!

One solution would be NOT to use update_all and run a loop to fetch every user and send an update to each one, but in this case my migration would be extremely slow.

Is there a way to send a "unique" random value to an update_all method?

Thanks, Augusto

mu is too short
  • 426,620
  • 70
  • 833
  • 800
Augusto
  • 1,140
  • 2
  • 18
  • 38

2 Answers2

4

Try changing it to Digest::SHA1.hexdigest([Time.now, rand].to_s) but personally I'd create a rake task to the above as it's not really a migration.

Your rake task would do

User.all.each do |u|
  u.update_attribute(:secret_code, Digest::SHA1.hexdigest([Time.now, rand].to_s))
end

However, for your migration I'd also add t.string :secret_code, :default => Digest::SHA1.hexdigest([Time.now, rand].to_s) to the attribute so that it is added on newly created records.

Michael De Silva
  • 3,808
  • 1
  • 20
  • 24
  • Thanks, but does update_attribute skip callbacks? Otherwise it woul be extremely slow.. – Augusto Sep 30 '11 at 09:48
  • 1
    You could also just do `User.update_all(:secret_code => Digest::SHA1.hexdigest([Time.now, rand].to_s))` in your rake task instead then =) – Michael De Silva Sep 30 '11 at 10:10
  • 1
    Since you can not do exactly what you are asking to do anywhere other than code, you have to execute a command for each User. This will be slow no matter what you do :( If you look at @[mu is too short]'s answer, he has a way to make it fast but you are doing something completely different. – Travis Sep 30 '11 at 10:29
  • Cheers for the clarification Travis! – Michael De Silva Sep 30 '11 at 10:33
3

For MySQL, you could throw this right in your self.up:

connection.execute(%Q{
    update users
    set secret_code = substring(sha1(rand()) from 1 for 12)
})

PostgreSQL doesn't have SHA1 support by default but it does have MD5 and that's probably good enough for this:

connection.execute(%Q{
    update users
    set secret_code = substring(md5(random()::varchar) from 1 for 12)
})

If you have the pgcrypto package installed then you could use SHA1 for both.

Both of those will let the database do all the work and avoid all the overhead of round-tripping your whole table. You can play around with what you hash a bit as well, if you wanted to mix the time in:

md5(random()::varchar || now()::varchar) -- PostgreSQL
sha(rand()            || now()         ) -- MySQL
mu is too short
  • 426,620
  • 70
  • 833
  • 800