librelist archives

« back to archive

Passing object to mailer template sent by resque

Passing object to mailer template sent by resque

From:
Mac Martine
Date:
2011-11-27 @ 19:40
I am calling invite! in devise_invitable which sends an email through 
DeviseMailer. I'm overriding DeviseMailer to use ResqueMailer so that the 
emails are sent through Resque:

devise_mailer.rb

    class Mailers::Devise::DeviseMailer < Devise::Mailer
      include Resque::Mailer
      
      def confirmation_instructions(record)
        devise_mail(record, :confirmation_instructions)
      end
    
      def reset_password_instructions(record)
        devise_mail(record, :reset_password_instructions)
      end
    
      def unlock_instructions(record)
        devise_mail(record, :unlock_instructions)
      end
      
    end

I then have a Client model, whose schema looks like: 

    id                     | integer                     | not null 
default nextval('clients_id_seq'::regclass)
    first                  | character varying(255)      | 
    last                   | character varying(255)      | 
    email                  | character varying(255)      | 
    phone                  | character varying(255)      | 
    cancelled              | boolean                     | default false
    encrypted_password     | character varying(128)      | default 
''::character varying
    reset_password_token   | character varying(255)      | 
    reset_password_sent_at | timestamp without time zone | 
    remember_created_at    | timestamp without time zone | 
    sign_in_count          | integer                     | default 0
    current_sign_in_at     | timestamp without time zone | 
    last_sign_in_at        | timestamp without time zone | 
    current_sign_in_ip     | character varying(255)      | 
    last_sign_in_ip        | character varying(255)      | 
    invitation_token       | character varying(60)       | 
    invitation_sent_at     | timestamp without time zone | 
    invitation_accepted_at | timestamp without time zone | 
    active                 | boolean                     | default true 

Note that most of those are Devise fields.

In the invitation_instructions.html.haml email template (sent off by 
calling invite!), I need to access a specific Business record as well. And
note that Client has_many businesses, not just one -- so simply retrieving
an association in the template doesn't cut it.

When I don't use Resque, I can create an attribute on Client like:

client.rb

    class Client < ActiveRecord::Base
    
      ...
      attr_accessor :this_business
    
    end

...then set it before calling invite!:

    def create
      
      @client.this_business = Business.find_by_subdomain(current_subdomain)
      @client.invite!
      
      ...
      
    end

and I can then access it in the template like:

    @resource.this_business

But when I use Resque, that attribute is lost. I think it's related to the
fact that when an object is passed to enqueue, it takes the object id, not
the object itself. So when the object is later retrieved again from the 
database before being passed to the template, the 'attr_accessor 
:this_business' is lost.

How can I pass another object as an arugment like this? I'm thinking I 
somehow need to pass in the object type 'Business' and the business id to 
be retrieved later, but even in stepping through the code in the debugger 
I can't quite figure out how to do this.

Any suggestions?

Thanks in advance.

-Mac


Re: [resque] Passing object to mailer template sent by resque

From:
nicholas a. evans
Date:
2011-11-28 @ 14:48
Yes, the reason that won't work is because you are passing the object
using database IDs, and the "this_business" attribute is not persisted
to the database.  You *could* get around that by passing a marshaled
object rather than a DB ID, but that route is fraught with its own
special sorts of perils, so I usually avoid passing marshalled
objects.  Something like the following should get the job done with
your current API:

module InviteJob
  extend self
  def queue; :invitations end
  # enqueue on the Job module is 9 parts sugar, 1 part decoupling
  def enqueue(client_id, business_subdomain)
    Resque.enqueue self, client_id, business_subdomain
  end
  def perform(client_id, business_subdomain)
    client = Client.find client_id
    client.this_business = Business.find_by_subdomain(business_subdomain)
    client.invite!
  end
end

InviteJob.enqueue client.id, current_subdomain

Honestly, I'd also prefer to change the #invite! method so it took the
business as an argument, rather than sneaking it in as a temporary
instance variable on the client.

I hope that helps.

-- 
Nick

On Sun, Nov 27, 2011 at 2:40 PM, Mac Martine <99miles@gmail.com> wrote:
> I am calling invite! in devise_invitable which sends an email through 
DeviseMailer. I'm overriding DeviseMailer to use ResqueMailer so that the 
emails are sent through Resque:
>
> devise_mailer.rb
>
>    class Mailers::Devise::DeviseMailer < Devise::Mailer
>      include Resque::Mailer
>
>      def confirmation_instructions(record)
>        devise_mail(record, :confirmation_instructions)
>      end
>
>      def reset_password_instructions(record)
>        devise_mail(record, :reset_password_instructions)
>      end
>
>      def unlock_instructions(record)
>        devise_mail(record, :unlock_instructions)
>      end
>
>    end
>
> I then have a Client model, whose schema looks like:
>
>    id                     | integer                     | not null 
default nextval('clients_id_seq'::regclass)
>    first                  | character varying(255)      |
>    last                   | character varying(255)      |
>    email                  | character varying(255)      |
>    phone                  | character varying(255)      |
>    cancelled              | boolean                     | default false
>    encrypted_password     | character varying(128)      | default 
''::character varying
>    reset_password_token   | character varying(255)      |
>    reset_password_sent_at | timestamp without time zone |
>    remember_created_at    | timestamp without time zone |
>    sign_in_count          | integer                     | default 0
>    current_sign_in_at     | timestamp without time zone |
>    last_sign_in_at        | timestamp without time zone |
>    current_sign_in_ip     | character varying(255)      |
>    last_sign_in_ip        | character varying(255)      |
>    invitation_token       | character varying(60)       |
>    invitation_sent_at     | timestamp without time zone |
>    invitation_accepted_at | timestamp without time zone |
>    active                 | boolean                     | default true
>
> Note that most of those are Devise fields.
>
> In the invitation_instructions.html.haml email template (sent off by 
calling invite!), I need to access a specific Business record as well. And
note that Client has_many businesses, not just one -- so simply retrieving
an association in the template doesn't cut it.
>
> When I don't use Resque, I can create an attribute on Client like:
>
> client.rb
>
>    class Client < ActiveRecord::Base
>
>      ...
>      attr_accessor :this_business
>
>    end
>
> ...then set it before calling invite!:
>
>    def create
>
>      @client.this_business = Business.find_by_subdomain(current_subdomain)
>      @client.invite!
>
>      ...
>
>    end
>
> and I can then access it in the template like:
>
>    @resource.this_business
>
> But when I use Resque, that attribute is lost. I think it's related to 
the fact that when an object is passed to enqueue, it takes the object id,
not the object itself. So when the object is later retrieved again from 
the database before being passed to the template, the 'attr_accessor 
:this_business' is lost.
>
> How can I pass another object as an arugment like this? I'm thinking I 
somehow need to pass in the object type 'Business' and the business id to 
be retrieved later, but even in stepping through the code in the debugger 
I can't quite figure out how to do this.
>
> Any suggestions?
>
> Thanks in advance.
>
> -Mac
>
>
>
>