homeASCIIcasts

206: ActionMailer in Rails 3 

(view original Railscast)

Other translations: En Es It

Other formats:

Written by 空心 (liguokai.com)

ActionMailer在Rails3.0里面有很大的变化。ActionMailer在Rails3.0里面使用更好用的Mail gem 替代之前使用的TMail gem。

我们创建一个新的Rails 3 应用程序来演示这个ActionMailer。我们把这个应用程序叫做:mailit

rails mailit

接下来我们为一个User模型创建脚手架(scaffold)。这个模型有两个属性:nameemail,用来演示简单的用户注册页面。

rails g scaffold user name:string email:string

然后我们运行数据库迁移(migrations):

rake db:migrate

刚才生成的脚手架(scaffolding)代码包括一个创建用户的页面。我们希望应用程序在这个表单(form)提交后创建一个新用户并且发送一封确认信。

The user registration form.

首先我们需要做的是创建一个新的初始化文件(initializer file)并添加一些配置信息,我们把它命名为setup_mail.rb。这个文件放在/config/initializers目录下。

如果你的机器里面已经安装并配置了sendmail,ActionMailer会使用sendmail发送邮件。我们也可以在initializer中指定SMTP设置。

/config/initializers/setup_mail.rb

ActionMailer::Base.smtp_settings = {
  :address              => "smtp.gmail.com",
  :port                 => 587,
  :domain               => "asciicasts.com",
  :user_name            => "asciicasts",
  :password             => "secret",
  :authentication       => "plain",
  :enable_starttls_auto => true
}

也许你想用在生产环境下(production environment)使用另外的发送方式。但如果我们的应用程序正在开发阶段,这个方式已经足够了。你需要去根据你自己的Gmail帐号信息去修改domain,user_name,和password

这样我们已经完成了配置信息,我们可以用下面的代码去生成一个新的mailer。

rails g mailer user_mailer

这个命令会帮我们在/app/mailers目录下创建一个新的user_mailer.rb文件。早期版本的Rails会把mailer类文件都放在/app/models文件夹里面,在Rails 3他们被放在自己的文件夹里面(译者注:/app/mailer)。Mailers在Rails 3像控制器(controller),并且Mailers和控制器共享很多底层代码。

UserMailer类的默认代码看起来像这样(/app/mailers/user_mailer.rb):

/app/mailers/user_mailer.rb

class UserMailer < ActionMailer::Base
  default :from => "from@example.com"
end

我们现在把default那行代码删掉,在后面我们会解释这行代码的用途。

就像Rails 2应用程序里面一样,我们为每个我们要发送的email类型添加一个方法。在我们的例子里,我们添加一个叫做 registration_confirmation的方法。

/app/mailers/user_mailer.rb

class UserMailer < ActionMailer::Base
  def registration_confirmation(user)
    mail(:to => user.email, :subject => "Registered", :from => "eifion@asciicasts.com")
  end
end

我们给registration_confirmation方法传递一个User对象,然后我们需要做的仅仅是调用mail方法,给mail方法传递:to,:from:subject之类的参数。

如果我们在类里面有多个共享相关设置的方法,我们可以把这些共享值放在之前我们删除的default方法里面。比方说email总是从同样的地方发送,我们就可以把:from设置放到default里面:

/app/mailers/user_mailer.rb

class UserMailer < ActionMailer::Base
  default :from => "eifion@asciicasts.com"

  def registration_confirmation(user)
    mail(:to => user.email, :subject => "Registered")
  end
end

我们可以把任何准备共享的设定(任何可以在mail方法里面设定的值)抽出来放在default方法里面。

类似controllers,mail需要有相对于的视图(view)文件。 我们的注册确认邮件的视图文件放在/app/views/user_mailer文件夹内。 我们准备用纯文本的形式发送邮件,因此我们的文件名叫做registration_confirmation.text.erb。这个文件里面的任何内容都将作为email的内容显示。

/app/views/usermailer/registration_confimration.text.erb:

Thank you for registering!

接下来我们需要写些代码,这些代码实现当创建用户的时候发送邮件的功能。有些人喜欢使用Model Observer去实现,但我们准备直接在controller层里面实现。 我们这样做的理由是: 如果我们使用观察者(observer), 那么我们如果在Rails命令行(console)里面创建User对象的时候,后台也会发送email。我们只想用户在应用程序里面创建时发送email。因此我们就把实现代码放在controller里面。

我们在UsersControllercreate action里面添加发送email的代码。我们所需要做的仅仅是调用我们刚刚写的registration_confirmation方法,把新创建的user作为参数传递进去,最后调用deliver方法发送email。

/app/controllers/users_controller.rb

def create
  @user = User.new(params[:user])

  respond_to do |format|
    if @user.save
      UserMailer.registration_confirmation(@user).deliver
      format.html { redirect_to(@user, :notice => 'User was successfully created.') }
      format.xml  { render :xml => @user, :status => :created, :location => @user }
    else
      format.html { render :action => "new" }
      format.xml  { render :xml => @user.errors, :status => :unprocessable_entity }
    end
  end
end

这个跟Rails 2有点不同,在Rails 2里,我们调用deliver_registration_confirmation方法。 现在我们的registration_confirmation方法返回一个mail message对象,然后再调用它的deliver方法。 这样我们可以在我们需要发送email的时候才调用deliver。

我们现在测试一下: 注册一个新的用户,当我们提交表单的时候,系统就会发送一封email.

The registration email send from the application.

从上图可以看到我们邮件发送成功。

如果我们想在email里面显示刚注册的用户的姓名该怎么办呢? 我们需要把user对象传到view里面。 在Rails 3里面我们很容易就可以这样做,因为mailers就像controllers一样, 任何实例变量(instance variables)都可以在view里面访问。 我们需要做的仅仅是用传递到registration_confirmation方法的user变量创建一个view里可以访问的实体变量。

/app/mailers/user_mailer.rb

def registration_confirmation(user)
  @user = user
  mail(:to => user.email, :subject => "Registered")
end

因为mail方法会返回mail message,mail方法的调用必须写在方法的最后。 这样我们得在mail方法前定义实体变量。

现在我们在mailer里面定义了实体变量,我们可以在view里面使用它把用户的姓名添加在email里。

/app/views/user_mailer/registration_confirmation.text.erb

<%= @user.name %>,

Thank you for registering!

当我们再次注册一个新的用户,用户的姓名将会显示在email的内容里:

The new user’s name is now included in the email.

如果我们想要在email里面提供给新用户编辑他们的个人信息的链接,我们可以这样做:

Edit Profile: <%= edit_user_url(@user) %>

这样是无法工作的。 我们必须使用:host提供应用程序域名.

/app/views/user_mailer/registration_confirmation.text.erb

<%= @user.name %>,

Thank you for registering!

Edit Profile: <%= edit_user_url(@user, :host => "localhost:3000") %>

必须这样做的原因是mailers已经完全从当前的请求(request)分离出来。 这样设计的原因是:mailer可以不在当前controller的请求内发送email。

我们可以在之前的initializer文件里设定host的值,这样我们就不需要对每封email的每个link都设定host值。

/config/initializers/setup_mail.rb

ActionMailer::Base.smtp_settings = {
  :address              => "smtp.gmail.com",
  :port                 => 587,
  :domain               => "asciicasts.com",
  :user_name            => "asciicasts",
  :password             => "secret",
  :authentication       => "plain",
  :enable_starttls_auto => true
}

ActionMailer::Base.default_url_options[:host] = "localhost:3000"

我们在这里可以用hash指定任何设置(options),现在我们只需要设定host值。当我们再次注册,我们可以在email里面看到一个带有正确url的链接。

The registration email now has a link to the user's profile.

Multipart邮件与附件

在Rails 3里面发送multipart emails也相当方便。 我们所需要做的仅仅是创建一个跟text view同名的视图文件(view)。 在我们的例子里面这个文件为registration_confirmation.html.erb。 在这个文件里面,我们添加一些简单的html代码。

/app/views/user_mailer/registration_confirmation.html.erb

<p><%= @user.name %>,</p>

<p>Thank you for registering!</p>

<p><%= link_to "Edit Profile", edit_user_url(@user, :host => "localhost:3000") %></p>

如果我们在支持html的客户端查看email,我们将会看到email里面有一个链接。两种类型的email内容都会发送,这样如果客户端不支持html,它将会显示纯文本形式的email内容。

The email is displayed as HTML.

添加附件操作相当直接而简单。我们只用调用attachements方法,设置附件名称,并读取一个文件给这个附件。

/app/mailers/user_mailer.rb

def registration_confirmation(user)
  @user = user
  attachments["rails.png"] = File.read("#{Rails.root}/public/images/rails.png")
  mail(:to => "#{user.name} <#{user.email}>", :subject => "Registered")
end

当我们再次注册,rails.png文件就作为一个附件显示在email里面。 注意上面的代码,我们把用户的姓名添加到:to里面了。

Adding an attachment.

可以看出使用新的ActionMailer API,我们很容易就创建复杂的emails. 默认的设置基本上够用了,如果你想设置类似编码格式(encoding types)的值,你可以修改覆盖默认设置。

拦截器(Interceptors)

再结束本章之前我们将会介绍一向新技术:在email messages发送前拦截它们。 拦截器可以帮我们在开发模式的时候(development mode)改变email的发送方式, 这样emails将会只发送到你自己的帐号里而不会发送到任何你创建的用户的email帐号里。

这个功能刚刚被加在新的Mail gem里, 因此我们需要升级mail到最新版本(至少2.1.3)。我们通过修改应用程序的Gemfile文件去获得正确的版本:

/Gemfile

gem "mail", "2.1.3"

然后我们运行bundle install去安装升级的版本。

接下来我们需要创建拦截器(interceptor)类。 我们把这个文件命名为development_mail_interceptor.rb并把它放在/lib目录下。

/lib/development_mail_interceptor.rb

class DevelopmentMailInterceptor
  def self.delivering_email(message)
    message.subject = "[#{message.to}] #{message.subject}"
    message.to = "eifion@asciicasts.com"
  end
end

类方法delivering_email获得准备发送的email message, 修改它的主题(subject)为原目标地址(message.to) 加上原主题(message.subject)。message的to字段也被修改,这样所有的email将会发到eifion@asciicasts.com

我们需要把这个拦截器(interceptor)注册到我们的初始化文件(initializer)内:

/config/initializers/setup_mail.rb

Mail.register_interceptor(DevelopmentMailInterceptor) if Rails.env.development?

如果我们的应用程序运行在开发模式(development mode)下,我们的拦截器(interceptor)的delivering_email方法将会被调用。 如果我们的Rails 3.0 包含升级了的Mail gem, 我们可以把方法调用Mail.register_interceptor替换为ActionMailer::Base.register_interceptor

当我们注册新用户的时候,注册确认email将永远发给eifion@asciicasts.com。 原始的收件人(recipient)地址显示在主题栏。

In development mode the email is now sent to us rather than the intended user.

当你开发你的应用程序的时候,这是个检查你的emails是否工作的好办法。

以上就是这章的全部了。 我希望这章对你有用。新的ActionMailer API 让 Rails 应用程序发送email更加方便。