如何避免ActionMailer :: Preview将数据提交到开发数据库?

时间:2014-02-22 06:55:00

标签: ruby-on-rails actionmailer

我正在使用Rails 4.1.0.beta1新的Action Mailer预览,并提供以下代码:

class EventInvitationPreview < ActionMailer::Preview
  def invitation_email
    invite = FactoryGirl.create :event_invitation, :for_match, :from_user, :to_user
    EventInvitationMailer.invitation_email(invite)
  end
end

这一切都很好,直到我实际尝试预览我的电子邮件并收到错误消息,说明由于重复的电子邮件地址,User对象的验证失败。事实证明,ActionMailer :: Preview正在写入我的开发数据库。

虽然我可以解决验证失败或使用fixtures而不是工厂,但有没有办法避免ActionMailer :: Preview写入开发数据库,​​例如使用测试数据库代替?或者我只是做错了?

5 个答案:

答案 0 :(得分:9)

您可以简单地使用围绕电子邮件预览的交易,只需将其放在lib/monkey_mailers_controller.rb内(并要求它):

# lib/monkey_mailers_controller.rb
class Rails::MailersController
  alias_method :preview_orig, :preview

  def preview
    ActiveRecord::Base.transaction do
      preview_orig
      raise ActiveRecord::Rollback
    end
  end
end

然后您可以在邮件预览中调用.create等,但不会将任何内容保存到数据库中。适用于Rails 4.2.3

答案 1 :(得分:5)

TL; DR - ActionMailer预览功能的原作者(通过MailView gem)提供了三个不同支持方法的示例:

  • 从现有灯具中提取数据:Account.first
  • 类似工厂的模式:user = User.create!后跟user.destroy
  • Stub-like:Struct.new(:email, :name).new('name@example.com', 'Jill Smith')

~~~~~~~~~~

详细阐述OP所面临的挑战......

此挑战的另一个表现形式是尝试使用FactoryGirl.build(而非创建)来生成非持久性数据。这种方法是由谷歌的最高结果之一提出的,其中包括&#34; Rails 4.1&#34; - http://brewhouse.io/blog/2013/12/17/whats-new-in-rails-4-1.html?brewPubStart=1 - 在&#34;如何使用此新功能&#34;例。这种方法似乎是合理的,但是如果您尝试根据该数据生成网址,则会导致以下错误:

ActionController::UrlGenerationError in Rails::Mailers#preview

No route matches {:action=>"edit", :controller=>"password_resets", :format=>nil, :id=>nil} missing required keys: [:id]

使用FactoryGirl.create(而不是构建)可以解决这个问题,但正如OP所说,会导致污染开发数据库。

如果您查看原始MailView gem的文档,该文档成为此Rails 4.1功能,原作者在这种情况下提供了更明确的意图。也就是说,原作者提供了以下三个例子,都集中在数据重用/清理/非持久性上,而不是提供使用不同数据库的方法:

# app/mailers/mail_preview.rb or lib/mail_preview.rb
class MailPreview < MailView
  # Pull data from existing fixtures
  def invitation
    account = Account.first
    inviter, invitee = account.users[0, 2]
    Notifier.invitation(inviter, invitee) 
  end

  # Factory-like pattern
  def welcome
    user = User.create!
    mail = Notifier.welcome(user)
    user.destroy
    mail
  end

  # Stub-like
  def forgot_password
    user = Struct.new(:email, :name).new('name@example.com', 'Jill Smith')
    mail = UserMailer.forgot_password(user)
  end
end

答案 2 :(得分:2)

如果您有一个复杂的对象层次结构,您可以利用事务语义来回滚数据库状态,就像在测试环境中一样(假设您的数据库支持事务)。例如:

# spec/mailers/previews/price_change_preview.rb
class PriceChangeMailerPreview < ActionMailer::Preview
  #transactional strategy
  def price_decrease
    User.transaction do
      user = FactoryGirl.create(:user, :with_favorited_products) #creates a bunch of nested objects
      mail = PriceChange.price_decrease(user, user.favorited_products.first)
      raise ActiveRecord::Rollback, "Don't really want these objects committed to the db!"
    end
    mail
  end
end

#spec/factories/user.rb
FactoryGirl.define do
  factory :user do
    ...
    trait :with_favorited_products do
      after(:create) do |user|
         user.favorited_products << create(:product)
         user.save!
      end
    end
  end
end

我们不能在这种情况下使用依赖于:: destroy的user.destroy,因为通常破坏相关产品是没有意义的(如果亚马逊将我作为客户删除,他们不会删除我所喜欢的所有产品来自市场)。

请注意previous gem implementations of the preview functionality支持交易。不确定为什么ActionMailer :: Preview不支持它们。

答案 3 :(得分:2)

对于Rails 6:

@Markus' answer对我有用,除了它引起了与Rails 6中Autoloading的变化有关的讨厌的即将弃用的即将成为现实的错误:

DEPRECATION警告:初始化会自动加载常量[许多常量似乎与我的实际操作无关]

不建议这样做。 在将来的Rails版本中,初始化过程中的自动加载将成为错误条件。

[...]

嗯,那不好!

更多搜索后,this blog和文档 to_prepare帮助我提出了这个解决方案,这只是@Markus的回答to_prepare中的包装。 (而且它位于initializer/中,而不是lib/中。)

# /config/initializers/mailer_previews.rb
---
# Wrap previews in a transaction so they don't create objects.

Rails.application.config.to_prepare do

  class Rails::MailersController
    alias_method :preview_orig, :preview

    def preview
      ActiveRecord::Base.transaction do
        preview_orig
        raise ActiveRecord::Rollback
      end
    end
  end

end

答案 4 :(得分:2)

一种更干净的处理方法是在模块之前添加一个将preview覆盖并包装到事务中的模块:

module RollbackingAfterPreview
  def preview
    ActiveRecord::Base.transaction do
      super
      raise ActiveRecord::Rollback
    end
  end
end

Rails.application.config.to_prepare do
  class Rails::MailersController
    prepend RollbackingAfterPreview
  end
end