我正在使用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写入开发数据库,例如使用测试数据库代替?或者我只是做错了?
答案 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
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