I've created a new mail on rails 5 using the mailer generator:
$ rails g mailer mymailer message
Rails created the application_mailer, mymailer_mailer, views and tests. Ok.
This is the mailer generated by rails:
class MymailerMailer < ApplicationMailer
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.mymailer_mailer.message.subject
#
def message
#greeting = "Hi"
mail to: "to#example.org"
end
end
But whenever I've tried to send the mail I've got the following error:
NoMethodError: undefined method `reject!' for nil:NilClass
After spent about two hours double-checking every config file I've decided to change method to bla...
VoilĂ : It worked, Ok! But why?
BTW: The message method I've found is from ActionMailer::MessageDelivery but there's no mention on Rails Guides of that.
If you look at the docs for MessageDelivery, there appears to be a method already provided named message which
Returns the resulting Mail::Message
My assumption is that your definition is overriding this provided method, but you are not returning the expected Mail::Message type object.
As another answer stated, there's already a method in the class named message. This shouldn't be a problem if you use the class as intended, since the mailer shouldn't have a single message named "message", it should have a more descriptive name.
The intent of a Mailer object is to define a context for messages that may be sent.
So for example, a UserMailer would be used to build messages to a user. Then each different type of message has a method, such as forgotten_password or welcome.
The documentation includes a more thorough example that follows this.
Related
I'm working inside a Rails engine where I have a GraphQL type.
module RailsEngine
module Graph
module RailsEngine
module Types
MyClass = GraphQL::ObjectType.define do
# some working code here
field :user, RailsEngine.graph_user_type.constantize, 'The description of user'
end
end
end
end
end
graph_user_type is a method that I define in the mattr_accessor and am getting a specific class of User from the main Rails app through the initializer.
When I'm running my Rspec tests for this type, I'm getting an error NameError: uninitialized constant Graph
So I was thinking of mocking the whole line field :user, RailsEngine.graph_user_type.constantize, 'The description of user' like this:
before do
user_type = double('Graph::User::Types::User')
allow(described_class.fields['user']).to receive(:constantize).and_return(user_type)
end
I've also tried allow(described_class.fields['user']).to receive(:type).and_return(user_type) also to no avail!
But I'm still getting the same error! Any ideas?
Failure/Error: field :user, RailsEngine.graph_user_type.constantize, 'The description of user'
NameError:
uninitialized constant Graph```
before do
user_type = double('Graph::User::Types::User')
allow(described_class.fields['user']).to receive(:constantize).and_return(user_type)
end
You need to understand what allow method does. It takes an object, on which later expectation is set with .to receive(...).
What seems to have more sense, would be:
allow(RailsEngine).to receive(:graph_user_type).and_return('Graph::User::Types::User')
or, if you need it to return a double, you'd do sth like
allow(RailsEngine).to receive_message_chain('graph_user_type.constantize').and_return(user_type)
https://relishapp.com/rspec/rspec-mocks/docs/working-with-legacy-code/message-chains
This way you're controlling what RailsEngine.graph_user_type is returning, and being passed on field as a second argument.
So the issue was hidden here
graph_user_type is a method that I define in the mattr_accessor and am getting a specific class of User from the main Rails app through the initializer.
Since I was getting the method graph_user_type through the initializer to the engine from the main app, before the spec was loaded, the error was already thrown - so it was useless to mock it inside the spec.
The solution was to add the very same thing that the main app initializer had to the dummy initializer inside the engine (with an indication that the data is mocked)
This was my initializer:
RailsEngine.graph_user_type = 'Graph::User::Types::User'
This was my dummy initializer inside Engine:
RailsEngine.graph_user_type = 'MockUserType'
I need to ensure that running an importer results in sending out an email.
This is what I got so far:
describe '#import' do
it 'triggers the correct mailer and action', :vcr do
expect(OrderMailer).to receive(:delivery_confirmation).with(order)
Importer.new(#file).import
remove_backed_up_file
end
end
It fails with:
pry(#<ActiveRecord::ConnectionAdapters::TransactionManager>)> error
=> #<NoMethodError: undefined method `deliver_now' for nil:NilClass>
Which obviously can't work out as I am expecting the Mailer class to receive the (instance) method call. But how can I get a hold of the mailer instance that will receive the call? How would you test that a unit's method triggers a certain mailer?
I assume the delivery_confirmation method in reality returns a Mail object. The problem is that ActionMailer will call the deliver method of the mail object. You've set an expectation stubbing out the delivery_confirmation method but you haven't specified what should be the return value. Try this
mail_mock = double(deliver: true)
# or mail_mock = double(deliver_now: true)
expect(mail_mock).to receive(:deliver)
# or expect(mail_mock).to receive(:deliver_now)
allow(OrderMailer).to receive(:delivery_confirmation).with(order).and_return(mail_mock)
# the rest of your test code
If I got you right,
expect_any_instance_of(OrderMailer).to receive(:delivery_confirmation).with(order)
will test the mailer instance that will receive the call.
For more precision you may want to set up your test with the particular instance of OrderMailer (let's say order_mailer) and write your expectation the following way
expect(order_mailer).to receive(:delivery_confirmation).with(order)
Having the following mailer previewer code:
class RegistrationMailerPreview < ActionMailer::Preview
# Preview this email at http://localhost:3000/rails/mailers/registration_mailer/welcome
def welcome
RegistrationMailer.welcome users(:one)
end
end
(full file).
Which is unable to reach my fixtures (users(:one)), return a 500 error status and print out the following error:
NoMethodError: undefined method `users' for #RegistrationMailerPreview
Can we get fixtures entries from mailer previewer?
If yes, I would like to know how to do that.
I have seen that should be possible here, but I can't require test_helper in this file (I don't know why) and I don't understand the difference between ActionMailer::TestCase and ActionMailer::Preview.
If no, is there a way to preview the mail without sending as a parameter User.first, since I could do my tests on a machine on which there is no data filled in the database.
I don't know about the default fixture framework, but I can say using FactoryBot for my fixtures that I was able to use them in my mailer previews simply by prepending the build/create methods with FactoryBot. I didn't need to require anything at the top of the file. i.e.:
class RegistrationMailerPreview < ActionMailer::Preview
def welcome
user = FactoryBot.create(:user)
RegistrationMailer.welcome(user)
end
end
To answer your second question, you could also simply replace the fixture line above with user = User.new(firstname: "Joe"). That would create a new user to use in the preview without persisting it to the database.
Question: Why is the method undefined if it's just there?
Details:
I have a very simple mailer class:
class ProductMailer < ApplicationMailer
def sample_email
mail(to: "me#example.com") # I hardcode my own email just to test
end
end
And a very simple call from ProductsController:
def sample_email
ProductMailer.sample_email().deliver_later
redirect_to #product, notice: 'Email was queued.'
end
The email fails to be sent. I am using Sidekiq to process emails in background. The Sidekiq Web UI shows failed jobs in the Tries page and I can see why it failed:
NoMethodError: undefined method `sample_email' for ProductMailer:Class
I tried to rename the method and restart the server with rails server but none of that removes the error. I am not using any namespaces.
Question: Why is the method undefined if it's just there?
Note: I found out by chance that the method is found if I name it notifybut maybe that's because I'm overwriting some method from ActionMailer base class, I don't know.
Answer: Restart Sidekiq
I created the mailer class before starting Sidekiq, but I renamed the sample_email method while Sidekiq was already running, so it seems that Sidekiq doesn't recognize new methods on-the-fly.
I renamed the method because I am used to development environment, where you can change almost anything on the fly...
It's because you've defined an instance method, and then you try to call it on a class. Change it to
def self.sample_email
....
Rails 4.1 has a nice way to preview mailers with ActionMailer::Preview. All of my mailers take a user parameter, and I would like to pass in current_user (from Devise) for the preview.
If I try this, it doesn't work.
class SubscriptionsMailerPreview < ActionMailer::Preview
# Preview this email at http://localhost:3000/rails/mailers/subscriptions_mailer/new
def new
SubscriptionsMailer.new(current_user)
end
end
It returns undefined local variable or method 'current_user' for #<SubscriptionsMailerPreview:0xa6d4ee4>.
I suspect this is because current_user is defined by Devise in ApplicationController, and according to the docs, ActionMailer uses AbstractController::Base. In that case, would storing current_user in a class variable be a bad idea?
Does anyone know how I can use the current_user helper in ActionMailer::Preview?
What would happen if you move your mailer job to the background? How would you get the current user then?
The mailer and its preview should not know about the current_user. The mailer's job is to send the mail to a user it receives. The preview is there to visually demonstrate its behaviour.
Create a new user in your mailer preview, and pass it to the mailer.
def new
user = User.create! # etc...
SubscriptionsMailer.new(user)
end
It doesn't matter who the user is. It matters that it's a user object.
If you want to test that the application will send a mail to the current_user, write a functional test for that.
You are right method defined in Controller won't be available in helper.
These posts can help you:
Where do I put helper methods for ActionMailer views? Access helpers from mailer?
https://www.ruby-forum.com/topic/168949