Delayed Jobs not picking up second ActionMailer action - ruby-on-rails

My problem is pretty simple, I am trying to delay 2 ActionMailer actions on the same method, this way:
#params = params
begin
ContactMailer.delay.email_application(#params)
ContactMailer.delay.email_application_confirmation(#params)
end
The first one is being sent to the website's admin, the second one to the user submitting the form.
But only the first one is being picked up by delayed_job and added to the jobs queue. What's going on with the second one? (I tried to add begin``end for that reason but it didn't change anything).
EDIT: I should mention that, looking at the logs, nothing appears, as if the second line was completely being ignored

this fixed it...
#params = params
begin
ContactMailer.delay.email_application(#params)
end
begin
ContactMailer.delay.email_application_confirmation(#params)
end

Related

Rails/Capybara- Test that form does not submit

In my Rails app, I used the following jQuery to disable the ability to submit a form by pressing 'enter' in an input field:
$('input').on('keydown', function(e) {
if(e.which == '13') {
return false;
}
})
I wanted to test this with Capybara, but can't figure out how. For example:
# the form being tested creates a new Shift object:
test "enter does not submit form" do
shift_count = Shift.count
# fill in form, etc...
page.driver.browser.execute_script "var e = jQuery.Event('keydown'); e.which = 13; $('#shift_employee').trigger( e );"
assert Shift.count == shift_count
end
This test passes even when the jQuery function is commented out, presumably because it calculates Shift.count before ActiveRecord has had time to save the new the record to the database.
I also tried having the jQuery function do a custom $.get() request, to which the controller responds with simple text, thinking that this would build in enough time for all server-side work to be completed. But this still doesn't work, and I think it's because, no matter how I set up the jQuery function, the submission of the form and saving of the new record will always occur after any AJAX stuff I build in.
Any ideas on how I can reliably test that the form was not submitted?
So the form submission is done via AJAX? This is a common problem that testers run in to with Capybara. What you need to do is wait for the AJAX to complete.
There are two ways to do this. One is very specific and is the preferred method, the other is generic and should only be used if the first method can't be done.
The first way is to wait for content to change on the DOM. In your case nothing might change, but if, for example, you had a warning come up saying "You cannot press Enter, you must click the Save button!" then you could wait for that to come up by doing something like:
page.driver.browser ... # your code
page.should have_css(".info", :text => "You cannot ...etc...")
Of course you don't have to use the :text option though.
The other way is to make a helper method, like wait_for_ajax, that does a generic wait until AJAX is complete.
def wait_for_ajax
start = Time.now
while true
break if (page.evaluate_script('$.active') == 0)
if Time.now > start + Capybara.default_wait_time.seconds
fail "AJAX did not register as complete after #{Capybara.default_wait_time} seconds!"
end
sleep 0.1
end
end
This is based off the old way that people used to check for AJAX, but this new script is preferred for Capybara 2.0.
Then your step would do:
page.driver.etc
wait_for_ajax
That wait_for_ajax method can go in to any file Cucumber will load.
I got it to work by having Capybara revisit the page, and then checking Shift.count. The test now fails when the jQuery function is commented out, and passes when the function is uncommented. I'd still like to know if there's a better way, though.

How to Make the Controller wait for a Delayed Job while the rest of the App continues on?

(This question is a follow-up to How do I handle long requests for a Rails App so other users are not delayed too much? )
A user submits an answer to my Rails app and it gets checked in the back-end for up to 10 seconds. This would cause delays for all other users, so I'm trying out the delayed_job gem to move the checking to a Worker process. The Worker code returns the results back to the controller. However, the controller doesn't realize it's supposed to wait patiently for the results, so it causes an error.
How do I get the controller to wait for the results and let the rest of the app handle simple requests meanwhile?
In Javascript, one would use callbacks to call the function instead of returning a value. Should I do the same thing in Ruby and call back the controller from the Worker?
Update:
Alternatively, how can I call a controller method from the Worker? Then I could just call the relevant actions when its done.
This is the relevant code:
Controller:
def submit
question = Question.find params[:question]
user_answer = params[:user_answer]
#result, #other_stuff = SubmitWorker.new.check(question, user_answer)
render_ajax
end
submit_worker.rb :
class SubmitWorker
def check
#lots of code...
end
handle_asynchronously :check
end
Using DJ to offload the work is absolutely fine and normal, but making the controller wait for the response rather defeats the point.
You can add some form of callback to the end of your check method so that when the job finishes your user can be notified.
You can find some discussion on performing notifications in this question: push-style notifications simliar to Facebook with Rails and jQuery
Alternatively you can have your browser periodically call a controller action that checks for the results of the job - the results would ideally be an ActiveRecord object. Again you can find discussion on periodic javascript in this question: Rails 3 equivalent for periodically_call_remote
I think what you are trying to do here is little contradicting, because you use delayed_job when do done want to interrupt the control flow (so your users don't want to want until the request completes).
But if you want your controller to want until you get the results, then you don't want to use background processes like delayed_job.
You might want to think of different way of notifying the user, after you have done your checking, while keeping the background process as it is.

Capybara skips a controller action but still ends up with the proper view, so the tests pass

This is the most confusing thing I have dealt with in a long time. I have a controller action that sends an e-mail when a comment is made:
class CommentsController < ActionController::Base
def create
#comment = Comment.new(params[:comment])
#comment.author = current_user
#comment.save
CommentMailer.recent_comment_made(#comment).deliver
respond_with(#comment)
end
end
I would like to test it. My tests go to the proper page (I've verified this), where there is a form with action /comments. It then submits the form and returns the view with a successful flash (using FlashResponder) and everything seems great... but the e-mail is never sent. In fact, the entire create action is never called. The weird thing is the same process works in development but sends the e-mail!
Now I know it's getting to the correct controller, because I can add this:
before_filter { raise }
And the tests fail. I can add:
before_filter { p params }
And I see the parameters, which have controller as 'comments' and action as 'create'. But if I add:
def create
raise
...
No exception is raised. In fact I can just comment out the entire create method and the test still passes, with the comment being created and everything. I am not using InheritedResources or anything of that kind. And like I said... it works on development!
I've used save_and_open_page after every step and it all looks good. The form action is correct. The flash message is correct. The assertion that the comment is created is correct... even when the create method is commented out completely.
Originally I thought that it was the wrong controller or that Cucumber was using some older version of my controller for some unknown reason, but when I add the before_filters to raise/print params... that all happens and works as expected.
Does anyone know what could be going on here, or any way I can at least SEE what is going on here? I am all out of ideas. My feature looks like this:
Given I visit the page
And I enter a comment
When I submit the comment
Then the e-mail is delivered
And the comment is saved to the database
These are made more generic than they actually are to conceal the actual intent of the project. The step definition pseudo code is:
visit ...
fill_in ...
find('submit button').click
assert ActionMailer::Base.deliveries.include? ...
assert comments.present?
Pretty simple stuff. Visit a page, submit a form, assert that the stuff in the create action worked.
WOW...
What happened was there was a copy of the controller in our api/v1 directory (but not namespaced to api/v1) on accident. The tests were loading that version, while development was loading the 'actual' version.

Redirect action not working

I'm guessing this one's pretty easy to do, but I don't know how, so I figured I'd ask.
I have a delay job in my app that calls a roll_back method in my Order model if something hasn't happened within five minutes. That method both destroys the instance it's called on and, before doing so, does a bunch of related things to associated models. The method is working just great.
The trouble is, once the method is done running, most users will be on the show page for the Order that has just been deleted, and I was hoping to redirect users to a different page once the time ran out and that roll_back method was called.
So I originally just tried this in the rollback method, right after the "self.destroy" line.
redirect_to "/orders/new", :notice => "Blah"
Didn't work, though. And at any rate, some poking around on the internet informed me that actions like redirect should be in the controller instead.
So I instead went and put that line in the Orders Controller's destroy method, right after #order.destroy. I figured when the roll_back method called self.destroy, it'd then do the redirect -- but that didn't work either, I'm guessing because the model destroy doesn't go through the controller.
So I'm not really sure how to do this. All the delay job/rollback stuff is in my model, but I'm apparently not supposed / unable to redirect users from the model. Any idea how to do this?
Tried this again, with this in my method, in case it was a routing error (I know orders_path to exist).
def destroy
#order = Order.find(params[:id])
#order.destroy
redirect_to orders_path, notice: "blah"
end
As you've already discovered, you can't respond to no request (in other words, you need a request, if you are to respond). So, what do you do when your displayed information gets stale? StackOverflow uses WebSockets to auto update some pages, including this one. You may want to check out How do real time updates work? for more information. Another technique would be to use a javascript request to make a request to your app, to verify that the order is still valid. You could easily redirect if the result indicates that the order is no longer valid. Anyway, hope this helps - good luck!

How can I abort the delivery of an ActionMailer request?

I'm running a Q&A service. One of the things admins can do is mark a question as offtopic. When they they do that an email gets sent to the person that asked the question telling them that their email is offtopic.
The email notification is sent via delayed_job:
QuestionMailer.delay.notify_asker_of_offtopic_flag question
However, on occasion someone might accidentally mark the question as offtopic or change their mind. To avoid an incorrect notification going to the person who originally asked it I want to create a short delay and evaluate whether the question is still offtopic when the mailer request runs:
Delayed call to mailer:
QuestionMailer.delay(run_at: Time.now + 3.minutes).notify_asker_of_offtopic_flag(question)
Mailer:
class QuestionMailer
...
def notify_asker_of_offtopic_flag question
if question.offtopic?
# do mailing
end
end
end
Unfortunately this isn't that simple since the if block simply causes an error which then causes delayed_job to retry the job again and again.
I'm now experimenting with some pretty roundabout methods to achieve the same end but I'd really like to find some way to abort the QuestionMailer action without triggering errors. Is this possible?
Dont delay the mailer then. Delay another class method in your Question class perhaps? Pass the id of the question and within that delayed method check if the question is still offtopic and if yes the send email synchronously.
Essentially, your notify_asker_of_offtopic_flag could be moved to your question model and then mailing is synchronous (i'm sure you'll rename your methods).
There is talk going on about preventing delivering by setting perform_deliveries to false within your mail action itself in core but i'm not 100% where or how that will end up.
#Aditya's answer was basically correct however I wanted to keep my methods on the Mailer object to keep things nice and tidy. This required a few extra hacks.
Create a new Class method in the mailer that CAN be delayed
The problem with trying to cancel an instance Mailer method is that it inherently triggers rendering and other things that stop the method from being aborted. However I would still like to keep all my Mailer logic together.
The way I did this was by using a class method instead of an instance method. This avoided all of the hooks that kick in when calling the method on an ActionMailer instance but still allowed me to keep the code tidy and together
class QuestionMailer
...
def notify_asker_of_offtopic_flag question
...
end
def self.notify_asker_of_offtopic_flag question_if question
if question.offtopic?
QuestionMailer.notify_asker_of_offtopic_flag question
end
end
end
NB fix for using delayed job
This works except for one slight hack that's necessary to deal with delayed_job.
When dealing with a Mailer, delayed_job will always call .deliver on the returned object in order to deliver the mail. This is fine when we return a mail object but in this case we're returning nil. delayed_job therefore tries to call .deliver on nil and everything fails.
In order to account for this we simply return a dummy mailer object containing a dupe .deliver method:
class QuestionMailer
...
class DummyMailer
def deliver
return true
end
end
def notify_asker_of_offtopic_flag question
# do mailing stuff
end
def self.notify_asker_of_offtopic_flag question_if question
if question.offtopic?
QuestionMailer.notify_asker_of_offtopic_flag question
else
DummyMailer.new
end
end
end

Resources