I'm trying to create a simple form to collect an email address and then send an email to me.
I'm randomly getting uninitialized constant Marketing::InviteController::MarketingMailer
, sometimes on the first time the form is submitted, but ALWAYS the second time is submitted, so if you go to localhost:3000/request-invite, enter an email and submit the form, then enter an email and submit the form again, you will get this error.
Any ideas why i'm getting this error even?
/controllers/marketing/invite_controller.rb
class Marketing::InviteController < ApplicationController
layout 'marketing/layouts/layout'
# GET /request-invite
def new
@invite = Invite.new
respond_to do |format|
format.html # new.html.erb
end
end
# POST /request-invite
def create
@invite = Invite.new(params[:invite])
respond_to do |format|
if @invite.save
MarketingMailer.invite(@invite.email).deliver
format.html { redirect_to request_invite_path, notice: 'Success' }
else
format.html { render action: "new", error: 'An error has occurred' }
end
end
end
end
/views/marketing/invite/new.html.erb
<%= render :partial => 'shared/messages', :locals => {:object => @invite} %>
<%= render :partial => 'shared/object_errors', :locals => {:object => @invite} %>
<%= form_for @invite, :url => request_invite_path, :method => :post do |f| %>
<%= f.text_field :email, :placeholder => 'Email' %>
<%= f.submit 'Send Request', :class => 'btn' %>
<% end %>
/mailers/marketing/marketing_mailer.rb
class Marketing::MarketingMailer < ActionMailer::Base
require 'mail'
address = Mail::Address.new "test@test.com" # ex: "john@example.com"
address.display_name = "Test" # ex: "John Doe"
# Set the From or Reply-To header to the following:
address.format # returns "John Doe <john@example.com>"
default from: address
# Sends an email when they request an invite
def invite(to)
@to = to
mail(:subject => "Jobfly Invite Request", :to => 'test@test.com', :reply_to => @to)
end
end
/views/marketing/marketing_mailer/invite.html.erb
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
</head>
<body>
<p>The following email has requested an invite for jobfly</p>
<p>Email: <%= @to %></p>
</body>
</html>
Log sample:
Started GET "/request-invite" for 127.0.0.1 at 2013-12-10 17:41:44 -0600
Processing by Marketing::InviteController#new as HTML
Rendered shared/_messages.html.erb (0.1ms)
Rendered shared/_object_errors.html.erb (0.1ms)
Rendered marketing/invite/new.html.erb within marketing/layouts/layout (3.0ms)
Completed 200 OK in 72ms (Views: 71.3ms | ActiveRecord: 0.0ms)
Started POST "/request-invite" for 127.0.0.1 at 2013-12-10 17:41:49 -0600
Processing by Marketing::InviteController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"RTVaxb3jK9VwS0/SZ1e6JjMeA6XVqBof44m04Wsvbd8=", "invite"=>{"email"=>"jeff@gmail.com"}, "commit"=>"Send Request"}
(0.2ms) BEGIN
Invite Exists (0.3ms) SELECT 1 AS one FROM `invites` WHERE `invites`.`key` = 'SZNu9vbl' LIMIT 1
SQL (33.8ms) INSERT INTO `invites` (`created_at`, `email`, `key`, `updated_at`) VALUES ('2013-12-10 23:41:49', 'jeff@gmail.com', 'SZNu9vbl', '2013-12-10 23:41:49')
(0.4ms) COMMIT
Completed 500 Internal Server Error in 43ms
NameError - uninitialized constant Marketing::InviteController::MarketingMailer:
...
It sounds like autoloading issues. Have you tried referencing the MarketingMailer constant using the full name? ::Marketing::MarketingMailer ?
Rails doesn't load all classes in development. Instead, when a class is missing, Rails tries to guess where in the project to find the class, based on a set of autoloading paths.
For instance, all of the folders in your app folder are added to the autoloading paths. So if you reference Foobar.new, Rails will look in your app/models or app/controllers for a foobar.rb file, that it expects to contain a class Foobar
definition. However, when you use modules, the waters become more muddy.
class Foo::Bar
# some code
Baz.new
end
Rails will now think that the Baz class is defined in a baz.rb class that is located in subfolders, so it will look in the autoload folders and search for subfolders named according to the enclosing modules, checking app/models/foo/bar/baz.rb and app/controllers/foo/bar/baz.rb
When it doesn't find anything, Rails has different ways of trying to resolve it which I don't fully understand. However, if you prefix your class reference with two colons, that means you are referencing from the root namespace and Rails should not try to search relative to your current module, so if we take the above example again:
class Foo::Bar
...
Baz.new # Searches for foo/bar/baz.rb
::Baz.new # Searches for baz.rb
::My::Baz.new # Searches for my/baz.rb
My::Baz.new # Searches for foo/bar/my/baz.rb
...
end
In production mode, most of these problems disappear, because Rails preloads all source files in your project, so if Baz
has been defined anywhere in any folder, Rails doesn't have to make guesses about where to find it. It already exists, because it has been loaded.