Rails 4.2: использование deliver_later с моделью без таблиц

Я пытаюсь настроить контактную форму, используя метод Rails 4.2 deliver_later. Тем не менее, я могу только доставить работу до сих пор, поскольку deliver_later пытается сериализовать мой объект и терпеть неудачу каждый раз.

Здесь моя настройка:

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

Ошибка при вызове ContactMailer.contact_form(@message).deliver_later:

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

В идеале я хотел бы, чтобы это было фоновым процессом. Я скоро добавлю что-то вроде Sidekiq, но я думаю, что лучше всего решить эту проблему с сериализацией заранее.

Любая помощь приветствуется! Спасибо:)

Ответ 1

Чтобы использовать ваш класс с ActiveJob (для чего делегат deliver_later), он должен иметь возможность однозначно идентифицировать объект по его идентификатору. Кроме того, он должен найти его позже по идентификатору при десериализации (в почтовом ящике/задании не требуется никакого десериализации).

class Message
  ...
  include GlobalID::Identification
  ...

  def id
    ...
  end

  def self.find(id)
    ...
  end
end

ActiveRecord предоставит вам эти методы, но поскольку вы его не используете, вам нужно реализовать его самостоятельно. Вам решать, где вы хотите сохранить запись, но, честно говоря, я думаю, вам будет лучше с помощью ActiveRecord и таблицы внизу.

Ответ 2

Простое решение, которое позволяет избежать обратного вызова объекта с помощью ActiveRecord или создать ненужную таблицу:

Вместо передачи объекта Message методу contact_form вы также можете передать параметры сообщения методу contact_form и затем инициализировать объект Message внутри этого метода.

Это решит проблему, не создавая таблицу, потому что вы инициализируете объект в пространстве памяти замедленного рабочего места.

Например:

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

Ответ 3

У меня была аналогичная проблема сегодня и решил ее следующим образом.

  • Преобразование объекта без таблиц в JSON sting
  • Передать его почтовой программе
  • Преобразование строки json в хэш

Окружающая среда

  • 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

Надеюсь, это поможет любому.

Ответ 4

Вам нужно будет сериализовать объект перед тем, как перейти к AJ и выполнить десериализацию в почтовой программе.