In a new Rails application I am developing at the moment, I have a background job that kicks in every few minutes that may need to send emails to users. This background job is started off in config/initializers/start_something.rb. I had multiple problems with this, but the main one is described in the title of this blog post.
First of all, I originally used FooMailer.foo_email(foo, bar).deliver_later. This would just silently do nothing. Mails just didn’t work. Nothing in /var/mail/maillog either. Drop the _later, and you get a stack trace and finally know why emails aren’t being sent: there is a problem rendering the template, in my case, link_to and url_for weren’t working.
The second problem is the main problem. You get a long stack trace like this:
from /home/.../.rvm/gems/ruby-2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/routing/route_set.rb:629:in `generate' from /home/.../.rvm/gems/ruby-2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/routing/route_set.rb:660:in `generate' from /home/.../.rvm/gems/ruby-2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/routing/route_set.rb:707:in `url_for' from /home/.../.rvm/gems/ruby-2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/routing/url_for.rb:172:in `url_for' from /home/.../.rvm/gems/ruby-2.3.0/gems/actionview-5.0.0.1/lib/action_view/routing_url_for.rb:90:in `url_for' from /home/.../.rvm/gems/ruby-2.3.0/gems/actionview-5.0.0.1/lib/action_view/helpers/url_helper.rb:196:in `link_to' from /home/.../kifu-kun/kifukun/app/views/..._mailer/..._email.html.erb:16:in `_app_views_..._mailer_..._html_erb__3955141667319229348_25724800'
And if you place <% byebug %> right before that line 16 in the template, and copy and paste the link_to line into the debugger, you get something like:
*** ActionController::UrlGenerationError Exception: No route matches {:action=>"...", :controller=>"...", :id=>...}
What? After you double and triple-checked the syntax and names of everything, you maybe decide to check the output of Rails.application.routes.routes:
#<ActionDispatch::Journey::Routes:0x00000004a5d940 @routes=[], @ast=nil, @anchored_routes=[], @custom_routes=[], @simulator=nil>
Um, that looks very empty! No routes? (Normally you get a couple screenfuls of stuff.) As stated earlier, we’re using a config/initializers/start….rb file, and I suspected that the routes just aren’t available yet at this point.
Rails.application.config.after_initialize do if defined?(Rails::Server) # don't perform job when running rails c FooJob.perform_now end end
Sorry, tangent: this job is running every two minutes, so it performs itself later at the end of the perform method:
FooJob.set(wait: 2.minutes).perform_later # why does self. not work?
Yeah, self.set(…).perform_later doesn’t seem to work, so just use the full class name. (There are cron gems around, but I opted to skip those to cut down on dependencies. And that’s what got me into this mess. :p)
And we’re back to our after_initialize thing. I found this page titled “Rails initialization and configuration order” and thought stuff run here would be able to take advantage of most or all of Rails’ capabilities. Well, it turns out that routes are special in that regard. Here’s something I found after searching for a while: “Rails initializer that runs *after* routes are loaded?” So the answer to my problem is:
Rails.application.config.after_initialize do if defined?(Rails::Server) # don't perform job when running rails c Rails.application.reload_routes! FooJob.perform_now end end
The third problem is really simple. This is the message:
*** ArgumentError Exception: Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true
That’s a pretty clear message. In other words, you just have to add (e.g.) host: ‘example.com’ (or something from the config) to the (perhaps implicit) options hash ({controller: ‘…’, action: ‘…’}) and you’re set.