How can I destroy an entity after some action is performed in Sidekiq's worker?
I have following code which doesn't work (mail is not deleted):
class MailingWorker
include Sidekiq::Workerdef
def perform(mail_id)
mail = Mail.find mail_id
#some logic for sending email
mail.destroy
end
end
Related
I am new to Ruby on Rails and Sidekiq. I want to set this delete request to be done in Sidekiq queue and I don't know how to send it to the perform method, I am sending the Book model to the perform method
My controller Action code
def destroy
BaseWorkerJob.perform_async(Book)
end
My BaseWorkerJob class code
class BaseWorkerJob
include Sidekiq::Job
sidekiq_options retry:0
def perform(book)
# Do something
book.find(params[:id]).destroy!
sleep 15
end
end
SideKiq Error
enter image description here
ruby 3.1.2
Rails 7.0.4
You can send the model name and object id to the worker
def destroy
BaseWorkerJob.perform_async(Book.to_s, params[:id])
end
class BaseWorkerJob
include Sidekiq::Job
sidekiq_options retry: 0
def perform(klass_name, object_id)
klass_name.constantize.find(object_id).destroy!
end
end
Try it out!
My question is probably not very clear and probably best illustrated with an example... I have an account model which has many users (which belongs to account). I also have a a method in a mailer that I only want to run if the account that the user is related to has a state of "active". Basically, the user should only be sent the email if there account is active. The method in the mailer file looks like this at the moment.
def pending_mail(document, user)
#user = user
mail(to: user.email, subject: t('emails.pending.subject') ... do |format|
format.text
format.html
end
end
The mailers only job is to render and send emails - not to handle business logic. Its not the mailers job to decide who gets an email or not.
This should be handled in the controller. For example:
class AccountsController < ApplicationController
def update
if #account.update(account_attributes)
Accounts::UsersNotificationJob.perform_later if #account.active?
redirect_to #account
else
render :new
end
end
end
module Accounts
class UsersNotificationJob
def perform(account)
account.users.each do |user|
UserMailer.pending_mail(user).deliver
end
end
end
end
You should have access the owning account through user.account, depending on how your model is defined. In which case you can just use a simple if statement.
if #user.account.state == "active"
mail blabla do |format|
.....
end
end
I've written a simple service to create the data I want to send over my ActionCable connection, but judging from the development log it looks like it is never executed and I can't figure out why.
#app\channels\quiz_data_channel.rb:
class QuizDataChannel < ApplicationCable::Channel
def subscribed
stream_from specific_channel
end
def send_data
logger.debug "[AC] send data method entered" #<-- this part is executed
QuizDataCreation.new(user: current_user).create #<-- calling the service HERE
logger.debug "[AC] service created new quiz data" #<-- not executed
#after I fix this issue I would broadcast the data here
end
private
def specific_channel
"quiz_data_#{params[:access_key]}"
end
end
#app\services\quiz_data_creation.rb:
module Services
class QuizDataCreation
def initialize(user)
self.user = user
logger.debug "[AC] service - initialize user: #{self.user.inspect}" #<-- not executed
end
def create
logger.debug "[AC] Service entered!" #<-- not executed
end
end
end
Calling the send_data method works, and the first text ("[AC] send data method entered") is printed into the development log, but that's it. I've been looking at several tutorials and tried placing the service in a subfolder of models at first, and calling Services::QuizDataCreation.new(user: current_user).create , but haven't gotten it to work yet. I'm sure there is a pretty obvious solution, but I just can't see it so I would be really thankful for any pointers.
Try defining the service class without module names Services as below:
#app\services\quiz_data_creation.rb:
class QuizDataCreation
def initialize(user)
self.user = user
logger.debug "[AC] service - initialize user: #{self.user.inspect}"
end
def create
logger.debug "[AC] Service entered!"
end
end
And then, you can access it normally as:
QuizDataCreation.new(current_user).create
There's a controller action in my Rails app that contacts a user via text-message and email. For reasons I won't go into, the text-message needs to complete before the email can be sent successfully. I originally had something like this:
controller:
class MyController < ApplicationController
def contact_user
ContactUserWorker.perform_async(#user.id)
end
end
workers:
class ContactUserWorker
include Sidekiq::Worker
def perform(user_id)
SendUserTextWorker.perform_async(user_id)
SendUserEmailWorker.perform_async(user_id)
end
end
class SendUserTextWorker
include Sidekiq::Worker
def perform(user_id)
user = User.find(user_id)
user.send_text
end
end
class SendUserEmailWorker
include Sidekiq::Worker
def perform(user_id)
user = User.find(user_id)
user.send_email
end
end
This was unreliable; sometimes the email would fail, sometimes both would fail. I'm trying to determine whether perform_async was the cause of the problem. Was the async part allowing the email to fire off before the text had completed? I'm a little fuzzy on how exactly perform_async works, but that sounded like a reasonable guess.
At first, I refactored ContactUserWorker to:
class ContactUserWorker
include Sidekiq::Worker
def perform(user_id)
user = User.find(user_id)
User.send_text
SendUserEmailWorker.perform_async(user_id)
end
end
Eventually though, I just moved the call to send_text out of the workers altogether and into the controller:
class MyController < ApplicationController
def contact_user
#user.send_text
SendUserEmailWorker.perform_async(#user.id)
end
end
This is a simplified version of the real code, but that's the gist of it. It seems to be working fine now, though I still wonder whether the problem was Sidekiq-related or if something else was going on.
I'm curious whether my original structure would've worked if I'd used perform instead of perform_async for all the calls except the email call. Like this:
class MyController < ApplicationController
def contact_user
ContactUserWorker.perform(#user.id)
end
end
class ContactUserWorker
include Sidekiq::Worker
def perform(user_id)
SendUserTextWorker.perform(user_id)
SendUserEmailWorker.perform_async(user_id)
end
end
If the email can only be sent after the text message has been sent, then send the email after successful completion of sending the text.
class ContactUserWorker
include Sidekiq::Worker
def perform(user_id)
SendUserTextWorker.perform_async(user_id)
end
end
class SendUserTextWorker
include Sidekiq::Worker
def perform(user_id)
user = User.find(user_id)
text_sent = user.send_text
SendUserEmailWorker.perform_async(user_id) if text_sent
end
end
class SendUserEmailWorker
include Sidekiq::Worker
def perform(user_id)
user = User.find(user_id)
user.send_email
end
end
In user.send_text you need to handle the fact that neither the text or the email has been sent.
I'm curious whether my original structure would've worked if I'd used perform instead of perform_async for all the calls except the email call
It would have. But this is not what you actually intdending. What you really want is this:
class MyController < ApplicationController
def contact_user
ContactUserWorker.perform_async(#user.id)
end
end
class ContactUserWorker
include Sidekiq::Worker
attr_reader :user_id
def perform(user_id)
#user_id = user_id
user.send_text
user.send_email
end
def user
#user ||= User.find user_id
end
end
The problem was indeed the perform async part. It schedules both tasks to be executed in the background by a separate sidekiq daemon process. i guess your sidekiq is configured to execute the jobs concurrently. In the first version you've first scheduled the ContactUserWorker to perform it's job in a background outside of the current rails request. As this worker is startet later on, it kicks off two separate delayed workers in turn, which are then run in parallel and so there is no way to determine which of the both executes/finishes first.
I don't know what you mean exatly by sending text, but sending an email is an io blocking process and therefore it was a good idea to perform this in a background, because it would be blocking a complete rails process otherwise until the email is delivered (on a typical unicorn/passenger multi-process deployment). And as you actually want to execute both tasks sequentially and as an atomic operation, it's totally fine, performing them by a single sidekiq job/worker.
You also don't have to check if send_text succeeds. Sidekiq will retry the complete job if any part of it fails with an exception
I am building an rails based e-commerce application first time. I want to send an email to user when he submits the order, like the way we get receipt on email when we place order on e.g. mealnut.com or fab.com. I was searching for tutorials but not getting related to order submit emails. Every where user sign up or reset etc.
Has any one implemented it? or know any resource/tutorial in rails?
Your guidance/help will be appreciated!
First generate the mailer for writing required actions.
rails g mailer UserMailer
then in app/mailers/user_mailer.rb file
class UserMailer < ActionMailer::Base
default from: 'notifications#example.com'
def order_confirmation(user, order)
#user = user
#order = order
mail(to: user.email, subject: 'Order has been received')
end
end
and the view content for the email would be like this app/views/user_mailer/order_confirmation.html.erb
Hi <%= #user.name %>
You have successfully placed an order with us.
Please find the details of order....
#.............
then in the controller action, where you will create a order, place the below line after creating the order to send an email
UserMailer.order_confirmation(user, order).deliver
Go through the action mailer tutorial for more information.
I'm afraid sending an email is a fairly standard procedure.. and the tutorials you've found are probably applicable. You need to understand that triggering a message to be sent can be done from any controller action.. in your case you'll want the order create action.
After reading the following:
http://railscasts.com/episodes/61-sending-email-revised?view=asciicast
You can make the necessary changes and call the mailer from your order create action:
class OrdersController < ApplicationController
def create
#order = Order.new(params[:order])
if #order.save
UserMailer.order_confirmation(#order, #user).deliver
redirect_to #user, notice: "Order Completed Successfully."
else
render :new
end
end
end
The reason I am using UserMailer in the above is because you will likely want to set up a mailer that sends messages to Users, but you could call it OrderMailer if you wanted.
You cannot/not suitable use Devise mailer (since devise mailer is for User authentication purposes). What you could do is, you could use a observer class to send e-mails
Ex:
class Order < ActiveRecord::Base
#your standard order code
end
class OrderObserver < ActiveRecord::Observer
def after_create(order)
#Email sending code
end
end
This OrderObserver sends an email when a Order#create is finished. Read more about observer class
Regarding sending email with rails3 check this, and its same as sending emails for forgotpassword / signup etc, it's just that content is different.