Implementing a schedular task using whenever gem in rails 3 - ruby-on-rails

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.

Related

Race Condition in Ruby on Rails

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.

Trouble getting DelayedJob custom job to work: uninitialized constant UsersController::UserScrapeJob

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.

Writing a DelayedJob custom job to run script and then destroy object

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?

Catching errors with Ruby Twitter gem, caching methods using delayed_job: What am I doing wrong?

What I'm doing
I'm using the twitter gem (a Ruby wrapper for the Twitter API) in my app, which is run on Heroku. I use Heroku's Scheduler to periodically run caching tasks that use the twitter gem to, for example, update the list of retweets for a particular user. I'm also using delayed_job so scheduler calls a rake task, which calls a method that is 'delayed' (see scheduler.rake below). The method loops through "authentications" (for users who have authenticated twitter through my app) to update each authorized user's retweet cache in the app.
My question
What am I doing wrong? For example, since I'm using Heroku's Scheduler, is delayed_job redundant? Also, you can see I'm not catching (rescuing) any errors. So, if Twitter is unreachable, or if a user's auth token has expired, everything chokes. This is obviously dumb and terrible because if there's an error, the entire thing chokes and ends up creating a failed delayed_job, which causes ripple effects for my app. I can see this is bad, but I'm not sure what the best solution is. How/where should I be catching errors?
I'll put all my code (from the scheduler down to the method being called) for one of my cache methods. I'm really just hoping for a bulleted list (and maybe some code or pseudo-code) berating me for poor coding practice and telling me where I can improve things.
I have seen this SO question, which helps me a little with the begin/rescue block, but I could use more guidance on catching errors, and one the higher-level "is this a good way to do this?" plane.
Code
Heroku Scheduler job:
rake update_retweet_cache
scheduler.rake (in my app)
task :update_retweet_cache => :environment do
Tweet.delay.cache_retweets_for_all_auths
end
Tweet.rb, update_retweet_cache method:
def self.cache_retweets_for_all_auths
#authentications = Authentication.find_all_by_provider("twitter")
#authentications.each do |authentication|
authentication.user.twitter.retweeted_to_me(include_entities: true, count: 200).each do |tweet|
# Actually build the cache - this is good - removing to keep this short
end
end
end
User.rb, twitter method:
def twitter
authentication = Authentication.find_by_user_id_and_provider(self.id, "twitter")
if authentication
#twitter ||= Twitter::Client.new(:oauth_token => authentication.oauth_token, :oauth_token_secret => authentication.oauth_secret)
end
end
Note: As I was posting this, I noticed that I'm finding all "twitter" authentications in the "cache_retweets_for_all_auths" method, then calling the "User.twitter" method, which specifically limits to "twitter" authentications. This is obviously redundant, and I'll fix it.
First what is the exact error you are getting, and what do you want to happen when there is an error?
Edit:
If you just want to catch the errors and log them then the following should work.
def self.cache_retweets_for_all_auths
#authentications = Authentication.find_all_by_provider("twitter")
#authentications.each do |authentication|
being
authentication.user.twitter.retweeted_to_me(include_entities: true, count: 200).each do |tweet|
# Actually build the cache - this is good - removing to keep this short
end
rescue => e
#Either create an object where the error is log, or output it to what ever log you wish.
end
end
end
This way when it fails it will keep moving on to the next user but will still making a note of the error. Most of the time with twitter its just better to do something like this then try to do with each error on its own. I have seen so many weird things out of the twitter API, and random errors, that trying to track down every error almost always turns into a wild goose chase, though it is still good to keep track just in case.
Next for when you should use what.
You should use a scheduler when you need something to happen based on time only, delayed jobs for when its based on an user action, but the 'action' you are going to delay would take to long for a normal response. Sometimes you can just put the thing plainly in the controller also.
So in other words
The scheduler will be fine as long as the time between updates X is less then the time it will take for the update to happen, time Y.
If X < Y then you might want to look at calling the logic from the controller when each indvidual entry is accessed, isntead of trying to do them all at once. The idea being you would only update it after a certain time as passed so. You could store the last time update either on the model itself in a field like twitter_udpate_time or in a redis or memecache instance at a unquie key for the user/auth.
But if the individual update itself is still too long, then thats when you should do the above, but instead of doing the actually update, call a delayed job.
You could even set it up that it only updates or calls the delayed job after a certain number of views, to further limit stuff.
Possible Fancy Pants
Or if you want to get really fancy you could still do it as a cron job, but have a point system based on views that weights which entries should be updated. The idea being certain actions would add points to certain users, and if their points are over a certain amount you update them, and then remove their points. That way you could target the ones you think are the most important, or have the most traffic or show up in the most search results etc etc.
Next off a nick picky thing.
http://api.rubyonrails.org/classes/ActiveRecord/Batches.html
You should be using
#authentications.find_each do |authentication|
instead of
#authentications.each do |authentication|
find_each pulls in only 1000 entries at a time so if you end up with a lof of Authentications you don't end up pulling a crazy amount of entries into memory.

Delayed Jobs on Rails 2: can't save in "perform" method/how to know when job is done?

I'm using the Delayed Jobs plugin for Rails 2, and every time I try to modify a model and save it in the "perform" method required by Delayed Jobs, it fails out (no error messages or anything, it's just listed as a failure in the database).
I have the "perform" method in one of my rails model files (Video), and I'm passing an instance of that model (#video, let's say) to the Delayed::Job.enqueue
Is it a known issue that you can't do database modifications while in the queue? Am I doing something wrong (it only fails when it tries to save, not when I'm actually changing the attributes, and that sounds like a database modification issue).
If this IS expected: How can I fix it? I'm trying to save a "done" attribute to true, so I know when the model is ready to get to the next step. Is there some standard way to figure out when a delayed job is done?
EDIT: I have confirmed that calling perform standalone (without delayed job) has no problems with saving (no errors or warnings, or anything). When I call it through DelayedJobs it fails IMMEDIATELY (no time out) the second it gets to the save line.
EDIT: Wait, I think I see what is going on: my "perform" is part of an "after_create" call back... Which is all well and good, until I try to SAVE. It looks like when I save, it calls perform AGAIN (while already in perform), and that doesn't fly with Delayed Jobs (nor should it). For some reason I thought after_create would only get called once (not after every save). Wait, a simple test just showed that that IS the case. Hrrm... So why is perform called twice when I save, and once when I don't, in delayed jobs?
My code:
after_create :start_transcodes
def start_transcodes
Delayed::Job.enqueue self
end
def perform
puts "performing"
self.flash_status = 100
self.save!
puts "done"
end
What I see:
performing
performing
2 jobs processed at 3.3406 j/s, 2 failed ...
I don't see it say "done" ever.
What I DO see in my rails log is:
"* [JOB] Video failed with NameError: undefined local variable or method `flush_deletes' for #<Paperclip::Attachment:0xb6e51da0> - 2 failed attempts
undefined local variable or method `flush_deletes' for #<Paperclip::Attachment:0xb6e51da0>"
I am using the paperclip plugin for this class, and I can call save all day (even in that perform method) and get no problems. I ALSO can call save(again, even in perform) all day and not see my after_create method called more than once--UNLESS I"m using Delayed Job. (might be it doing some sort of auto retry?)
I'm gonna look around my paperclip plugin, see what's going on...
If your save fails, its got nothing to do with delayed_job ( atleast it shouldn't be unless the save takes longer than the MAX_RUN_TIME. )
Try diagnosing the problem with the save, by not using delayed_job.
Also take a look at the delayed_job.log file in your logs
Okay, not sure EXACTLY what was happening, but I've made a skeleton "TranscodeJob" class in my lib directory. This class gets initialized with a reference to what video I want it to process, and processes it, and saves it, and plays nicely with Delayed Job.
Basically, it looks like passing my entire complicated Video object (complete with paperclip plugins) to Delayed Job was freaking things out, and passing a simple object, with no more info than it needs makes things much easier.
Below is the code I used, which works just fine (and if that works fine, i can add my long running code back little by little and confirm it continues to do so, but it worked fine before, just hiccuped with saving)
class TranscodeJob
def initialize(video_id)
#video_id = video_id
end
#delayed jobs expected method
def perform
#video = Video.find(#video_id)
#video.flash_status = 100
#video.save!
end
end
This code is STILL called from a after_create filter, and I'm not seeing it called twice, so it looks like I mistoke DelayedJobs auto-retry for recursion, or something.

Resources