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.
Environment
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With