i am new in ROR and i am using 'Sidekiq' for scheduling Reminders. i can schedule reminder jobs/messages successfully through "Sidekiq". Here is code of 'reminder.rb'
class Reminder < ActiveRecord::Base
belongs_to :user
belongs_to :status_value
belongs_to :communication
after_create :add_sidekiq_job
# before_destroy :remove_sidekiq_job
def add_sidekiq_job
id = ReminderWorker.perform_at(scheduled_at, {id: self.id.to_s})
# self.update_attributes(job_id: id)
end
def remove_sidekiq_job
queue = Sidekiq::ScheduledSet.new
job = queue.find_job(self.job_id)
job.delete if job
end
def scheduled_at
self.notification_time.to_time.to_i - Time.now.to_i
end
end
and here is code of 'reminderworker.rb' where i can get 'reminder/notification message' by passing the id.
class ReminderWorker
include Sidekiq::Worker
def perform(args)
reminder = Reminder.find(args['id'])
puts "~~~~~Notificaiton~~~~~~~~"
end
end
Everything is working till now but now i want to show notification message (which i can get from 'reminder = Reminder.find(args['id'])') in my notification page . how i can do this?
If you want to show this message on user's page you should use some kind of persisted pub/sub client-server connection, that will be available in sidekiq. In Rails 5 it could be ActionCable, in previous versions of Rails - Faye or something like this.
Related
I'm new to Delayed Jobs and am not sure if I'm doing this correctly.
Worker
class BillingFailedEmailWorker < BaseWorker
attr_accessor :car
def initialize(car)
#car = car
end
def billing_failed_for_subscriptions
StripeMailer::billing_failed_for_subscriptions(car.owner.email).deliver
end
def perform
self.class.new(car).delay(
run_at: DateTime.now + 1.week,
queue: "Failed Subscription"
).billing_failed_for_subscriptions
end
end
Mailer
class StripeMailer < ActionMailer::Base
default from: Settings.default_from_email
def billing_failed_for_subscriptions(email)
mail to: email, subject: 'Autobrain Subscription Renewal Failed'
end
end
billing_failed_for_subscriptions also obviously corresponds to the correct mailer view.
My question is when I run BillingFailedEmailWorker.new(Car.last).perform I see the command execute correctly in Delayed::Backend::ActiveRecord::Job but when I run Delayed::Backend::ActiveRecord::Job.last or Delayed::Job.last I don't see the job I just created.
Could this be a matter of my Rails environment being in development or is there something I'm missing?
Instead of using
StripeMailer::billing_failed_for_subscriptions(car.owner.email).deliver
use
StripeMailer::billing_failed_for_subscriptions(car.owner.email).deliver_later
Then in the console you can check it as Delayed::Job.last
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
I need to call tweeter api in a day with fixed time like 1PM, 3PM, 9AM..
I am using twiter gem and rufus-scheduler
Here is my model
class Feed < ActiveRecord::Base
def self.latest
order('created_at desc').take(500)
end
def self.pull_tweets
$client.search("#ukraine", since_id: maximum(:tweet_id), count: 10).take(10).each do |tweet|
create!(
tweet_id: tweet.id,
content: tweet.text,
screen_name: tweet.user.screen_name,
)
end
end
end
Here is the controller where i am using the scheduler to call pull_tweets
class FeedsController < ApplicationController
def index
scheduler = Rufus::Scheduler.new
scheduler.schedule_every('60s') do
Feed.pull_tweets
end
#tweets = Feed.latest
end
end
Its working perfectly but how i can set multiple times like 1AM, 3PM, 9AM for scheduling the task from users input?
Simplify your controller to:
class FeedsController < ApplicationController
def index
#tweets = Feed.latest
end
end
Then make sure you have a config/initializers/scheduler.rb that looks like:
require 'rufus-scheduler'
Rufus::Scheduler.singleton.every('60s') { Feed.pull_tweets }
So that you have a single scheduler for the whole Ruby process.
To answer your question:
require 'rufus-scheduler'
Rufus::Scheduler.singleton.cron('0 1,9,15 * * *') { Feed.pull_tweets }
# pull tweets at 0100, 0900 and 1500
Your code is creating a rufus-scheduler instance each time a browser GETs /feeds, the code here has a single scheduler.
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.
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)