読者です 読者をやめる 読者になる 読者になる

真のRubyistへの道 ~ActionMailer編~

Rails Ruby
どうもこんばんは、先週に引き続き久保田です。


先週のRailsAPIモード」から一週間。。。

次はAction Cableだ!と思っていたのですが、既にやり方は僕より1_000_000倍くらい優秀なRubyistの方々が取り上げていましたので、僕の出番でないなと思いました。

じゃあ何を書こうか、と悩んでいたところ、あることに気づきました。
僕は記事を書けるほどRailsのことを知っているのか、と。

思い返してみれば、Railsが非常に使いやすくわかりやすいフレームワークであるため、
ちゃんと勉強せず使っていました。
どこかで「Railsなんて」と軽視している自分がいたのかもしれません。


こんなことではだめだ。真のRubyistへの道は遠い。
と、感じ、今回からRailsを頭の先から足の先、まで調べ上げて丸裸にしてやろうと思う所存でございます。

というわけで、とりあえずRailsで標準で搭載されているGem達を取り上げて行きたいと思います。

環境は以下の通りです。

Rails 5.0beta3
・ActionMailer 4.1.5

今回は、「Action Mailer」です。

今まで聞いたことだけあって、実際意識して使ったことがないので、今回メールを送ってみたいと思います。


まず、generateします。

bundle exec rails g mailer Greet

色々できましたね。

      create  app/mailers/greet_mailer.rb
      invoke  erb
      create    app/views/greet_mailer
      invoke  test_unit
      create    test/mailers/greet_mailer_test.rb
      create    test/mailers/previews/greet_mailer_preview.rb

Rails Guides によると、mailerはcontrollerみたいなものと書いてあります。
確かにviewがありますね。これを使ってテンプレートを作るみたいです。


さて、早速実装します。
まずは、設定です。メールサーバーなどの設定をしていきます。
今回は、AWSのSESを使用しました。
環境は適宜読み替えてください。


# config/environments/development.rb

config.action_mailer.raise_delivery_errors = true # テスト用にerrorを出す

config.action_mailer.delivery_method = :smtp # smtp

config.action_mailer.smtp_settings = {
  address: '選んだリージョンごとのアドレス',
  port: 587,
  domain: 'gmail.com',
  user_name: ‘SESでsmtpを設定したときに与えられるユーザー名です’,
  password: ‘SESでsmtpを設定したときに与えられるパスワードです’,
}


そして


app/mailers/greet_mailer.rb です。
コントローラーみたいなやつですね。 
ここにメールを送る処理を書きます。

class GreetMailer < ApplicationMailer
  default form: 'test@gmail.com'

  def greet
    mail(to: 'test@gmail.com', subject: 'hello') do |f|
      f.text {render text: 'hello world'}
    end
  end
end


この状態で、rails console からメソッドを呼び出してみましょう。

[1] irb > GreetMailer.greet.deliver


これでできちゃうんですね。
簡単。これは素晴らしいですね!

しかしあまりにも味気ないので、viewを触り、そして添付ファイルをつけてみましょう。

まずはviewを触ってみます。

今回はerbファイルをテンプレートととして使います。htmlでもテキストファイルでも結構です。

htmlならば、タグが使えます。

# app/views/greet_mailer/greet.html.erb
<h1><%=@greet%></h1>


# app/views/greet_mailer/greet.text.erb
テキストならば、インスタンス変数のみを書いておきます。
 
@greet


ではcontrollerから、どのファイルを読むか指定します。

# app/mailers/greet_mailer.rb

class GreetMailer < ApplicationMailer
  default form: 'test@gmail.com'


  def greet

    @greet = “hey!” # ここでインスタンス変数を作成

    mail(to: 'test@gmail.com', subject: 'hello') do |f|
      f.html # htmlを使うときはこちら
      f.text # textを使うときはこちら
    end
  end
end


こちらも非常に簡単ですね。モデルアクセスのロジックを組めばもっと使い勝手が良くなりそうですね。


では次は添付画像をつけてみます。
使うのはattachmentsというメソッドです。

# app/mailers/greet_mailer.rb

class GreetMailer < ApplicationMailer
  default form: 'test@gmail.com'


  def greet

    attachments['ruby.png'] = File.read("tmp/ruby.png")

    @greet = “hey!” # ここでインスタンス変数を作成

    mail(to: 'test@gmail.com', subject: 'hello') do |f|
      f.html # htmlを使うときはこちら
      f.text # textを使うときはこちら
    end
  end
end


こちらも簡単!この簡単さが愛される理由かもしれませんね。

一旦ここまでで終了です。
すごく簡単に、直感的にしかも管理がしやすくメールが送れるので、かなりいいGemだと思います!


さてここからは、少しGemの中を覗いてみましょう。

まずGemの場所を聞いてみます。

bundle show gem名

でGemの場所が分かります。
ちなみに

bundle open gem名 

で該当のGemをeditorで開けます。


今回はメールを送る際のdeliverメソッドの挙動、色々なメール方式の取り扱い方を調べてみたいです。


まず、適当にそれっぽいのを探します。


すると
lib/action_mailer/delivery_methods.rb

に多数のdeliveryが見つかりますね。見てみましょう。

このmoduleはどうやら、色々なメールの方式が定義されているみたいですね。
初期設定を色々やってますね。
class_attributeやcater_accessorで変数やアクセサを定義し、
他のクラスからincludeされた時に設定をそのクラスからも呼べるようにしていますね。
おそらくここの設定を他のクラスがincludeして使う感じになるのでしょう。いいですね。

そして、おそらく重要なのは、「add_deliver_method」でしょう。このメソッドが各メール方式をうまく閉じ込めていそうです。

# lib/action_mailer/delivery_methods.rb

def add_delivery_method(symbol, klass, default_options={})
  class_attribute(:"#{symbol}_settings") unless respond_to?(:"#{symbol}_settings")
  send(:"#{symbol}_settings=", default_options)
  self.delivery_methods = delivery_methods.merge(symbol.to_sym => klass).freeze
end


ClassMethodsモジュールに入っているので、DeliveryMehtodsのクラス変数になっていますね。
そして呼び出し側を見てみると、カッコがないのでわかりにいくですが、
第一引数で方式を、第二引数でクラスを渡すことで、メール方式名のclass_attributesを定義すると同時に、
delivery_methodsというクラス変数にペアにして登録しています。
そして、第三引数でハッシュを渡すことでメール方式名のclass変数に初期設定がなされていますね。

      add_delivery_method :smtp, Mail::SMTP,
        address:              "localhost",
        port:                 25,
        domain:               'localhost.localdomain',
        user_name:            nil,
        password:             nil,
        authentication:       nil,
        enable_starttls_auto: true


おそらくdeliverメソッドは、このmoduleで定義される数々の設定から、configで設定されたメール方式の設定を受け取り、どれを使うかを選び、メールを送っているのでしょう。
 
このmoduleを拡張すれば、独自のメール方式も定義できそうですね。



さてでは実際メールを送っている処理が見てみたいものです。

引き続きdeliverを探してみると、
def deliverというものはどうやらないらしいです。しかし近いものは見つけました。

MessageDeliveryクラス(lib/action_mailer/message_delivery.rb)です。
この中を見てみると、
deliver_now, deliver_later
というメソッドがありました。

そしてそのなかでmessageに対してdeliveryを呼んでいる。。。

def deliver_now
      message.deliver
end
 
もしやと思い、このmessageを見てみると、

書いてありますね。


    # Returns the Mail::Message object
    def message
      __getobj__
    end


で、少し長くなってきたので割愛しますが、ここからすすんでいくと最終的にActionMailer::Baseクラスで、Mailオブジェクトをリターンしていました。

このMessageDeliveryクラス自体はActionMailer::Baseのmethod_missingでインスタンスが作られており、さらにその中でmessageとしてMailオブジェクトが作られ、最終的にMailオブジェクトに対して、deliverが呼ばれていました。
 
MessageDeliveryクラスの上の方にもコメントでこのクラスはMail::Messageのラッパーだよって言ってますね。


これ以上はMailとの連携の部分なので、今度Mailを読んだ時にまた見てみようと思います。


今回は以上です。
次はまた違うGemを見てみたいと思います!