Search code examples
ruby-on-railsdeviseruby-on-rails-5

Pass instance variable from Devise controller to custom mailer?


I use Devise for authentication in my Rails app.

In my registrations_controller I set an instance variable like this:

class RegistrationsController < Devise::RegistrationsController
  def create
    @foo = "bar"
    super
  end
end

In my customized mailer I then try to access the @foo instance variable, but it just returns nil:

class CustomMailer < Devise::Mailer
  helper :application
  include Devise::Controllers::UrlHelpers

  def confirmation_instructions(record, token, opts={})
    Rails.logger.error @foo.inspect # => nil
    super
  end
end

Anyone who could help?

I have looked through the posts How do I add instance variables to Devise email templates?, How to pass additional data to devise mailer?, How to pass instance variable to devise custom mailer in Rails?. But none of them seem to deal with this exact problem.


Solution

  • First let me explain you have instance variables work in ruby classes.

    In ruby, instance variable(in your case @foo) in a class(in your case RegistrationsController) do not pass down to other classes(in your case CustomMailer) even if inheritance is ON. For Example consider:

    class Abc
      @first = "First"
      puts "Abc: #{@first}"
    end
    
    class Def < Abc
      puts "Def: #{@first}"
    end
    
    # => Abc: First instance variable
    # => Def:
    

    As you can see the class Def cannot have access to @first instance variable. Thus, instance variables don't get passed down to other classes automatically.

    If you want these variables to pass down the class, you should consider using Class Instance Variables which starts with @@. Ex:

    class Abc
      @@first = "First instance variable"
      puts "Abc: #{@@first}"
    end
    
    class Def < Abc
      puts "Def: #{@@first}"
    end
    
    # => Abc: First instance variable
    # => Def: First instance variable
    

    Now @@first will pass down to inherited class automatically.


    Now, relate the above senario to your question. So you're creating a instance variable @foo in RegistrationsController and it will not be passed to other inherited classes. Under the hood both Devise::RegistrationsController and DeviseMailer are inherited from Devise.parent_controller.

    So, better way to work with it is to send the @foo as a parameter, for example:

    class RegistrationsController < Devise::RegistrationsController
      def create
        @foo = "bar"
        CustomMailer.confirmation_instructions(user, token, opts={foo: @foo})
        ...
      end
    end
    

    Then you can access this in your CustomMailer:

    def confirmation_instructions(record, token, opts={})
      puts opts[:foo]
      super
    end