How should I use sidekiq workers? - ruby-on-rails

I have application in which I can create invoice, render it to pdf and send to the customer. I have two action in my mailer:
class InvoiceMailer < ActionMailer::Base
default from: "from#example.com"
def send_invoice_reminder(invoice)
#invoice = invoice
attach_invoice
mail :subject => "Invoice reminder", :to => invoice.customer.email
end
def send_invoice(invoice)
#invoice = invoice
attach_invoice
mail :subject => "Your Invoice", :to => invoice.customer.email
end
protected
def attach_invoice
attachments["invoice.pdf"] = WickedPdf.new.pdf_from_string(
render_to_string(:pdf => "invoice",:template => 'admin/invoices/show.pdf.erb')
)
end
end
Now I want to send this through Sidkiq workers. But I have question. Whether I need two sidekiq workers:
one to send invoice email
second to send reminder
or maybe one worker will be enough?

I think you could use one worker for it, because you have mostly the same job for these two tasks
and It can looks like:
class InvoiceMailer < ActionMailer::Base
default from: "from#example.com"
def send_invoice(invoice, subject)
#invoice = invoice
attachments["invoice.pdf"] = pdf
mail subject: subject, to: invoice.customer.email
end
private
def pdf
WickedPdf.new.pdf_from_string render_to_string(
pdf: "invoice",
template: 'admin/invoices/show.pdf.erb'
)
end
end
class InvoceEmailSender
include Sidekiq::Worker
def perform(invoice, subject)
InvoiceMailer.send_invoice(invoice, subject).deliver
end
end
InvoiceEmailSender.perform_async invoice, 'Your Invoice'
InvoiceEmailSender.perform_async invoice, 'Invoice Reminder'

Related

Can't get users to create a custom message with devise invitable

I have 2 user-like models in my app: 'Participant' and 'Member'.
I'm trying to allow them to include a custom message when they invite other members/participants through Devise Invitable. However, I can't make it work.
I'm following this official tutorial so I've made the following changes to override Devise Invitable Controller but when using pry it seems that this custom controller goes untouched when sending an invite. What am I doing wrong:
controllers/participants/invitations_controller.rb
class Participants::InvitationsController < Devise::InvitationsController
before_action :update_sanitized_params, only: :update
def create
binding.pry
#from = params[:from]
#subject = params[:invite_subject]
#content = params[:invite_content]
#participant = Participant.invite!(params[:user], current_member) do |u| #XXX Check if :user should be changed
u.skip_invitation = true
end
ParticipantInvitationNotificationMailer.invite_message(#participant, #from, #subject, #content).deliver if #participant.errors.empty?
#participant.invitation_sent_at = Time.now.utc # mark invitation as delivered
if #participant.errors.empty?
flash[:notice] = "successfully sent invite to #{#participant.email}"
respond_with #participant, :location => root_path
else
render :new
end
end
def update
respond_to do |format|
format.js do
invitation_token = Devise.token_generator.digest(resource_class, :invitation_token, update_resource_params[:invitation_token])
self.resource = resource_class.where(invitation_token: invitation_token).first
resource.skip_password = true
resource.update_attributes update_resource_params.except(:invitation_token)
end
format.html do
super
end
end
end
protected
def update_sanitized_params
devise_parameter_sanitizer.permit(:accept_invitation, keys: [:password, :password_confirmation, :invitation_token, :username])
end
end
config/routes.rb
Rails.application.routes.draw do
devise_for :members, controllers: { invitations: "members/invitations" }
devise_for :participants, controllers: { invitations: "participants/invitations" }
end
models/participant.rb
class Participant < ApplicationRecord
attr_reader :raw_invitation_token
end
mailers/notification_mailer.rb
class NotificationMailer < ApplicationMailer
def invite_message(user, from, subject, content)
#user = user
#token = user.raw_invitation_token
invitation_link = accept_user_invitation_url(:invitation_token => #token)
mail(:from => from, :bcc => from, :to => #user.email, :subject => subject) do |format|
content = content.gsub '{{first_name}}', user.first_name
content = content.gsub '{{last_name}}', user.last_name
content = content.gsub '{{full_name}}', user.full_name
content = content.gsub('{{invitation_link}}', invitation_link)
format.text do
render :text => content
end
end
end
end
If I send an invitation:with Participant.invite!({:email => 'example#email.com'}, Member.first) the invitation is sent through the default mailer as shown in the console but not through my new mailer. why?
Rendering /Users/andres/.rvm/gems/ruby-2.4.0#pixiebob/gems/devise_invitable-1.7.1/app/views/devise/mailer/invitation_instructions.html.erb
Rendered /Users/andres/.rvm/gems/ruby-2.4.0#pixiebob/gems/devise_invitable-1.7.1/app/views/devise/mailer/invitation_instructions.html.erb (0.6ms)
Rendering /Users/andres/.rvm/gems/ruby-2.4.0#pixiebob/gems/devise_invitable-1.7.1/app/views/devise/mailer/invitation_instructions.text.erb
Rendered /Users/andres/.rvm/gems/ruby-2.4.0#pixiebob/gems/devise_invitable-1.7.1/app/views/devise/mailer/invitation_instructions.text.erb (0.8ms)
Finally, I could solve this issue.
It ended up being a rookie mistake I was thinking that calling the invite! method would have anything to do with the custom create method in the custom invitations controller.
I had of course to reach the create method through the specified route and within that method prevent the invite! method to send the email through the default mailer using code below (as established clearly in the Devise Invitable Documentation):
#participant = Participant.invite!({:email => #invitation_draft.email}, current_member) do |u|
u.skip_invitation = true
end
After this we can call any custom mailer in the create method.

Rails: Why is only one email sent to the last member of a loop?

I am very much a rails novice!
I am trying to write a method for a kind of on-line committee meeting. There are a fixed number(9) of users. When a user proposes a topic for discussion and/or voting the submit button needs to send an email to all members.
in app/mailers/user_mailer.rb I have:-
class UserMailer < ApplicationMailer
def new_topic_alert(topic)
#users = User.all
#users.each do |user|
mail to: user.email, subject: "New topic alert"
end
end
end
as part of app/controllers/topics_controller.rb I have:-
def send_alert
#topic = Topic.new(topic_params)
UserMailer.new_topic_alert(#topic).deliver_now
end
and:-
def create
#topic = Topic.new(topic_params)
if #topic.save
send_alert
flash[:info] = "New Topic alert emails sent."
redirect_to root_url
else
render 'new'
end
end
Please, why does the loop in user_mailer only send an email to the final person of the list. By incorporating "byebug" I have shown that it goes through all the user emails.
Try like below:
def send_alert
#topic = Topic.new(topic_params)
users = User.all
users.each do |u|
UserMailer.new_topic_alert(#topic, u).deliver_now
end
end
and update the mailer like
class UserMailer < ApplicationMailer
def new_topic_alert(topic,user)
mail to: user.email, subject: "New topic alert"
end
end

Rails 4.2: using deliver_later with a tableless model

I am trying to setup a contact form using Rails 4.2's deliver_later method. However, I can only get deliver_now to work, as deliver_later is trying to serialize my object and fails each time.
Here's my setup:
messages_controller.rb
class MessagesController < ApplicationController
def new
#message = Message.new
end
def create
#message = Message.new(params[:message])
if #message.valid?
ContactMailer.contact_form(#message).deliver_later
redirect_to root_path, notice: "Message sent! Thank you for contacting us."
else
render :new
end
end
end
contact_mailer.rb
class ContactMailer < ApplicationMailer
default :to => Rails.application.secrets['email']
def contact_form(msg)
#message = msg
mail(:subject => msg.subject, from: msg.email)
end
end
message.rb
class Message
include ActiveModel::Model
include ActiveModel::Conversion
## Not sure if this is needed ##
include ActiveModel::Serialization
extend ActiveModel::Naming
attr_accessor :name, :subject, :email, :body
validates_presence_of :email, :body
validates_format_of :email, with: /\A([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})\z/i
validates_length_of :body, :maximum => 1000
def initialize(attributes = {})
attributes.each { |name, value| send("#{name}=", value) }
end
## Not sure if this is needed ##
def attribtues
{'name' => nil, 'subject' => nil, 'email' => nil, 'body' => nil}
end
end
The error I get when calling ContactMailer.contact_form(#message).deliver_later is:
ActiveJob::SerializationError in MessagesController#create
Unsupported argument type: Message
Extracted source (around line #10):
if #message.valid?
ContactMailer.contact_form(#message).deliver_later
redirect_to root_path, notice: "Message sent! Thank you for contacting us."
else
render :new
Ideally I'd like this to be a background process. I will be adding something like Sidekiq soon but I think it's best I get this serialization problem fixed beforehand.
Any help is appreciated! Thanks :)
In order to use your class with ActiveJob (that's what deliver_later delegates to), it needs to be able to uniquely identify the object by its ID. Further, it needs to find it later by the ID when deserializing (no manual deserialize is necessary in the mailer / job).
class Message
...
include GlobalID::Identification
...
def id
...
end
def self.find(id)
...
end
end
ActiveRecord would provide you with these methods but since you're not using it, you need to implement it yourself. It's up to you to decide where you want to store the record but honestly I think you'd be better off by using ActiveRecord and the table underneath.
A simple solution that avoids having to back the object with ActiveRecord or create an unnecessary table:
Instead of passing the Message object to the contact_form method, you can also pass the message params to the contact_form method and then initialize the Message object inside that method.
This will solve the problem without having to create a table, because you are initializing the object in the delayed job worker's memory space.
For example:
messages_controller.rb
MessagesController < ApplicationController
def new
#message = Message.new
end
def create
#message = Message.new(params[:message])
if #message.valid?
ContactMailer.contact_form(params[:message]).deliver_later
redirect_to root_path, notice: "Message sent! Thank you for contacting us."
else
render :new
end
end
end
contact_mailer.rb
class ContactMailer < ApplicationMailer
default :to => Rails.application.secrets['email']
def contact_form(msg_params)
#message = Message.new(msg_params)
mail(:subject => msg.subject, from: msg.email)
end
end
I had a similar problem today and solved it as follows.
Convert a tableless object into a JSON sting
Pass it to a mailer
Convert the json string to hash
Environment
Rails 5.0.2
messages_controller.rb
class MessagesController < ApplicationController
# ...
def create
#message = Message.new(message_params)
if #message.valid?
ContactMailer.contact_form(#message.serialize).deliver_later
redirect_to root_path, notice: "Message sent! Thank you for contacting us."
else
render :new
end
end
# ...
end
contact_mailer.rb
class ContactMailer < ApplicationMailer
default :to => Rails.application.secrets['email']
def contact_form(message_json)
#message = JSON.parse(message_json).with_indifferent_access
mail(subject: #message[:subject], from: #message[:email])
end
end
message.rb
class Message
include ActiveModel::Model
attr_accessor :name, :subject, :email, :body
validates_presence_of :email, :body
validates_format_of :email, with: /\A([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})\z/i
validates_length_of :body, :maximum => 1000
# Convert an object to a JSON string
def serialize
ActiveSupport::JSON.encode(self.as_json)
end
end
Hope this will help anybody.
You'll need to serialize the object before passing to AJ and deserialize in the mailer.

PayPal IPN Send Email

I have a controller that handles PayPal's IPN callback. I want to mark an attendee as 'paid' and send them a confirmation email if they've successfully paid.
The mark paid action is working but the email is not sending.
Here's my controller:
class PaymentNotificationsController < ApplicationController
protect_from_forgery :except => [:create]
def create
PaymentNotification.create!(:params => params, :attendee_id => params[:invoice], :status => params[:payment_status], :transaction_id => params[:txn_id])
if params[:payment_status] == 'Complete'
#attendee = Attendee.find(params[:invoice])
## Working
#attendee.update_attribute(:paid, Time.now)
## Not Working
UserMailer.welcome_email(#attendee).deliver
end
render nothing: true
end
end
Here's my user_mailer file:
class UserMailer < ActionMailer::Base
default from: 'example#email.com'
def welcome_email(user)
#user = user
email_with_name = "#{#user.first_name} #{#user.last_name} <#{#user.email}>"
#url = 'http://example.com'
mail(
to: email_with_name,
subject: 'Welcome to Yadda Yadda'
)
end
end
Here's the weird thing, in another controller that doesn't have PayPal the mailer works:
class VendorsController < ApplicationController
def create
#vendor = Vendor.new(vendor_params)
if #vendor.save
UserMailer.welcome_email(#vendor).deliver
redirect_to vendor_success_path
else
render 'new'
end
end
end
I am pulling your answer out of your question and posting it here for future reference.
This takes two actions (mark paid and send mail). It has been moved to the model as an after_create method.
Here's the model:
class PaymentNotification < ActiveRecord::Base
...
after_create :mark_attendee_paid
private
def mark_attendee_paid
if status == 'Completed'
attendee.update_attribute(:paid, Time.now)
UserMailer.welcome_email(attendee).deliver
end
end
end

Rails - Mailer Instance Variable Nil in Email View

I am attempting to create beta invitations using the structure from railscasts episode 124, updated for rails 3.2.8.
Currently, the invitation email gets sent, but does not contain the url (which includes the invitation token) for users to follow to sign up because the instance variable I am creating in ActionMailer (#invitation_link) is nil in the view. Inspecting #invitation_link in the ActionMailer controller shows that it is pointing to the correct url, but it is nil in the view.
I have also checked out the following questions and none of the solutions have worked for me:
How do you use an instance variable with mailer in Ruby on Rails?
https://stackoverflow.com/questions/5831038/unable-to-access-instance-variable-in-mailer-view
Actionmailer instance variable problem Ruby on Rails
ActionMailer pass local variables to the erb template
Relevant code snippets below:
invitations_controller.rb
class InvitationsController < ApplicationController
def new
#invitation = Invitation.new
end
def create
#invitation = Invitation.new(params[:invitation])
#invitation.sender = current_user
if #invitation.save
if signed_in?
InvitationMailer.invitation(#invitation).deliver
flash[:notice] = "Thank you, invitation sent."
redirect_to current_user
else
flash[:notice] = "Thank you, we will notify when we are ready."
redirect_to root_url
end
else
render :action => 'new'
end
end
end
in invitation_mailer.rb file
class InvitationMailer < ActionMailer::Base
default from: "holler#thesite.com", content_type: "text/html"
def invitation(invitation)
mail to: invitation.recipient_email, subject: "Invitation"
#invitation_link = invited_url(invitation.token)
invitation.update_attribute(:sent_at, Time.now)
end
end
views/invitation_mailer/invitation.text.erb
You are invited to join the site!
<%= #invitation_link %> # INSTANCE VARIABLE THAT IS NIL IN VIEW
routes.rb (only showing relevant line)
match '/invited/:invitation_token', to: 'users#new_invitee', as: 'invited'
try this way
This is your InvitationMailer
def invitation(invitation)
#invitation = invitation
mail(:to => #invitation.recipient_email, :subject => "Invitation")
end
now, in your InvitationsController
if signed_in?
#invitation.update_attribute(:sent_at, Time.now)
InvitationMailer.invitation(#invitation).deliver
...
else
...
end
now, views/invitation_mailer/invitation.text.erb
You are invited to join the site!
<%= invited_url(#invitation.token) %> # INSTANCE VARIABLE THAT IS NIL IN VIEW
try this...
#invitation_link = invited_url(invitation.token, :host => "localhost:3000")

Resources