3

I'm using delayed job 3.0.2 with ActiveRecord and Rails 3.2.3. I have a User model which uses the has_secure_password mixin, so the password is only stored encrypted. Now I want to use delayed job to send the welcome email, which should contain a copy of the unencrypted password.

When creating the record, the plain-text password is in User#password. But delayed job seems to serialize/ deserialize the id of the record only and create a new instance of the model by doing User.find(X). This way my plain-text password is lost and the user gets an empty password in his email.

How can I tell delayed-job to serialize/ deserialize custom "virtual" attributes too, which are not stored in the database otherwise?

This is my monkey patch for delayed job 2.x, which worked fine.

class ActiveRecord::Base
  def self.yaml_new(klass, tag, val)
    klass.find(val['attributes']['id']).tap do |m|
      val.except("attributes").each_pair{ |k, v| m.send("#{k}=", v) }
    end
  rescue ActiveRecord::RecordNotFound
    raise Delayed::DeserializationError
  end
end

It doesn't work with delayed job 3.x. I'm also not really interested in fixing my monkey patch as I hope there's a proper solution to this.

gucki
  • 4,582
  • 7
  • 44
  • 56
  • If I'm understanding your post correctly, you are sending a plain text password through email. Sending the plain text password in an email is a security issue in my mind. I would not include it. More info about it in the top answer here (http://stackoverflow.com/questions/1069722/sending-username-and-password-through-email-after-user-registration-in-web-appli) – John Aug 02 '12 at 07:04

1 Answers1

2

In delayed job 3.x, the best way to do this is to override a few methods on your ActiveRecord class, and then to force the Psych YAML deserializer to load the ActiveRecord object from the serialized data. By default, delayed job uses just the deserialized id, and then loads the ActiveRecord object from the DB. So, say I have an ActiveRecord class called ShipmentImport, and I want an attr_accessor named 'user_id' to work with delayed job serialization/deserialization. Here is what I would do.

In the ShipmentImport ActiveRecord class, add this:

def encode_with(coder)
  super
  coder['user_id'] = @user_id
end

def init_with(coder)
 super
  @user_id = coder['user_id']
  self
end

In an initializer for your application, add this for your ActiveRecord class:

Psych.load_tags[['!ruby/ActiveRecord', ShipmentImport.name].join(':')] = ShipmentImport
munchbit
  • 531
  • 5
  • 10
  • Thanks, this is very helpful! Can you explain what the code for the initializer is doing? Also, could you see this as being a potential security issue -- that is, could this potentially make it easier for an outsider to discover the value of the attribute? – CodeBiker Jul 03 '13 at 04:40
  • The initializer code is telling delayed job to initialize the object by actually deserializing the serialized object rather than just deserializing the id and then looking it up from the DB. – munchbit Jul 22 '13 at 22:48
  • Also, I don't see anything inherently insecure with augmenting delayed job to support attributes that are not database fields. The specific ways that this technique is applied may raise security concerns, but that is true with almost any code that you write. – munchbit Jul 22 '13 at 23:03