ActionMailer in Ruby On Rails

I. Giới thiệu.

ActionMailer trong Rails cho phép bạn gửi email từ ứng dụng của bạn thông qua class mailer và views. ActionMailer về cơ bản hoạt động cũng giống như ta xây dựng một controller và định nghĩa các action và view tương ứng. Nó được kế thừa từ lớp ActionMailer::Base và được đặt mặc định trong thư mục app/mailers và lớp views cũng nằm trong thư mục app/views.

II. Gửi email bằng ActionMailer.

Trong phần này chúng ta sẽ xây dựng một app với một vài chức năng mà tôi tin hầu hết các ứng dụng web bây giờ đều cần đến, đó là cho User tạo tài khoản của hệ thống bằng địa chỉ email, và sau đó xác thực địa chỉ email bằng việc để hệ thống tự động gửi email đến tài khoản email của User, kèm một confirm_token để xác thực.

1. Việc đầu tiên là tạo ứng dụng demo.

rails new demo_app

Sau đó tạo model User:

rails g model User mail_address password_digest confirm_token confirmed_at:datetime

Trong gem file, ta thêm "gem 'bcrypt', '~> 3.1.7'", "gem 'sidekiq'", "gem 'redis-namespace'". Và bước tiếp theo là cần cài đặt thuộc tính, các function trong class user, trong app/models/user.rb ta cài đặt như sau:

class User < ActiveRecord::Base
  PARAMS_SIGNUP = [:mail_address, :password, :password_confirmation]
  EMAIL_REGEX = /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i

  validates :mail_address, format: {with: EMAIL_REGEX}, uniqueness: true, presence: true
  validates :password, presence: true, length: {minimum: 6, maximum: 20}

  before_create :generate_confirm_token

  has_secure_password

  scope :confirmed, ->{where "`users`.`confirm_token` IS NULL AND `users`.`confirmed_at` < ?", Time.now}

  def reset_confirm_token
    generate_confirm_token
    self.save
  end

  private
  def generate_token column
    begin
      self[column] = SecureRandom.urlsafe_base64
    end while User.exists?(column => self[column])
  end

  def generate_confirm_token
    generate_token :confirm_token
  end
end

Để ý dòng scope :confirmed, ->{where "users.confirm_tokenIS NULL ANDusers.confirmed_at< ?", Time.now} , ta định nghĩa một scope confirmed ở đây để chỉ ra các tài khoản đã xác thực mail_address. Sau đó chúng ta cần tạo controller và view của User để có được giao diện signup:

rails g controller Users

Ta tiếp tục cài đặt các hàm và các view tương ứng, đầu tiên là trang signup và trang chuyển đến sau khi tài khoản được tạo với nội dung thông báo cho người dùng phải xác thực tài khoản email. Trong app/contrrollers/users_controller.rb ta cài đặt các action sau:

class UsersController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new user_signup_params
    if @user.save
      redirect_to check_mail_address_path
    else
      render :new
    end
  end

  def check_mail_address
  end

  private
  def user_signup_params
    params.require(:user).permit(User::PARAMS_SIGNUP)
  end
end

Tạo các view, trong app/views/users tạo view "new.html.erb":

<%= form_for @user do |f| %>
  <%= f.label :mail_address, "Email"%>
  <%= f.text_field :mail_address %> <br>
  <%= f.label :mail_address, "Password"%>
  <%= f.password_field :password %> <br>
  <%= f.label :mail_address, "Password confirmation"%>
  <%= f.password_field :password_confirmation %><br>
  <%= f.submit "Submit"%>
<% end %>

Tiếp tục tạo view của trang chuyển đến, thông báo cho người dùng phải xác thực mail_address sau khi signup: tạo view "check_mail_address.html.erb" trong app/views/users

We sent an email to your mail address, please check !

Trong config/routes.rb ta tạo các route để có đường link /signup , /check_mail_address :

Rails.application.routes.draw do
  get "/signup", to: "users#new"
  post "/signup", to: "users#create"
  get "check_mail_address", to: "users#check_mail_address"
  resources :users
end

2. Gửi email xác thực người dùng.

Bây giờ ta bắt đầu nói đến việc gửi email cho người dùng. Trước hết tôi xin giải thích flow mà chúng ta đã làm được 1/2 ở trên. Đầu tiên User vào trang signup, tạo tài khoản, điền đầy đủ thông tin vào sau đó submit form, lúc này tài khoản của người dùng đã có trong database nhưng sau đó, hệ thống của chúng ta cần gửi email cho ngươi dùng để xác thực email đó là email thật, không phải email giả, bằng một đường link kèm theo confirm_token mà đã được tạo ra bằng phương thưc call_back ở trong model "user.rb" đó là:

before_create :generate_confirm_token

Và sau khi người dùng click vào link đó thì hệ thống sẽ xử lí việc xác thực thông qua confirm_token, và chuyển đến trang "/signup_success". Giờ ta sẽ tạo một class mailer để xử lí việc đó:

rails g mailer UserMailer

Trong "app/mailers/user_mailer.rb" ta cài đặt action "confirm_user_mail" như sau:

class UserMailer < ApplicationMailer
  def confirm_user_mail user
    @user ||= user
    mail(to: user.mail_address, subject: "Confirm Your Account") if user.mail_address?
  end
end

Tiếp đó trong "app/views/user_mailer" ta tạo file "confirm_user_mail.html.erb" với nội dung:

<p>Welcome</p> <% @user.mail_address %> <p>to Framgia</p>
<%= link_to "Confirm Account", confirm_mail_address_user_url(@user.confirm_token) %>

Sau đó ta cần config lại file "config/environments/development.rb", thêm một số dòng để có thể sử dụng được ActionMailer:

config.action_mailer.default_url_options = {:host => "localhost:3000"}
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = true
config.action_mailer.perform_caching = false
config.action_mailer.smtp_settings = {
    address: "smtp.gmail.com",
    port: 587,
    domain: "gmail.com",
    authentication: "plain",
    enable_starttls_auto: true,
    user_name: ENV["GMAIL_USERNAME"],
    password: ENV["GMAIL_PASSWORD"]
  }

GMAIL_USERNAME ở đây là địa chỉ gmail của bạn, còn GMAIL_PASSWORD là mật khẩu tài khoản gmail của bạn. Tiếp theo, ta cần 1 function để xác thực confirm_token của người dùng trong "users_controller.rb", và nếu xác thực thành công, cần chuyển sang 1 trang signup_success, còn nếu không chuyển sang trang signup_fail. Thêm vào app/controllers/users_controller.rb :

def confirm_mail_address
    @user = User.find_by confirm_token: params[:id]
    if @user
      @user.update confirm_token: nil, confimed_at: Time.now
      redirect_to signup_success_path
    else
      redirect_to signup_fail_path
    end
  end

  def signup_success
  end

  def signup_fail
  end

Thêm vào app/views/users file "signup_success.html.erb":

Singup success, please login!

Và file "signup_fail.html.erb":

Signup fail !

Sau đó cần chỉnh lại config/routes.rb :

get "/signup_success", to: "users#signup_success"
  get "/signup_fail", to: "users#signup_fail"
  resources :users do
    put :confirm_mail_address, on: :member
  end
end

Để cho hệ thống tự động gửi mail vào địa chỉ mail của người dùng sau khi người đó tạo tài khoản, ta cần có 1 hàm gửi mail được gọi sau khi User được tạo. Thêm vào file "app/models/user.rb":

class User < ActiveRecord::Base
  PARAMS_SIGNUP = [:mail_address, :password, :password_confirmation]
  EMAIL_REGEX = /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i

  validates :mail_address, format: {with: EMAIL_REGEX}, uniqueness: true, presence: true
  validates :password, presence: true, length: {minimum: 6, maximum: 20}

  before_create :generate_confirm_token
  after_create :confirm_mail_address

  has_secure_password

  scope :confirmed, ->{where "`users`.`confirm_token` IS NULL AND `users`.`confirmed_at` < ?", Time.now}

  def reset_confirm_token
    generate_confirm_token
    self.save
  end

  private
  def generate_token column
    begin
      self[column] = SecureRandom.urlsafe_base64
    end while User.exists?(column => self[column])
  end

  def generate_confirm_token
    generate_token :confirm_token
  end

  def confirm_mail_address
    UserMailer.confirm_user_mail(self).deliver_now
  end
end

Ta sẽ gọi đến hàm confirm_mail_address sau khi User được tạo bằng call_back "after_create". Giờ thì bật server local lên và check thử thôi các bạn, đừng quên bật sidekiq cho việc gửi mail nhé 😃