I've been trying to implement a DelayedJob custom job for a very long time, but am not finding much information online in terms of how to do this from start to finish, and am finding almost nothing that is not about sending mass emails (I've read collectiveidea's Github intro, Railscasts, SO questions, etc). As someone relatively new to Rails, I imagine that while the instructions are likely clear for someone more experienced, they are not clear enough for someone at my level to understand how to get this to work properly.
The aim of my task is to run the job, and then destroy the object (I am aware that DelayedJob destroys all completed jobs, but I also want the object destroyed from my database as well upon completion of the job.)
Previously, I was doing this with a DelayedJob non-custom job in my controller's create method: user.delay.scrape, followed by user.delay.destroy, which worked well. Therefore, everything else in my application is working fine, and the problem lies strictly in how I am setting up this custom job. However, for various reasons, it would be much better in this case to create a custom job.
Below is the current (non-working) way I have DelayedJob set up in my app. However, when I run the app, the console reports: uninitialized constant UsersController::UserScrapeJob. Any suggestions of how to get this to work properly would be greatly appreciated, and I'd be happy to answer any questions about this request.
Here is my model:
class User < ActiveRecord::Base
def scrape
some code here...
end
end
In my controller, the delayed job needs to function as part of the create method.
And here is my controller (with only the create method shown):
class UsersController < ApplicationController
#user = User.new(params[:user])
if #user.save
Delayed::Job.enqueue UserScrapeJob.new(user.id)
else
render :action => 'new'
end
end
And here is the job file userScrapeJob.rb, which is in the app/jobs folder:
class UserScrapeJob < Struct.new(:user_id)
def perform
user = User.find(user_id)
user.scrape
user.destroy
end
end
You have a typo when you create the job, the name of the class is UserScrapeJob, with a capital 'U' (name of classes in ruby are constants).
Delayed::Job.enqueue UserScrapeJob.new(user.id)
You also have a syntax error in the if, it's if ... else ... end, and not if ... end else ... end
Try renaming your job file from userScrapeJob.rb to user_scrape_job.rb.
When you call UserScrapeJob.new Rails converts the class name to snake case (i.e. user_scrape_job) and looks for the corresponding file of that name, user_scrape_job.rb.
Related
I'm running into a weird bug on Heroku, which I believe may be a race condition, and I'm looking for any sort of advice for solving it.
My application has a model that calls an external API (Twilio, if you're curious) after it's created. In this call, it passes a url to be called back once the third party completes their work. Like this:
class TextMessage < ActiveRecord::Base
after_create :send_sms
def send_sms
call.external.third.party.api(
:callback => sent_text_message_path(self)
)
end
end
Then I have a controller to handle the callback:
class TextMessagesController < ActiveController::Base
def sent
#textmessage = TextMessage.find(params[:id])
#textmessage.sent = true
#textmessage.save
end
end
The problem is that the third party is reporting that they're getting a 404 error on the callback because the model hasn't been created yet. Our own logs confirm this:
2014-03-13T18:12:10.291730+00:00 app[web.1]: ActiveRecord::RecordNotFound (Couldn't find TextMessage with id=32)
We checked and the ID is correct. What's even crazier is that we threw in a puts to log when the model is created and this is what we get:
2014-03-13T18:15:22.192733+00:00 app[web.1]: TextMessage created with ID 35.
2014-03-13T18:15:22.192791+00:00 app[web.1]: ActiveRecord::RecordNotFound (Couldn't find TextMessage with id=35)
Notice the timestamps. These things seem to be happening 58 milliseconds apart, so I'm thinking it's a race condition. We're using Heroku (at least for staging) so I think it might be their virtual Postgres databases that are the problem.
Has anyone had this sort of problem before? If so, how did you fix it? Are there any recommendations?
after_create is processed within the database transaction saving the text message. Therefore the callback that hits another controller cannot read the text message. It is not a good idea to have an external call within a database transaction, because the transaction blocks parts of the database for the whole time the slow external request takes.
The simples solution is to replace after_save with after_commit (see: http://apidock.com/rails/ActiveRecord/Transactions/ClassMethods/after_commit)
Since callbacks tend to become hard to understand (and may lead to problems when testing), I would prefer to make the call explicit by calling another method. Perhaps something like this:
# use instead of .save
def save_and_sent_sms
save and sent_sms
end
Perhaps you want to sent the sms in the background, so it does not slow down the web request for the user. Search for the gems delayed_job or resque for more information.
Do you have master/slave database where you always write to master but read from slave? This sounds like the db replication lag.
We solved such problems by forcing a read being made from the master database in the specific controller action.
Another way would be to call send_sms after the replication has been finished.
I've been trying to figure this out for a long time, and can't figure it out.
I am using DelayedJob in my Rails app in order to run a script to fill out some forms on a website via a Mechanize script. However, after the job completes, I don't want any record of the entry to be stored in any database in my application, as there is no reason anyone should access it again.
The process works perfectly when I ran it as a simple background method within the controller's create method - that is, by calling #course.delay.scrape right after if #course.save. But now that I want to destroy the object right after the background job finishes, I believe I need to create a custom job, and am struggling with that.
I am aware that the DelayedJob documentation lists the method def after(job). In order to use that method, I need to create a custom job. I'm confused about how to create a custom job, as nearly every example I can find is for sending mass emails, whereas this is for a different purpose. I don't know how to get the script to run this way.
If you can help me with fixing up this code at all, that would be greatly appreciated! I've tried many variations, looking at as many examples as possible. I'm aware it has at least a few errors, but am not advanced enough to know what to change. This is the last thing I tried before throwing in the towel.
Here is my model (in models/course.rb):
class Course < ActiveRecord::Base
after_create :send_to_delayed_job
def scrape
...Mechanize script goes here ....
end
def send_to_delayed_job
Delayed::Job.enqueue CourseJob.new(self.id), :queue => 'mycoursequeue'
end
end
Here is my job (in models/course_job.rb):
class CourseJob < Struct.new(:course_id)
def perform
course = Course.find(self.id)
course.scrape
end
def after(job)
Course.destroy(params[:id])
end
end
Can we just have course.destroy as the last line of CourseJob#perform method?
So i have many database operations that i put into my helpers that i want to do in the background. As an example, I define a record_activity method in my Users helper. When a Post gets created I want to record this activity ie in the create method in the Posts controller:
def create
#operations to save the post
record_activity(user, post)
end
For performance reasons, I want to delay this record_activity and others, and run them with workers on the back-end. I use delayed_job for delaying mailers and it works excellently. In rails console, method.delay works great ie I could in rails console do:
record_activity.delay
However, the same .delay doesn't work when written in a controller ie the following still runs live, not delayed:
def create
#operations to save the post
record_activity(user, post).delay
end
Why is this? I'm using Rails 3.0.9 in one app and Rails 3.1.3 in another, plus I have delayed_job version 2.1.4.
Can anyone suggest how to make this work?
EDIT *
I think the answer provided by mu_is_too_short is the right path. It creates a delayed job, only it doesn't execute the record_activity method properly. When the delayed_job worker starts, it executes the delayed_job and has no errors, and deletes the record as if it worked. But no activity gets recorded. To give some context, here is the call and the method i am troubleshooting now:
self.delay.record_activity(user, #comment)
ANd the method:
def record_activity(current_user, act)
activity = Activity.new
activity.user_id = current_user.id
activity.act_id = act.id
activity.act_type = act.class.name
activity.created_at = act.created_at
activity.save
end
I then thought that maybe I couldn't pass user variables through, in this case, so I tried to just pass integer values and so on. I restarted the delayed_job workers and tried these methods, to no avail:
self.delay.record_activity(user.id, #comment.id, #comment.class.name, #comment.created_at)
And the altered method:
def record_activity(current_user_id, act_id, act_type, act_created_at)
activity = Activity.new
activity.user_id = current_user_id
activity.act_id = act_id
activity.act_type = act_type
activity.created_at = act_created_at
activity.save
end
I don't think record_activity.delay in the console is working the way you think it is. That will execute record_activity before delay has a chance to do anything.
The delay call has to go first, then you call your delayed method on what delay returns:
def create
self.delay.record_activity(user, post)
end
The delay call will return an object that intercepts all method calls (probably through method_missing), YAMLizes them, and adds the YAML to its delayed job queue table in the database. So, just saying record_activity.delay doesn't do anything useful, it just executes record_activity, creates the delayed-job interceptor object, and throws away what delay created.
I'm trying to implement a scheduler task that deletes a user in user table who got abused more than 5 times. To achieve this in the user.rb file I have return a method report_abuse_delete method which performs the functionality of finding the user who got abuses more than 5 times and delete his records from the database.
Here is my method in User model:
def report_abuse_delete
#delete_abused_user= Abuse.find(:all, :conditions=>['count>=?',5])
#delete_abused_user.each do |d|
#abused_user= User.find(d.abuse_id)
if #abused_user.delete
render :text=> "User account has been deleted. Reason: This user has been reported spam for more than 5 times"
UserMailer.user_delete_five_spam_report(#user).deliver
end
end
end
And this is what I have written in the Scheduler.rb file
every 2.minutes do
rake "log:clear", :environment => "development"
runner "User.report_abuse_delete", :environment => "development"
end
As you can see in the scheduler.rb file I'm trying to perform a 2 functions one is clearing my log for every 2minutes and trying to run a method report_abuse_delete that I wrote in my model.
I'm facing a issue as follows for every 2 minutes my log is getting cleared but the method which I wrote in the model in not getting invoked I guess the functionality is not getting triggered. I have searched all the web and checked every possible way. I'm unable to figure out what was the problem is.
Help me out please. Any kind of help is welcome and appreciable.
You've defined report_abuse_delete as a normal - that is instance - method, but you're calling it as a class method. Try defining the method as def self.report_abuse_delete.
Also, I don't know if the render call will work: I haven't used this gem, but since you don't have any kind of user agent to see the text, I'm not sure what you'd expect it to do.
So I'm using Delayed::Job workers (on Heroku) as an after_create callback after a user creates a certain model.
A common use case, though, it turns out, is for users to create something, then immediately delete it (likely because they made a mistake or something).
When this occurs the workers are fired up, but by the time they query for the model at hand, it's already deleted, BUT because of the auto-retry feature, this ill-fated job will retry 25 times, and definitely never work.
Is there any way I can catch certain errors and, when they occur, prevent that specific job from ever retrying again, but if it's not that error, it will retry in the future?
Abstract the checks into the function you call with delayed_job. Make the relevant checks wether your desired job can proceed or not and either work on that job or return success.
To expand on David's answer, instead of doing this:
def after_create
self.send_later :spam_this_user
end
I'd do this:
# user.rb
def after_create
Delayed::Job.enqueue SendWelcomeEmailJob.new(self.id)
end
# send_welcome_email_job.rb
class SendWelcomeEmailJob < Struct(:user_id)
def perform
user = User.find_by_id(self.user_id)
return if user.nil? #user must have been deleted
# do stuff with user
end
end