2

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.

JohnSmith1976
  • 536
  • 2
  • 12
  • 35

1 Answers1

3

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
  • Thanks for a thorough answer @allenbrkn. It's widely advised against to use class instance variables, so I hope there is another way. Your suggestion to pass `@foo` as an argument requires me to overwrite methods entirely. If you check my example code you'll see that I call super in the `create` method. In order to change how `confirmation_instructions ` is invoked I need to overwrite the entire method that is calling it. I'm generally uncomfortable with overriding methods in devise. – JohnSmith1976 Aug 15 '19 at 17:46
  • 1
    OK I'll accept the answer because you've answered what is asked in the headline. And I'll open a new question about passing variables rather than instance variables. – JohnSmith1976 Aug 16 '19 at 10:43