testing emails with capybara and delayed_job - ruby-on-rails-3

inspired by the episode of railscast (http://railscasts.com/episodes/275-how-i-test) i tried to add some request specs to my app.
using delayed_job for sending my emails, i did not found an easy way to test the sending of emails within my capybara test. i tried:
it "emails user when requesting password reset" do
...(some user action that triggers sending an email)...
Delayed::Worker.new.work_off
ActionMailer::Base.deliveries.last.to.should include(user.email)
end
thanks for any hints or help!

Much easier solution: Just create an initializer in config/initializers, e.g. delayed_job_config.rb
Delayed::Worker.delay_jobs = !Rails.env.test?
In your actual test you do not even need to know that delayed_job is deployed to send the mails, which keeps them clean and simple.

well, it should work. i found some misconfigurations in my app.
check you test env. you should set:
Staffomatic::Application.configure do
...
config.action_mailer.delivery_method = :test
config.action_mailer.default_url_options = { :host => "example.com" }
end
i added a line in the mailer macros module from the railscast to work off the Delayed::Job:
module MailerMacros
def last_email
Delayed::Worker.new.work_off
ActionMailer::Base.deliveries.last
end
def reset_email
Delayed::Job.destroy_all
ActionMailer::Base.deliveries = []
end
end
now you can check with:
last_email.to.should include(user.email)
your last email.
pretty easy!
ps. if you have the awesome mail_safe gem installed you should make sure it's not in the test env.!

Related

Resque and Resque_mailer with Devise

I am implementing background email processing with Resque using the resque_mailer gem (https://github.com/zapnap/resque_mailer). I was able to get it to work for all my emails except the ones sent by Devise.
I went through a bunch of SO questions, and blog posts (for instance http://teeparham.posterous.com/send-devise-emails-with-resque) but could not find a way to get it to work.
What are the precise steps to follow to get resque_mailer to work with Devise?
I went through tee's answer and several resources online, but couldn't find a working solution.
After a few days of reading through resque-mailer and devise code, a solution that worked for me. Thanks to tee for gist which put me in right direction.
Assuming your app/mailers/application_mailer.rb looks similar to
class ApplicationMailer < ActionMailer::Base
include Resque::Mailer # This will add a `self.perform` class method, which we will overwrite in DeviseResqueMailer
end
In config/initializers/devise.rb
Devise.parent_mailer = "ApplicationMailer"
Devise.setup do |config|
config.mailer = 'DeviseResqueMailer'
end
In the resource class which uses devise, overwrite the send_devise_notification method to send resource class and id instead of object to prevent marshalling
# app/models/user.rb
protected
def send_devise_notification(notification, *args)
# Based on https://github.com/zapnap/resque_mailer/blob/64d2be9687e320de4295c1bd1b645f42bd547743/lib/resque_mailer.rb#L81
# Mailer may completely skip Resque::Mailer in certain cases - and will fail as we write custom handle in DeviseResqueMailer assuming mails are handled via resque
# So in those cases, don't retain original devise_mailer so things work properly
if ActionMailer::Base.perform_deliveries && Resque::Mailer.excluded_environments.exclude?(Rails.env.to_sym)
# Originally devise_mailer.send(notification, self, *args).deliver
# Modified to ensure devise mails are safely sent via resque
resource_id, resource_class = self.id, self.class.name
devise_mailer.send(notification, {resource_id: resource_id, resource_class: resource_class}, *args).deliver
else
super
end
end
Finally, in app/mailers/devise_resque_mailer.rb, fetch the record again from the database and continue
class DeviseResqueMailer < Devise::Mailer
def self.perform(action, *args)
# Hack to prevent RuntimeError - Could not find a valid mapping for admin.attributes
record_hash = args.shift
record = record_hash["resource_class"].constantize.find(record_hash["resource_id"])
args.unshift(record)
super # From resque-mailer
end
end
I feel this approach is a better than using devise-async as all the mails go through same code path. Its easier to control and overwrite if needed.
I'd take a look at devise-async. Looks like it fits your use case. Devise Async

Ruby on Rails with Sorcery reset password email has a undefined method error

Using Sorcery 0.7.4 with Rails 3.1.1 for authentication.
Everything was going well until I tried to setup password resetting.
Activation works perfectly and emails are sent, but for some reason I get this error when trying to send the reset password email.
undefined method `reset_password_email' for nil:NilClass
I copied the tutorial exactly, and when I did a quick test in the console it shot off the email as expected. In console:
user = User.find(1)
user.deliver_reset_password_instructions!
In the actual controller, it finds the user by the email submitted from the form and in the log I can see it is retrieving the right user and setting the token, but errors out as above and rolls back.
I checked the gem's code for deliver_reset_password_instructions! and there seems to be no reason for it to fail.
PasswordResetsController:
#user = User.find_by_email(params[:email])
#user.deliver_reset_password_instructions! if #user
The following is copied from the gem code:
Instance Method in Gem:
def deliver_reset_password_instructions!
config = sorcery_config
# hammering protection
return false if config.reset_password_time_between_emails && self.send(config.reset_password_email_sent_at_attribute_name) && self.send(config.reset_password_email_sent_at_attribute_name) > config.reset_password_time_between_emails.ago.utc
self.send(:"#{config.reset_password_token_attribute_name}=", TemporaryToken.generate_random_token)
self.send(:"#{config.reset_password_token_expires_at_attribute_name}=", Time.now.in_time_zone + config.reset_password_expiration_period) if config.reset_password_expiration_period
self.send(:"#{config.reset_password_email_sent_at_attribute_name}=", Time.now.in_time_zone)
self.class.transaction do
self.save!(:validate => false)
generic_send_email(:reset_password_email_method_name, :reset_password_mailer)
end
end
The method called above for mailing:
def generic_send_email(method, mailer)
config = sorcery_config
mail = config.send(mailer).send(config.send(method),self)
if defined?(ActionMailer) and config.send(mailer).superclass == ActionMailer::Base
mail.deliver
end
end
Again all the required mailer bits and pieces are there and work from the console.
Uncomment this lines in the sorcery initializer
user.reset_password_mailer = UserMailer
user.reset_password_email_method_name = :reset_password_email
Check your app/mailers/user_mailer.rb file.
If you were following the tutorial you probably did something like copy and paste the method definition from the wiki (which takes one parameter) into the generated method definition (which doesn't take any parameter), hence the 1 for 0 ArgumentError.
In other words, you likely have something that looks like this:
def reset_password_email
def reset_password_email(user)
This is bad, but an easy fix :-)

Deny entire application with Rack?

I have a client who wishes to lock their entire application under certain conditions. This seems like a good job for Rack Middleware, so I wrote the following:
class Duress
def initialize(app)
#app = app
end
def call(env)
if env['UNDER_DURESS'] == true
# Return HTTP 503 - Service Unavailable
[503, {"Content-Type" => "text/html"},["<h1>Service Unavailable.</h1><p>Please try again later.</p>"]]
else
#app.call(env)
end
end
end
Firstly, I am not sure if an environment variable is necessary or even best practice, so I am open to ideas here. I am not even sure the above code will work.
Secondly, I am having problems hooking into the Devise login process to set this environment variable. I have tried over riding the after_sign_in_path_for method in application_controller, but it does not seem to to triggered. Any tips here?

uninitialized constant Twitter::OAuth

I'm struggling to get my app to display a timeline of feeds from my app. So far I've used the oauth-plugin, oauth and twitter gems (for rails3) to get it authorised. This has worked just fine.
Now I'm struggling when I try and connect.
I end up with an error:
uninitialized constant Twitter::OAuth
Have checked I don't have another action calling twitter (as in another post here). But so far, no luck.
Hope someone can help!
Edit -
I forgot to mention I'm using Devise to authenticate my users. Have tried inserting:
require 'twitter'
But still no success..
-- EDIT TWO --
Found a solution on the twitter gem git site about depreciating this in version 1.0.
I've now replaced the code in my twitter_token.rb file with:
def client
unless #client
#twitter_oauth=Twitter::Client.new(:TwitterToken.consumer.key,:TwitterToken.consumer.secret)
#twitter_oauth.authorize_from_access(token,secret)
#client=Twitter::Base.new(#twitter_oauth)
end
Which gets rid of that error but now leads to another :(
undefined method `consumer' for :TwitterToken:Symbol
I have also tried this:
def client
unless #client
#twitter_oauth=Twitter::Client.new(:oauth_token =>'TwitterToken.consumer.key', :oauth_token_secret=>'TwitterToken.consumer.secret')
#twitter_oauth.authorize_from_access token,secret
#client=Twitter::Base.new(#twitter_oauth)
end
Which gives the following error:
undefined method `authorize_from_access' for #<Twitter::Client:0x00000102da1530>
Any ideas? I'm going insane!
I'm going to answer my own question here - if it helps one person, it's worth it considering I lost three days to it.
Using the latest twitter gem, devise and oauth-plugin. I was seeing a lot of errors. The latest twitter_token controller on the oauth-plugin site does not work, even though it's been updated for a recent twitter gem..
In the end, I deleted my entire twitter_token.rb file and started again:
require 'twitter'
class TwitterToken < ConsumerToken
TWITTER_SETTINGS={:site=>"http://api.twitter.com", :request_endpoint => 'http://api.twitter.com',}
def self.consumer
#consumer||=OAuth::Consumer.new credentials[:key],credentials[:secret],TWITTER_SETTINGS
end
def client
Twitter.configure do |config|
config.consumer_key = TwitterToken.consumer.key
config.consumer_secret = TwitterToken.consumer.secret
config.oauth_token = token
config.oauth_token_secret = secret
end
#client ||= Twitter::Client.new
end
end
You can then update twitter using something like this:
<%= current_user.twitter_token.client.update("At last it's working!") %>
Also, make sure you're using the rails3 branch of the oauth-plugin..

How can I minimize logging of monitoring requests?

My rails app is pinged every minute for a health check and I want to keep these out of the log unless there is an error. I was able to do this in Rails 2.3.5 by setting the logger with this in application_controller.rb:
def logger
if params[:__no_logging__] == 'true' && params[:controller] == 'welcome'
&& params[:action] == 'index'
# ignore monitoring requests
RAILS_MONITOR_NULL_LOGGER
else
RAILS_DEFAULT_LOGGER
end
end
But this doesn't work in Rails 3.0.5
I've been able to put together a new solution by monkeypatching before_dispatch and after_dispatch in Rails::Rack::Dispatch:
require 'active_support/core_ext/time/conversions'
module Rails
module Rack
# Log the request started and flush all loggers after it.
class Logger
include ActiveSupport::BufferedLogger::Severity
def before_dispatch(env)
request = ActionDispatch::Request.new(env)
#path = request.filtered_path
path = request.fullpath
if request.path == '/' && request.parameters['__no_logging__'] == 'true'
#log_level = logger.level
logger.level = Logger::ERROR
#logger.level = 3
end
info
"\n\nStarted #{request.request_method}
\"#{path}\" " \
"for #{request.ip} at #{Time.now.to_default_s}"
end
def after_dispatch(env)
logger.level = #log_level unless #log_level.nil?
ActiveSupport::LogSubscriber.flush_all!
end
end
end
end
I put the patch in config/initializers/monkey_patch.rb
This works exactly as I need, I don't see this request in the log:
http://mydomain.com?__no_logging__=true
But all other request remain in the log unaffected
But there are still two problems:
1. I needed to comment out:
path = request.filtered_path
Because it causes this error:
ERROR NoMethodError: undefined method `filtered_path' for #<ActionDispatch::Request:0x105b4c0e8>
/ce_development/Rails/g3/config/initializers/monkey_patches.rb:52:in `before_dispatch'
/ce_development/Rails/g3/.bundle/ruby/1.8/gems/railties-3.0.5/lib/rails/rack/logger.rb:12:in `call'
...
I now understand this is not a problem. The offending method "request.filtered_path" doesn't exist in Rails 3.0.5, which I am using. I inadvertently copied my class from Rails 3.1.0.beta which does define filtered_path. Rails 3.0.5 uses request.fullpath as now shown above.
2. I needed to comment out
logger.level = Logger::ERROR
Because it causes this error:
ERROR NameError: uninitialized constant Rails::Rack::Logger::ERROR
/ce_development/Rails/g3/config/initializers/monkey_patches.rb:57:in `before_dispatch'
/ce_development/Rails/g3/.bundle/ruby/1.8/gems/railties-3.0.5/lib/rails/rack/logger.rb:12:in `call'
...
I solved this second problem by adding this line above
include ActiveSupport::BufferedLogger::Severity
I'm new to monkey patching and I can't figure out how to get filtered_path or Logger::Error defined in my patch. I've tried other requires, but no luck yet.
I'd also like any advice about the robustness of using this monkey patch on my project. Is there a better way to do this?
I know some people don't believe in altering logs, but I don't want all these pings in the log unless there is an error during its request.
A possible solution for Rails 3 where the Logger is replaced by a Custom Logger is described here: Silencing the Rails log on a per-action basis and here: How can I disable logging in Ruby on Rails on a per-action basis?. I had to add require 'rails/all' to the custom_logger.rb class to make it work.