Sidekiq not finding records for Rails Active Job - ruby-on-rails

Jobs are queued on after a user is created like so in the model
user.rb
after_create_commit :profile_photo_job
def profile_photo_job
message = "Add a profile photo"
ReminderJob.set(wait: 1800).perform_later(self.id.to_s, message)
end
reminder_job.rb
class ReminderJob < ApplicationJob
queue_as :default
def perform(user_id, message)
if user_id
user = User.find(user_id)
end
##sending message notification here
end
end
However, it often throws the following error inside my sidekiq console
ActiveRecord::RecordNotFound: Couldn't find User with 'id'=7749
Processor: User-MacBook-Pro.local:*****
This error happens in production.

I ran into this issue with workers returning the error:
ActiveRecord::RecordNotFound: Couldn't find User with 'id'
I tried a bunch of things and realised I had 2 separate Rails apps (with different databases) reading from the same queue. Silly error, but once I changed the queue name for one of the apps, I never saw this again.
Leaving this here if someone hasn't checked this 🤦‍♂️

In User.rb
after_commit :profile_photo_job, on: :create
def profile_photo_job
message = "Add a profile photo"
ReminderJob.set(wait: 1800).perform_later(self.id.to_s, message)
end

Related

Best practices for sending many emails at the same time or in a loop in Rails 6?

I have a rake task that loops through bookings and sends an email for each one using .deliver method (which I got from here (which I'm conscious is now 7 years old).
The problem is, sometimes some of the emails don't get sent. Here is my code
# Select bookings starting soon
bookings = Booking.where('start_time < ?', 24.hours.since)
# Email a reminder
bookings.each do |booking|
customer = booking.customer
CustomerMailer.reminder_24h(customer, booking).deliver
end
Since the loop is in a rake task, I don't think there's any value in calling .deliver_later, so I just use .deliver like in the rails cast
I am curious to know if there are best practices that can help using Action Mailer, for example should there be a sleep 2 between each email? Or should I always use .deliver_later to relieve the load on the server? Are there any other rails-related reasons that my code may not work (or, worse, I am using any anti patterns that I should refactor?)
TL;DR why would emails sent in a loop like in the code above occasionally fail to send
No an answer, but some advice from another forum.
Sending emails is a process that is filled with potential failures. It is always a good idea to do it in a background job that can be re-tried in case of intermittent errors like networks etc. and also skipped due to faulty addresses.
Here is a sketch of what may work:
# Reminder process rake task
namespace :bookings do
desc "Deliver reminders to upcoming bookings"
task remind_upcoming: :environment do
EnqueueUpcomingBookingReminders.call(UpcomingBookingRemindersQuery.call)
end
end
class EnqueueUpcomingBookingReminders
def self.call(bookings_scope)
booking_communication_attrs =
bookings_scope
.pluck(:id)
.map { |id| {booking_id: id, type: "reminder"} }
communications_result =
BookingCommunication.insert_all(booking_communication_attrs, unique_by: %i[booking_id type])
# Email a reminder
communications_result.rows.flatten.each do |communication_id|
DeliverBookingCommunicationJob.perform_later(communication_id)
end
end
end
class UpcomingBookingRemindersQuery
def self.call(scope: Booking)
Booking
.upcoming_this_day
.left_outer_joins(:communications)
.merge(BookingCommunication.reminder)
.where(communications: {id: nil})
end
end
class Booking
has_many :communications, class_name: "BookingCommunication"
def self.upcoming_this_day
where(starts_at:, (Time.current..24.hours.from_now))
end
end
class BookingCommunication
belongs_to :booking
enum step: {confirmation: "confirmation", reminder: "reminder"} # combination of this and the booking id should be unique
enum status: {pending: "pending", delivered: "delivered", canceled: "canceled", failed: "failed"} # should default to pending at database layer
end
class DeliverBookingCommunicationJob < ApplicationJob
def perform(communication_id)
communication = BookingCommunication.find_by(communication_id)
# Guard against state that invalidates us running this job
return unless communication
return unless communication.pending?
return communication.canceled! if communication.booking.canceled? # This should probably live in the cancel booking process
booking = communication.booking
mailer = CustomerMailer.with(customer: booking.customer, booking: booking)
case communication.step
when "reminder"
mailer.reminder_24h.deliver_now
else
# log unknown communication step, send to error tracking but dont raise since we do not want job to run again
end
communication.delivered!
rescue SomeEmailRelatedError => err
communication.failed!
# deliver err to error tracking service
end
end

active job not saving data in the database rails

i want to save this message five time from active jobs not from controller.
is there anyway to that ?
here message.save just returning true and its not saving the message in databas.
class MessageBroadcastJob < ApplicationJob
queue_as :default
def perform(message)
for i in 0..5
message.save!
ActionCable.server.broadcast 'chat', {message: render_message(message)}
end
end
private
def render_message(message)
MessagesController.render(
partial: 'message',
locals: {
message: message
}
)
end
end
this code is from model.
class Message < ApplicationRecord
belongs_to :user
after_create_commit {
MessageBroadcastJob.perform_later(self)
}
end
You are not calling your job in code anywhere. You are saving that after_create_commit mean after saving message to active record you want to run this job. But you are not doing it.
So you have to save at least one time, to run this job. That mean in your controller or anywhere in code at least save once message, this job will run when you have saved message onces, than it will save message. If you still have issue please check job is running properly? do you have any configuration in redis
I smell from your codes you are using cable, that mean you have to put following in your conversation channel file.
Message.create(message_params)
As soon as above code run than your job will also run. If above all is good than you have also issue in your job, you are saying that same message should be saved, so it won't save 5 message, you have to take parameters in it do following
m = Message.new
m.body = message
m.user_id = message.user_id
# setup other parameters required
m.save
ActionCable.server.broadcast 'chat', {message: render_message(message)}

Model fails in validation but is created

I'm facing an issue for a long time now. The code I have is the following:
class BrokenModel < ActiveRecord::Base
validates_with BrokenValidator
has_many :association_name
end
class BrokenValidator < ActiveModel::Validator
def validate record
#record = record
check_alerted
end
private
def check_alerted
return if #record.association_name.to_a.empty?
alerted = <test for alerted>
if alerted
#record.errors[:base] << "It was alerted recently"
end
p "check_alerted: #{#record.errors[:base]}"
end
end
worker.rb
[...]
BrokenModel.create(association_name: [model1, model2])
[...]
In my logs for the last print is shows that the validation passed only once, but I have actually multiple entries created for this model with association_name present.
My environment is running this in multiple threads and multiple cores, but as the entries are created minutes away from each other, it is not a concurrency issue, unless an exception in a separated thread is affecting the model creation.
Just for curiosity sake, this is running in a Sidekiq worker.
Edit
So I noticed in my logs, that it might be a concurrency issue. So here is what is happening:
instance 1 validation: alerted recently: failed (It was alerted recently)
instance 2 validation: alerted recently: passed
instance 2 validation: other validation: failed (Other validation)
instance 2 creation errors: It was alerted recently + Other validation
instance 1 creation errors: None
Any clue if there is any kind of thread unsafety in ActiveModel::Validator or the #record might be overwritten/shared by other threads?
Adding errors to a record does NOT make it invalid. In fact when the model is validated before save, all previous errors including the one you're adding in your code are erased.
Do this validation in the model... Not in the worker.
validate :check_alerted
def check_alerted
return if association_name.to_a.empty?
alerted = test
if <test for alerted>
errors.add(:base, "It was alerted recently")
end
end

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

Strange behavior with a resque scheduler job

so some context, I got some advice here:
Scheduling events in Ruby on Rails
aand have been tying to implement it today. I cant seem to make it work though. this is my scheduler job that is used to move my questions around between a delayed queue and a ready to send out queue (i've since decided to use email instead of SMS)
require 'Assignment'
require 'QuestionMailer'
module SchedulerJob
#delayed_queue = :delayed_queue
#ready_queue
def self.perform()
#delayed_queue.each do |a|
if(Time.now >= a.question.schedule)
#ready_queue << a
#delayed_queue.delete(a)
end
end
push_questions
end
def self.gather()
assignments = Assignment.find :all
assignments.each do |a|
#delayed_queue << a unless #delayed_queue.include? a
end
end
private
def self.push_questions
#ready_queue.each do |a|
QuestionMailer.question(a)
end
end
end
I use a callback on_create to call the gather method every time an assignment is created, and then the perform action actually does the sending of emails when resque runs.
I'm getting a strange error from the callback though.
undefined method `include?' for :delayed_queue:Symbol
here is the code from the assignment model
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :question
attr_accessible :title, :body, :user_id, :question_id , :response , :correct
after_create :queue_assignments
def grade
self.correct = (response == self.question.solution) unless response == nil
end
def queue_assignments
SchedulerJob.gather
end
Any ideas what's going on? I think this is a problem with my understanding of how these queue's work with resque-scheduler. I assumed that if the queues were list-like objects then I could operate on them , but it appears that it a symbol instead of something with methode like include? I assume the << notation for adding something to it is also invalid.
Also please advise if this isn't the way to go about handling this kind of job scheduling
It appears you may have not restarted your Rails app after adding the new method gather to the SchedulerJob module. Try restarting your app to resolve this.
You may also be able to add the directory containing your Resque worker to Rails' watchable_dirs array so that changes you make to Resque worker modules in development don't require restarting your app. See this blog post for details:
http://wondible.com/2012/01/13/rails-3-2-autoloading-in-theory/

Resources