DelayedJob Job instance treats all passed in objects as nil - ruby-on-rails

I have been wrestling with DelayedJob for the last day and a half. I'm trying to create a simple Job class that accepts a large string and an ActiveRecord object. But no matter what I pass in to the Job when enqueing it, it is treated as nil. I've tried many different strategies to make this work; I have tried passing in just the id of the ActiveRecord object (treated as nil), I've tried adding an initializer to the Job object (rather than having it inherit from an instance of Struct)...nothing works.
I've simplified my job class into something ridiculous, and it still doesn't work:
class SimpleJob < Struct.new(:owner_id)
def perform
#owner = Owner.find(owner_id)
puts #owner.full_name
end
end
And in my controller:
def test_job
Delayed::Job.enqueue(SimpleJob.new(#owner.id))
redirect_to :action => 'index', :controller => 'owner'
end
The error is, of course, that Owner can't be found with an id of nil (before you might ask, yes, #owner is instantiated and working; a before_filter ensures this).
I'm using Rails 2.3.5, DelayedJob version 2.0.7. My Job object is located in the libs folder, if that makes a difference.
Is there some part of the configuration I'm missing?

Your call to delayed job is setup correctly. The first thing to check is that #owner.id is not nil to start with as its more then likely that its the issue

Have a look to the DB, in the table delayed_jobs, and check if the object are correctly serialized.
bye

Related

Rails methods not initialized in time for worker

Earlier, I had posted this question – and thought it was resolved:
Rails background worker always fails first time, works second
However, after continuing with tests and development, the error is back again, but in a slightly different way.
I'm using Sidekiq (with Rails 3.2.8, Ruby 1.9.3) to run background processes, after_save. Below is the code for my model, worker, and controller.
Model:
class Post < ActiveRecord::Base
attr_accessible :description,
:name,
:key
after_save :process
def process
ProcessWorker.perform_async(id, key) if key.present?
true
end
def secure_url
key.match(/(.*\/)+(.*$)/)[1]
end
def nonsecure_url
key.gsub('https', 'http')
end
end
Worker:
class ProcessWorker
include Sidekiq::Worker
def perform(id, key)
post = Post.find(id)
puts post.nonsecure_url
end
end
(Updated) Controller:
def create
#user = current_user
#post = #user.posts.create(params[:post])
render nothing: true
end
Whenever jobs are first dispatched, no matter the method, they fail initially:
undefined method `gsub' for nil:NilClass
Then, they always succeed on the first retry.
I've come across the following github issue, that appears to be resolved – relating to this same issue:
https://github.com/mperham/sidekiq/issues/331
Here, people are saying that if they create initializers to initialize the ActiveRecord methods on the model, that it resolves their issue.
To accomplish this, I've tried creating an initializer in lib/initializers called sidekiq.rb, with the following, simply to initialize the methods on the Post model:
Post.first
Now, the first job created completes successfully the first time. This is good. However, a second job created fails the first time – and completes upon retry... putting me right back to where I started.
This is really blowing my mind – has anyone had the same issue? Any help is appreciated.
Change your model callback from after_save to after_commit for the create action. Sometimes, sidekiq can initialize your worker before the model actually finishes saving to the database.
after_commit :process, :on => :create

Controller Action to Delayed Job

I am uploading a tab-delimited document and processing in the controller. Everything works fine, but can take some time on a large file. I want to move this to a delay_job, which I have working elsewhere in my app, but as this is in the controller, cannot be called in the same way.
The form calls on the process_file action, which in turn calls on the salesupload action. How should I turn this into a background job?
class SalesController < ApplicationController
def salesupload(file)
uploaded_io = file.read
numrows = "uploaded_io.size"
FasterCSV.parse(uploaded_io, {:headers => true, :col_sep =>"\t"}).each do |row_data|
full, upc, _discard, isrc = row_data[26].match(/^([^_]+)(_(.+))?/).to_a
new_record = AppleSale.new(
'provider' => row_data[0],
'provider_country' => row_data[1],
'vendor_identifier' => row_data[2]
)
new_record.save
end
end
def process_file
file = params[:apple_sale][:tsv_file]
salesupload(file)
end
end
I found when I had to do this that the method defined in the controller has to be a class method. I can't remember why this was, I think it had to do with having a more explicit receiver. So what I would do is make the salesupload method a class method, and then just call .delay on it.
def self.salesupload(files)
# code
end
def process_file
file = params[:apple_sale][:tsv_file]
SalesController.delay.salesupload(file)
head :no_content
end
And you should be good to go! I also made my original method (process_file in this case) called via AJAX, and then I appended the head :no_content so that it returned something without needing a redirect or anything.
I wrote a gem called delayed_action to do this.
In this case you'd write
delayed_action [:sales_upload]
and that's it.
I guess you should move this code to a model or a separated class (for instance inside the lib folder). But that is just a matter of organization and best practices.
About the running this code in background, you have many options.
If you want to use delayed_job I guess you should watch this screencast:
http://railscasts.com/episodes/171-delayed-job
but basically, after setting up delayed job, you just use something like send_later(:process_file) to tell to run that project in background.
It is pretty straightforward, your command and data structure will be saved in a database table and later a separated process can execute it. I imagine that after watching the railscast you will have your answers ;)

Delayed_job : defining time

So I'm trying to figure out how to do this cleanly. Trying to keep the example simple lets say I have an object, MyMailMeeting in which I define two times that I want to send two different emails to people in a meeting.
In my MyMailMeeting model, I want to define both methods (rather than having their own delayed job class), but I need to have those methods see the times defined within the object in order to know when to send.
def send_first
... do stuff
end
handle_asynchronously :send_first, :run_at => Proc.new { send_first_time }
Problem is that according to the documentation, send_first_time needs to be a class method, and want it to be an instance method so that I can see the times defined by the user.
How do I do this? Or do I just need to create two separate Delayed_job classes and do something like this:
Delayed::Job.enqueue(SendFirstJob.new, :run_at => send_first_time)
I believe that handle_asynchronously passes the object into Procs for its attributs so:
handle_asynchronously :send_first, :run_at => Proc.new { |obj| obj.send_first_time }
You can always roll your own async wrapper
def send_first_async(*args)
delay(:run_at => send_first_time).send_first(*args)
end
I ended up using the second method. Though it isn't as time sensitive as I would like.
If anyone has an answer on how to get send_first_time to be a variable that is based on user input, I'll gladly accept it as the right answer. Thanks.

Why are my thread variables intermittent in Rails?

I have the following in my application controller:
before_filter :set_current_subdomain
protected
def set_current_subdomain
Thread.current[:current_subdomain] = current_subdomain
#account = Account.find_by_subdomain(current_subdomain)
end
def current_subdomain
request.subdomain
end
and then the following in some of my models:
default_scope :conditions => { :account_id => (Thread.current[:account].id unless Thread.current[:account].nil?) }
Now, this works - some of the time. I for instance load up an index method and get back a list of records with the scope applied, but also sometimes get an empty list as Thread.current[:account_id] is coming out as nil, even though queries earlier in the request are working using the same value.
Question is, why is this not working, and is there a better way to set a variable that's global to the current request?
Manipulating the Thread local variables is a really bad idea and is going to lead to nothing but sadness, heartache, and pain. There's no guarantee that different parts of the request processing will be handled by the same thread, and because of this, your variables might end up getting lost.
The Rails convention is to create instance variables in the context of ApplicationController. In simple terms, all you really do is this:
class ApplicationController < ActionController::Base
before_filter :set_current_subdomain
attr_reader :current_subdomain
helper_method :current_subdomain
protected
def set_current_subdomain
#current_subdomain = request.subdomain
#account = Account.find_by_subdomain(#current_subdomain)
end
end
Any #... type variables you create will be attached to the instance of the ApplicationController associated with the current request. It's important to note that each request will be issued a brand-new instance of the appropriate controller class.
You're free to create whatever instance variables you want provided they don't somehow conflict with those used by Rails itself but in general terms this doesn't happen very often and conflicts typically occur on method names instead.
Class-level instance variables will persist between requests in environments where the "cache classes" flag is enabled. In the development environment your controller class is re-loaded each time a request is made to ensure it reflects the current state of your source files.

Rails: Why does the model method not work?

its the first time I post here. I have a problem that i can somehow not solve. Just for the record, I know what instance and class methods are (even if I may not understand them completely ;-)
Here is my model code:
class Car < ActiveRecord::Base
has_many :drives
has_many :users, :through => :drives
def self.user_ids()
ids = []
self.users.each do |user|
ids += user.id
end
ids
end
def self.common_times()
start_times = []
stop_times = []
self.drives.each do |drive|
drive.start_date_time += start_times
drive.stop_date_time += stop_times
end
times = { :start => start_times.sort.last, :stop => stop_times.sort.first}
end
what I want is an array of all users using the car (which I use to check if a given user is already connected to the car for permissions etc.. Is there a better way to check if two datasets are already connected without doing SQL queries all the time?) and which start and stop times they prefer. I need than a hash with the latest starting time and the earliest stop time.
Somehow the user_ids method works (even if I think it should be an instance method) and the common_times is always missing. if I define them both as an instance method I have problems with fixnum and array stuff (like "+").
user_id:
"TypeError: can't convert Fixnum into Array"
common_times:
"NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.+"
I guess the best way is to make them instance methods. But then I need to refer differently to the other models as users and drives.
Why does user_ids work as an instance method even if declared as a class method?
How do I call already loaded models [c=Car.find(:all, :include => :drives)] inside an instance method?
Funny thing was also, that as long as they were class methods I could delete them and restart mongrel and they would still work (user_ids) and not work (common_times).
I am pretty confused right now and hop you can help me. And sorry for my bad english (I am german :-)
Because of your users association, Rails already pre-builds the user_ids and users instance methods. Using #car.users should be your best bet.
As for instance and class methods: instance methods are for specific objects, whereas class methods are just put under the class name for your convenience. #car.id is an instance method, since it returns the ID of a single car. Cars.find is a class method, since it is independent of any single object and instead is grouped under the Cars class for organizational purposes. (It could just as easily be its own global find_cars method and work just as well, though it would be horrible design.)
So both of your methods should be instance methods, and the first one Rails creates for you because it loves you so much.
As for your individual errors, adding objects to an array is done with the << operator, not the plus sign. Ruby thinks you are trying to add two arrays, so is confused why you are trying to use a Fixnum in the same way you would typically use an array. Try making those fixes and see if you still get errors.
Got it to work (thnx #Matchu). Here is the final code for the method (no self-made user_ids anymore ;-)
def common_times()
start_times = []
stop_times = []
drives.each do |drive|
start_times << drive.start_date_time
stop_times << drive.stop_date_time
end
times = { :start => start_times.sort.last, :stop => stop_times.sort.first}
end
the biggest error was the switched start_times << drive.start_date_time
Really silly error..
thanks again!

Resources