Rails - Delayed job completion - ruby-on-rails

I have a web application based on ruby on rails, which uses this delayed job gem.
I have a function that triggers a delayed job which in turn triggers several other delayed jobs. Is there a way, nest case being an event that can indicate that all the jobs related to a parent have completed? Or do I just have to work on whatever data is available when I try to retrieve docuemnts ?
Example:
def create_vegetable
#..... creates some vegetable
end
def create_vegetable_asynchronously id
Farm.delay(priority: 30, owner: User.where("authentication.auth_token" => token).first, class_name: "Farm", job_name:create "create_vegetable_asynchronously").create_vegetable(id)
end
def create_farm_asynchronously data
data.each do |vegetable|
create_vegetable_asynchronously vegetable.id
end
end
handle_asynchronously :create_farm_asynchoronously

maybe a little overkill, but you can explicitly organize your jobs hierarchically and employ before hook to pass on the parent job id to subtasks.
something like:
class Job < Delayed::Job
belongs_to :parent, class: 'Job' #also add db column for parent_id
end
class CreateVegetable
def initialize(id)
#id = id
end
def perform
Farm.create_vegetable(id)
end
end
class CreateFarm
def initialize(vegetable_ids,owner_id)
#vegetable_ids = vegetable_ids
#owner_id = owner_id
end
def before(job)
#job_id = job.id
end
def perform
#vegetable_ids.each { |id| Job.enqueue CreateVegetable.new(id), priority: 30, owner_id = #owner_id, :parent_id = #job_id }
end
end
Then when you launch create farm, somehow remember the job id.
def create_farm_asynchronously data
owner_id = User.where("authentications.auth_token" => token).first.id
#parent = Job.enqueue CreateFarm.new(data.map(&:id), owner_id)
end
then you can check sub-jobs by:
Job.where(parent_id: #parent.id)

Related

How to access Delayed Job instance inside Active Job - Rails 4.2

I'm using ActiveJob with delayed_job (4.0.6) in the background and I want to find a scheduled job to deleted it.
For instance, if I have
class MyClass
def my_method
perform_stuff
MyJob.set(wait: 1.month.from_now).perform_later(current_user)
end
end
Then, if I edit MyClass instance and call my_method again, I want to cancel that job and schedule a new one.
As suggested in this post http://www.sitepoint.com/delayed-jobs-best-practices, I added two columns to the Delayed Job Table:
table.integer :delayed_reference_id
table.string :delayed_reference_type
add_index :delayed_jobs, [:delayed_reference_id], :name => 'delayed_jobs_delayed_reference_id'
add_index :delayed_jobs, [:delayed_reference_type], :name => 'delayed_jobs_delayed_reference_type'
So this way I may find a delayed Job and destroy it. But I wanted to do that inside a ActiveJob class, to maintain the pattern of jobs in my project.
I wanted to do something like:
class MyJob < ActiveJob::Base
after_enqueue do |job|
user = self.arguments.first
job.delayed_reference_id = user.id,
job.delayed_reference_type = "User"
end
def perform(user)
delete_previous_job_if_exists(user_id)
end
def delete_previous_job_if_exists(user_id)
Delayed::Job.find_by(delayed_reference_id: 1, delayed_reference_type: 'User').delete
end
end
But that doesn't work.
Anyone had this kind of issue?
Two changes:
1. updated the after_enqueue callback so that you can update the
delayed_jobs table directly
2. fixed a typo where delayed_reference_id was hard coded as 1
This should work:
class MyJob < ActiveJob::Base
after_enqueue do |job|
user = self.arguments.first
delayed_job = Delayed::Job.find(job.provider_job_id)
delayed_job.update(delayed_reference_id:user.id,delayed_reference_type:'User')
end
def perform(user)
delete_previous_job_if_exists(user.id)
end
def delete_previous_job_if_exists(user_id)
Delayed::Job.find_by(delayed_reference_id: user_id, delayed_reference_type: 'User').delete
end
end
If you want to access the Delayed::Job from your worker, in an initializer, you can monkey patch the JobWrapper class.
module ActiveJob
module QueueAdapters
class DelayedJobAdapter
class JobWrapper
def perform(job)
end
end
end
end
end

Delayed Job - Find by object id

I have using Rails + DelayedJob Mongoid. I have this model:
class User
def run
...
end
end
I create new Dejayed::Job object:
user = Use.create
user.delay.run
How I can I do something like this?
Delajed::Job.where(object_id: user.id)
I am not sure if this will work in your case.
If you have the delayed_job object id, you can simply find it like normal activerecord objects.
# find a job by id
job = Delayed::Job.find(params[:id])
Now when you dont have the id. Then add something unique during the creation of the job itself
in users_controller.rb
user = User.create
in user.rb
def delayed_run
my_job = MyJob.new('xyz')
job = Delayed::Job.enqueue(run, 0, 1.hour.from_now)
job.user_id = self.id
end
So after this, you can find your delayed job object from the unique user_id
Delayed::Job.where(user_id: 5)
Based on #aelor answer, finally I had to add field object_id to Job class:
# config/initializers/dejayed_job.rb
module Delayed
module Backend
module Mongoid
class Job
include ::Mongoid::Document
field :object_id
end
end
end
end
Then it works ok:
user = Use.create
user.delay(object_id: user.id).run
Delayed::Job.where(object_id: 5).first
=> #<Delayed::Backend::Mongoid::Job _id: 551a5116726a750e08000000

Rails model specs passing, Sidekiq spec failing

I'm building a Rails (4.1.0) app that runs a poll. each poll has n Matchups with n Seats. Here are my models:
class Matchup < ActiveRecord::Base
has_many :seats, dependent: :destroy
def winning_seat
seats.sort { |a,b| a.number_of_votes <=> b.number_of_votes }.last
end
end
class Seat < ActiveRecord::Base
belongs_to :matchup
validates :matchup, presence: true
validates :number_of_votes, presence: true
def declare_as_winner
self.is_winner = true
self.save
end
end
My specs for Matchup and Seat pass without issue. At the end of a poll, I need to display the winner. I am using a Sidekiq worker to handle the end of the poll. It does many things, but here's the code in question:
class EndOfPollWorker
include Sidekiq::Worker
def perform(poll_id)
poll = Poll.where(:id poll_id)
poll.matchups.each do |matchup|
# grab the winning seat
winning_seat = matchup.winning_seat
# declare it as a winner
winning_seat.declare_as_winner
end
end
end
The spec for this worker doesn't pass:
require 'rails_helper'
describe 'EndOfPollWorker' do
before do
#this simple creates a matchup for each poll question and seat for every entry in the matchup
#poll = Poll.build_poll
end
context 'when the poll ends' do
before do
#winners = #poll.matchups.map { |matchup| matchup.seats.first }
#losers = #poll.matchups.map { |matchup| matchup.seats.last }
#winners.each do |seat|
seat.number_of_votes = 1
end
#poll.save!
#job = EndOfPollWorker.new
end
it 'it updates the winner of each matchup' do
#job.perform(#poll.id)
#winners.each do |seat|
expect(seat.is_winner?).to be(true)
end
end
it 'it does not update the loser of each matchup' do
#job.perform(#poll.id)
#losers.each do |seat|
expect(seat.is_winner?).to be(false)
end
end
end
end
end
end
When I run this spec, I get:
EndOfPollWorker when poll ends it updates the winner of each matchup
Failure/Error: expect(seat.is_winner?).to be(true)
expected true
got false
My specs for the Seat and Matchup models pass just fine. I cut a lot of the test code out, so excuse any mismatched tags, assume that's not the problem!
Also, when the workers actually run in development mode, the seats.is_winner attribute isn't actually updated.
Thanks
Sidekiq has nothing to do with your problem. You're directly calling perform so the issue is with rspec and activerecord. For instance, pull the code out of the perform method and put it directly in the spec, it should still fail.
I suspect the instances are stale and need to be #reload'd from the database to pick up the changes done in #perform.

How to write a method trailing with a keyword

I have a situation where i need to call something like this :
class Office
attr_accessor :workers, :id
def initialize
#workers = []
end
def workers<<(worker)
type = worker.type
resp = Organiation::Worker.post("/office/#{#id}/workers.json", :worker => {:type => type})
end
end
this is where i need to call
office = Office.new()
new_worker = Worker.new()
office.workers << new_worker
how should i modify the above workers method in order to implement above code.
New answer for this (based on updated question):
class WorkersClient
attr_accessor :office_id
def <<(worker)
type = worker.type
resp = Organiation::Worker.post("/office/#{#office_id}/workers.json", :worker => {:type => type})
end
end
class Office
attr_accessor :workers, :id
def initialize
#workers = WorkersClient.new
#workers.office_id = #id
end
end
I'm assuming that the Worker class is defined somewhere, something like:
def Worker
attr_accessor :type
...
end
The WorkersClient class is just a proxy to handle the collection (like ActiveRecord 3 does with associations). You can develop it further to store a local cache of workers, and so on.
I would recommend looking at how Rails' ActiveResource is implemented, as it does something VERY similar.
try this office.build_worker
If those objects are actually ActiveRecord objects (which it sort of sounds like), you're probably looking at
office.workers << new_worker
Note the plural form.
If those objects are your own creations, you probably want Office#workers to return an Array'ish object, so something like
class Office
def workers
#workers ||= []
end
end
Add sanity checks and whatnot as you see fit.
There's not much to add to what's already been said, but one thing to think about is hiding the implementation of workers. Sure, it starts out with an array, but that may change. By implementing your own << method you can hide implementation details from the user.
class Office
attr_accessor :workers
def initialize
#workers = []
end
def <<(other)
self.workers << other
end
end
I tend to use getter/setters inside my classes as that's something I learned from Smalltalk books, but of course you could just do #workers << other.

Ruby attr_accessor setter does not get called during update_attributes on child object

I'm trying to add functionality to a project done in ruby, I'm unfamiliar with Ruby, but have managed to create a project review page that allows updates on the project task codes for a given monthly review.
My issue is that the client (my brother) has asked me to allow him to edit the scheduled hours for the next few months on "this" month's project review.
I've been able to show those values that don't belong to the child on the page, and I can get the usual child elements to update, but I cannot get the update to happen on the value I'm borrowing from the future month(s).
To get the page to show and not fail on update, I've added that attr_accessor (otherwise I had failures on update because the value didn't exist in the model.
an excerpt from my code is shown below. There are no errors, but there are also no updates to the variable reflected in the attr_accessor, I have tried testing with changes to the usual elements in the child object, those will get updated, but still no call to the attr_accessor "setter".
suggestions?
Thanks much,
Camille..
class Projectreview < ActiveRecord::Base
has_many :reviewcostelements
accepts_nested_attributes_for :reviewcostelements
end
class ProjectreviewsController < ApplicationController
def update
#projectreview = Projectreview.find(params[:id])
respond_to do |format|
if #projectreview.update_attributes(params[:projectreview])
format.html { redirect_to(#projectreview) }
end
end
end
end
class Reviewcostelement < ActiveRecord::Base
belongs_to :projectreview
attr_accessor :monthahead_hours1
def monthahead_hours1(newvalue) #this is the setter
#why do I never see this log message ??
logger.info('SETTER 1')
set_monthahead_hours(1, newvalue)
end
def monthahead_hours1 #this is the getter
get_monthahead_hours(1)
end
def update_attributes(attributes)
#never gets called!!!
logger.info('update_attributes values rce')
super(attributes)
end
def get_monthahead_hours(p_monthsahead)
#this works and returns the next month's scheduled_hours_this_month value
rce = Reviewcostelement.first(:joins => :projectreview,
:conditions => ["projectreviews.project_id = ?
and reviewcostelements.projecttaskcode_id =?
and projectreviews.month_start_at = ?", projectreview.project_id ,
projecttaskcode_id ,
projectreview.month_start_at.months_since(p_monthsahead)])
if rce
return rce.scheduled_hours_this_month
else
return 0
end
end
def set_monthahead_hours(p_monthsahead, newvalue)
#this never seems to get called
logger.info("set the month ahead hours")
rce = Reviewcostelement.first(:joins => :projectreview,
:conditions => ["projectreviews.project_id = ?
and reviewcostelements.projecttaskcode_id =?
and projectreviews.month_start_at = ?",
projectreview.project_id ,
projecttaskcode_id ,
projectreview.month_start_at.months_since(p_monthsahead)])
if rce
rce.scheduled_hours_this_month = newvalue
rce.save
end
end
end
The setter of the accessor looks like this:
def monthahead_hours1=(newvalue)
...
end
Note the equals (=) symbol.

Resources