Rails before_action для ActionMailer, который будет использовать аргументы почтовой программы

Предположим, у меня есть почтовая программа, которая отправляет разные электронные письма, но ожидается, что она будет вызываться с одинаковыми параметрами. Я хочу обрабатывать эти параметры для всех действий почтовой программы. Таким образом, вызов before_action, который будет читать параметры, отправленные в метод почтовой программы

/mailers/my_mailer.rb
class MyMailer < ApplicationMailer
    before_filter do |c|
      # c.prepare_mail # Will fail, because I need to pass `same_param` arguments
      # # I want to send the original arguments
      # c.prepare_mail(same_param) # How do I get `same_param` here ?
    end

    def action1(same_param)
      # email view is going to use @to, @from, @context    
      method_only_specific_to_action1
    end

    def action2(same_param)
      # email view is going to use @to, @from, @context
      method_only_specific_to_action2
    end

    private
      def prepare_mail(same_params)
        @to = same_params.recipient
        @from = same_params.initiator
        @context = same_params.context
      end
    end

Затем в моем контроллере/сервисе я где-то делаю

MyMailer.actionx(*mailer_params).deliver_now

Как я могу получить доступ к списку аргументов same_param внутри блока before_action?

РЕДАКТИРОВАТЬ :

Я хочу провести рефакторинг из

/mailers/my_mailer.rb
class MyMailer < ApplicationMailer

    def action1(same_param)
      @to = same_params.recipient
      @from = same_params.initiator
      @context = same_params.context   
      method_only_specific_to_action1
    end

    def action2(same_param)
      @to = same_params.recipient
      @from = same_params.initiator
      @context = same_params.context   
      method_only_specific_to_action2
    end

    def actionx
      ... 
    end
  end

И этот рефакторинг

/mailers/my_mailer.rb
class MyMailer < ApplicationMailer

    def action1(same_param)
      prepare_mail(same_params)   
      method_only_specific_to_action1
    end

    def action2(same_param)
      prepare_mail(same_params)   
      method_only_specific_to_action2
    end

    def actionx
      ... 
    end

    private
      def prepare_mail(same_params)
        @to = same_params.recipient
        @from = same_params.initiator
        @context = same_params.context
      end
    end

Выглядит неоптимально (prepare_mail(same_params) повторяется в каждом действии)

Отсюда то, что было предложено выше


person Cyril Duchon-Doris    schedule 20.02.2015    source источник
comment
просто мысль - вы думали об использовании net::STMP напрямую для отправки электронных писем ruby-doc.org/stdlib-2.0.0/libdoc/net/smtp/rdoc/Net/SMTP.html. Он будет более настраиваемым, чем Action Mailer.   -  person z atef    schedule 15.01.2017
comment
С точки зрения разработки программного обеспечения, ActionMailer — это адаптер, и вы можете настроить несколько методов доставки электронной почты (я сейчас использую 3-4), я боюсь, что SMTP — это еще не все, что есть в мире, поэтому я хочу использовать ActionMailer.   -  person Cyril Duchon-Doris    schedule 15.01.2017
comment
Я думаю, вы перемещаете логическую часть из контроллера в слой. Лучше всего добавить сервисный класс/класс модели между классом почтовой программы и контроллером, чтобы достичь этого, а не переопределять почтовую программу по умолчанию для достижения того, что вы хотите.   -  person Sairam    schedule 15.01.2017
comment
@spickermann Извините, забыл о наследстве, добавляю его сейчас. Вы можете считать ApplicationMailer похожим на ActionMailer::Base. В моем предыдущем комментарии я просто объяснял, что я не только отправляю почту напрямую через SMTP, но также настраиваю использование адаптеров доставки :file, :cache, :letter_opener и т. д., которые хорошо упакованы ActionMailer   -  person Cyril Duchon-Doris    schedule 15.01.2017
comment
@sairam Я не согласен, вы можете видеть в prepare_mail, что я просто устанавливаю переменные экземпляра, которые будут напрямую использоваться в представлении ActionMailer. В противном случае мне пришлось бы передать слишком много параметров   -  person Cyril Duchon-Doris    schedule 15.01.2017
comment
@spickermann Нет, совсем не жаль, если вопрос неясен, но в предыдущем комментарии упоминалось, что ActionMailer полностью отключен. Я просто хочу понять, могу ли я в своем фильтре before_action сделать что-то вроде c.prepare_mail(*arguments_sent_to_mailer_action). Я только что отредактировал, надеюсь так понятнее   -  person Cyril Duchon-Doris    schedule 15.01.2017
comment
before_action определяется в AbstractController::Callbacks. Этот метод не существует в контексте Mailer. Если вы действительно хотите использовать обратные вызовы в контексте Mailer, вам необходимо реализовать логику для их использования. Вы можете заглянуть в ActiveSupport::Callbacks.   -  person spickermann    schedule 16.01.2017
comment
@spickermann Извините, но Callbacks in Action Mailer are implemented using AbstractController::Callbacks (по крайней мере, в Rails 4-5, да, я знаю, что странно, что они не рефакторили это имя) и before_action РАБОТАЮТ. api.rubyonrails.org/classes/ActionMailer/Base.html   -  person Cyril Duchon-Doris    schedule 16.01.2017
comment
Кажется, ваша проблема решена с помощью параметризованных почтовых программ, которые являются частью Rails 5.1 (см. пиар)   -  person spickermann    schedule 28.01.2017
comment
Привет, @spickermann, звучит круто! Пожалуйста, добавьте ответ с этим, когда он будет выпущен, я обязательно его использую: D   -  person Cyril Duchon-Doris    schedule 28.01.2017


Ответы (3)


ActionMailer использует модуль AbstractController::Callbacks. Я попробовал это, и это, кажется, работает для меня.

Код

class MyMailer < ApplicationMailer
  def process_action(*args)
    # process the args here
    puts args
    super
  end

  def some_mail(*args)
  end
end

MyMailer.some_mail(1, 2) #=> prints ['some_mail', 1, 2]

Документация


ОБНОВИТЬ

Если вы используете Rails 5.1, вы можете взглянуть на ActionMailer:: Параметризовано

person hsluo    schedule 19.01.2017
comment
как бы вы сделали это, используя before_action? Я пытался, но аргументов не было - person pixelearth; 08.06.2018
comment
Я тоже не знаю. На самом деле, если вы используете Rails 5.1, вы можете взглянуть на ActionMailer ::параметризованный - person hsluo; 10.06.2018
comment
api.rubyonrails.org/v5.1/classes/ActionMailer/ на самом деле довольно хорош и, вероятно, там, где дела идут, но на самом деле он сломал большинство существующих тестов, и на момент написания он на самом деле не очень хорошо поддерживается RSpec и потребует довольно много переписывания кода. Мы пытались использовать это в разных почтовых программах, но на момент написания статьи это было не так просто, как process_action - person Cyril Duchon-Doris; 04.04.2019

Решение1:

Я бы посоветовал вам использовать это, если вам не нужен формат

MyMailer.generic("actionx", *mailer_params).deliver_now

def generic(actiontype, *mailer_params)
  # custom logic goes here to construct the to, from, etc.,
  # new_options from custom logic
  self.send(actiontype, new_options)
end

альтернативное решение ниже с использованием method_missing из родительского контроллера

Неправильно помещать туда свою логику, но если вы все же хотите это сделать, вы можете использовать method_missing, чтобы поместить туда свою логику и пропустить методы action1 и action2.

Оригинальный метод method_missing из action_mailer, который можно использовать в качестве ссылки:

def method_missing(method_name, *args)
  if action_methods.include?(method_name.to_s)
    MessageDelivery.new(self, method_name, *args)
  else
    super
  end
end

https://github.com/rails/rails/blob/c8a18aaf44a84f1ef0df007aa3f8446752dc327d/actionmailer/lib/action_mailer/base.rb#L561-L567

person Sairam    schedule 15.01.2017
comment
Проблема в том, что есть много not-really logic, которые состоят в заполнении заголовков электронной почты (и они одинаковы для каждого действия), но каждое отдельное действие mailer_action остается определенным, и я мог бы захотеть сделать там дополнительные вещи (например, прикрепить определенный файл и т. д. .) - person Cyril Duchon-Doris; 15.01.2017
comment
см. первое решение. self.send (тип действия, параметры) - person Sairam; 15.01.2017
comment
О, это действительно имеет смысл! Извините, сначала не понял, что он делает. - person Cyril Duchon-Doris; 15.01.2017
comment
@Sairam, ты действительно попробовал свое первое предложение? Это привлекательно, но под rails 4.2 я получаю ActionView::MissingTemplate: Missing template my_mailer/generic with "mailer". - person KenB; 08.01.2019

Основываясь на ответе Сайрама, я подумал о следующем, но это кажется немного странным, нельзя ли это сделать с помощью обратного вызова before_action ??

class MyMailer < ApplicationMailer

    # Simulation of before_action callback that would support passing the *args to the callback method
    def self.method_missing(method_name, *args)
      method_name = :"#{method_name.to_s}_headers_prefilled"
      if action_methods.include?(method_name)
        mailer = MyMailer.generic(*args) # The before_action callback that passes *args
        mailer.send(method_name, *args) # The action itself
      else
        super
      end
    end

    def generic(*mailer_params)
      # custom logic goes here to construct the headers to, from, etc.,
    end

    def action1_headers_prefilled(mailer_params)
      # Logic only relevant for action1
    end

Также я теряю все классные вещи из before_action (передача массива only или except и т. д.)

person Cyril Duchon-Doris    schedule 15.01.2017