1 Wstęp
Moduł Action Mailer umożliwia wysyłanie e-maili z Twojej aplikacji przy użyciu modelu programu pocztowego i jego widoków. Tak więc, w Rails, e-maile są wykorzystywane przez tworzenie modeli, które dziedziczą z ActionMailer::Base, które to istnieją obok innych modeli w folderze app/models. Modele te posiadają skojarzone widoki, które to są wyświetlane obok widoków kontrolera w folderze app/views.
2 Wysyłanie e-maili
W tej sekcji omówimy krok po kroku tworzenie mailera i jego widoków.
2.1 Wprowadzenie do Generowania Mailera
2.1.1 Tworzenie modułu Action Mailer
./script/generate mailer UserMailer
exists app/models/
create app/views/user_mailer
exists test/unit/
create test/fixtures/user_mailer
create app/models/user_mailer.rb
create test/unit/user_mailer_test.rb
Mamy więc takie elementy jak model, dodatki oraz testy.
2.1.2 Edycja Modelu
Plik app/models/user_mailer.rb zawiera pusty mailer:
class UserMailer < ActionMailer::Base
end
Dodajmy metodę nazywaną welcome_email, która wyśle e-mail na zarejestrowany adres e-mail użytkownika:
class UserMailer < ActionMailer::Base
def welcome_email(user)
recipients user.email
from "My Awesome Site Notifications <notifications@example.com>"
subject "Welcome to My Awesome Site"
sent_on Time.now
body( {:user => user, :url => "http://example.com/login"})
end
end
Oto krótkie wyjaśnienie opcji przedstawionych w poprzedniej metodzie. Aby uzyskać pełną listę dostępnych opcji, należy spojrzeć w dół, w pełną listę modułu Action Mailer (Complete List of Action Mailer) pokazującą ustawiane przez użytkownika atrybuty sekcji.
recipients | e-mail odbiorcy. Może to być łańcuch znaków lub jeśli istnieje wielu odbiorców, tablica łańcuchów znaków |
from | z jakiego adresu pochodzi email |
subject | temat e-maila |
sent_on | znacznik czasu e-maila |
Klucze hasha przekazywane do body stają się zmiennymi instancji w widoku. Zatem w naszym przykładzie, widok mailera będzie miał je dostępne jako zmienne instancji @user i @url.
2.1.3 Tworzenie widoku Mailera
Stwórz plik o nazwie welcome_email.text.html.erb w folderze app/views/user_mailer/. Będzie to szablon używany do wiadomości e-mail w formacie HTML:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
</head>
<body>
<h1>Welcome to example.com, <%=h @user.first_name %></h1>
<p>
You have successfully signed up to example.com, and your username is: <%= @user.login %>.<br/>
To login to the site, just follow this link: <%=h @url %>.
</p>
<p>Thanks for joining and have a great day!</p>
</body>
</html>
Gdybyśmy chcieli wysłać tylko tekst wiadomości e-mail, plik byłby nazwany welcome_email.text.plain.erb.
Railsy ustawiają typ zawartości wiadomości e-mail jako ten używany w nazwie pliku.
2.1.4 Włączanie opcji wysyłającej e-maila z powiadomieniem, w momencie gdy użytkownik się zarejestruje
Istnieją trzy sposoby osiągnięcia tego celu. Jednym z nich jest wysłanie e-maila od kontrolera, który wysyła wiadomości e-mail. Kolejnym jest umieszczenie w modelu użytkownika, e-maila zwrotnego w metodzie before_create. Natomiast ostatnim rozwiązaniem jest wykorzystanie obserwatora w modelu użytkownika. To czy korzystasz z drugiej, czy trzeciej metody zależy od Ciebie, jednak korzystanie z pierwszego rozwiązania nie jest zalecane. Nie dlatego, że jest ono złe, lecz dlatego, że drugie i trzecie rozwiązanie utrzymuje kontroler czysty i zachowuje wszystkie logiczne powiązania z modelem użytkownika. W ten sposób, niezależnie od sposobu tworzenia użytkownika (np. poprzez formularz na stronie www lub wywołania API) mamy zagwarantowane, że e-mail zostanie wysłany.
Zobaczmy jak pójdzie nam powiązanie go za pomocą obserwatora:
W pliku config/environment.rb:
Rails::Initializer.run do |config|
# ...
config.active_record.observers = :user_observer
end
Możesz umieścić obserwatora w folderze app/models, gdzie zostanie on załadowany automatycznie przez Rails.
Teraz należy stworzyć plik o nazwie user_observer.rb w folderze app/models w zależności od miejsca gdzie go zapisałeś. Plik ten powinien wyglądać następująco:
class UserObserver < ActiveRecord::Observer
def after_create(user)
UserMailer.deliver_welcome_email(user)
end
end
Zauważ, jak nazywamy metodę deliver_welcome_email? W module Action Mailer wysyłamy e-maile za pomocą wywołania deliver_<method_name>. W module User Mailer, zdefiniowaliśmy metodę zwaną welcome_email, a więc dostarczamy wiadomości przez wywołanie metody deliver_welcome_email. W następnej sekcji przeanalizujemy jak Action Mailer jest w stanie to zrobić.
2.2 Action Mailer oraz metody dynamiczne deliver_<method_name>
W jaki więc sposób Action Mailer rozumie wywołanie deliver_welcome_email ? Jeśli przeczytasz dokumentację (http://api.rubyonrails.org/files/vendor/rails/actionmailer/README.html), to w sekcji “Sending Emails” znajdziesz wszelkie potrzebne informacje:
Twoje metody dostarczania instancji są zazwyczaj automatycznie opakowane w metodach klas, zaczynających się od słowa deliver_, po którym następuje nazwa metody mailera, którą chcesz wykonać.
Zatem, jak dokładnie to działa?
Patrząc na źródło ActionMailer::Base znajdziesz:
def method_missing(method_symbol, *parameters)#:nodoc:
case method_symbol.id2name
when /^create_([_a-z]\w*)/ then new($1, *parameters).mail
when /^deliver_([_a-z]\w*)/ then new($1, *parameters).deliver!
when "new" then nil
else super
end
end
Tak więc, jeśli nazwa metody rozpoczyna się od deliver_ po dowolnej kombinacji małych liter lub podkreślenia, to metoda method_missing wywołuje metodę new na Twojej klasie mailera (w naszym przykładzie powyżej jest to UserMailer), wysyłając kombinację małych liter lub podkreślenia wraz z parametrami. Obiekt jest następnie przesyłany metodą deliver!, która to… go dostarcza.
2.3 Pełna lista atrybutów ustawialnych przez użytkownika modułu Action Mailer
bcc | Adresy BCC adresów e-mail, występują albo jako ciąg znaków (jeden adres) lub tablica łańcuchów znaków (do wielu adresatów) |
body | Część body adresu e-mail. Jest to albo tablica asocjacyjna (w tym przypadku określa ona zmienne przekazywane do szablonu podczas renderingu) lub ciąg znaków, w którym to przypadku określa on aktualną zawartość części body wiadomości e-mail |
cc | Adresy CC dla poczty elektronicznej, reprezentowane jako ciąg znaków (jeden adres) lub jako tablica łańcuchów znaków (do wielu adresatów) |
charset | Charset dla wiadomości e-mail. Ustawienia domyśle default_charset określone dla ActionMailer::Base. |
content_type | Typ zawartości (content type) wiadomości email. Domyślnie ustawiony jako “text/plain”, ale nazwa pliku może go określać |
from | Z jakiego adresu e-mail nadano wiadomość |
reply_to | Adres, na który bezpośrednio będzie przesłana odpowiedź (jeśli jest on inny niż adres “from”) |
headers | Dodatkowe nagłówki, które będą dodane do wiadomości e-mail |
implicit_parts_order | Kolejność, w jakiej części powinny być posortowane na podstawie typu zawartości (content type). Domyślna wartość to default_implicit_parts_order |
mime_version | Domyślna wersja mime to “1.0”, ale może zostać podana w razie wyraźnej potrzeby |
recipient | Odbiorca wiadomości e-mail, reprezentowany jako ciąg znaków( gdy jest to jeden adres) lub jako tablica łańcuchów znaków (w przypadku wielu adresów) |
sent_on | Znacznik czasu, w którym wiadomość została wysłana. Jeśli nie został on ustawiony, nagłówek zostanie ustalony przez dostarczyciela |
subject | Temat wiadomości e-mail |
template | Szablon możliwy do użycia. Jest to “podstawowa” nazwa szablonu, bez rozszerzenia lub katalogu, może być użyta do wielu metod mailera tego samego szablonu |
2.3.1 Making Inline Attachments
Inline attachments are now possible in ActionMailer. While previously in the pre 3.0 version of Rails, you could do inline attachments, it involved a lot of hacking and determination to pull it off.
ActionMailer now makes inline attachments as trivial as they should be.
- Firstly, to tell Mail to turn an attachment into an inline attachment, you just call #inline on the attachments method within your Mailer:
def welcome
attachments.inline['image.jpg'] = File.read('/path/to/image.jpg')
end
- Then in your view, you can just reference attachments[] as a hash and specify which attachment you want to show, calling url on it and then passing the result into the image_tag method:
<p>Hello there, this is our image</p>
<%= image_tag attachments['image.jpg'].url %>
- As this is a standard call to image_tag you can pass in an options hash after the attachment url as you could for any other image:
<p>Hello there, this is our image</p>
<%= image_tag attachments['image.jpg'].url, :alt => 'My Photo',
:class => 'photos' %>
2.4 Widoki Mailera
Widoki Mailera znajdują się w katalogu app/views/name_of_mailer_class. Konkretny widok mailera jest znany klasie, ponieważ jego nazwa jest taka sama jak metoda mailera. Na przykład, w naszym przykładzie u góry, nasz widok mailera dla metody welcome_email znajduje się w pliku app/views/user_mailer/welcome_email.text.html.erb dla wersji HTML oraz dla wersji tekstowej w welcome_email.text.plain.erb.
Aby zmienić domyślny widok mailera powinieneś zrobić coś takiego:
class UserMailer < ActionMailer::Base
def welcome_email(user)
recipients user.email
from "My Awesome Site Notifications<notifications@example.com>"
subject "Welcome to My Awesome Site"
sent_on Time.now
body( {:user => user, :url => "http://example.com/login"})
content_type "text/html"
# use some_other_template.text.(html|plain).erb instead
template "some_other_template"
end
end
2.5 Layouty Action Mailera
Podobnie jak w widokach kontrolera możemy posiadać layouty mailera. Nazwa layoutu musi kończyć się końcówką “_mailer”, aby być automatycznie rozpoznawalną przez mailera jako layout. Tak więc w naszym przykładzie UserMailer, musimy odwołać się do naszego layoutu user_mailer.text.(html|plain).erb. Aby korzystać z innego pliku, użyj:
class UserMailer < ActionMailer::Base
layout 'awesome' # use awesome.text.(html|plain).erb as the layout
end
Podobnie jak w przypadku kontrolera widoku, należy użyć metody yield, aby wyrenderować widok wewnątrz layoutu.
2.6 Generowanie adresów URL w widokach Action Mailer
Adresy URL mogą być generowane przy użyciu metody url_for lub nazwanej ścieżki. W przeciwieństwie do kontrolerów, zapytanie mailera nie ma żadnego związku z przychodzącym żądaniem, dlatego musimy podać takie dane jak :host, :controller, i :action:.
<%= url_for(:host => "example.com", :controller => "welcome", :action => "greeting") %>
Przy użyciu nazwanych ścieżek potrzebujemy dodać tylko :host:
<%= users_url(:host => "example.com") %>
Klienci e-mail nie mają żadnego związku z adresami internetowymi, dlatego też ścieżki nie mają bazowego adresu URL do utworzenia kompletnych adresów internetowych. Tak więc podczas korzystania z ścieżek sens ma tylko wariant “_url”.
Jest także możliwość ustawienia domyślnego hosta, który będzie stosowany we wszystkich podmiotach wysyłających poprzez użycie opcji :host w hashu ActionMailer::Base.default_url_options tak jak poniżej:
ActionMailer::Base.default_url_options[:host] = "example.com"
Możemy to również ustawić jako opcję w konfiguracji config/environment.rb:
config.action_mailer.default_url_options = { :host => "example.com" }
W przypadku domyślnego ustawienia :host dla wysyłanych wiadomości należy dodać :only_path => false do url_for. W przeciwnym razie nasze ustawienia nie zostaną uwzględnione.
2.7 Wysyłanie wieloczęściowych wiadomości e-mail
Action Mailer automatycznie wyśle wieloczęściowe wiadomości e-mail, jeśli wykorzystasz różne szablony dla tych samych akcji. Tak więc dla naszego przykładu UserMailer, jeśli pliki welcome_email.text.plain.erb i welcome_email.text.html.erb znajdują się w katalogu app/views/user_mailer, to Action Mailer automatycznie wyśle wieloczęściową wiadomość e-mail jako wersję HTML i wersję tekstową w dwóch różnych częściach.
Aby jawnie określić wieloczęściowe wiadomości, możesz zrobić coś takiego:
class UserMailer < ActionMailer::Base
def welcome_email(user)
recipients user.email_address
subject "New account information"
from "system@example.com"
content_type "multipart/alternative"
part :content_type => "text/html",
:body => "<p>html content, can also be the name of an action that you call<p>"
part "text/plain" do |p|
p.body = "text content, can also be the name of an action that you call"
end
end
end
2.8 Wysyłanie e-maili z załącznikami
Załączniki mogą być dodawane za pomocą metody attachment :
class UserMailer < ActionMailer::Base
def welcome_email(user)
recipients user.email_address
subject "New account information"
from "system@example.com"
content_type "multipart/alternative"
attachment :content_type => "image/jpeg",
:body => File.read("an-image.jpg")
attachment "application/pdf" do |a|
a.body = generate_your_pdf_here()
end
end
end
2.9 Wysyłanie wieloczęściowych wiadomości e-mail z załącznikami
Po zastosowaniu metody attachment moduł Action Mailer nie będzie automatycznie używać odpowiedniego szablonu na podstawie nazwy pliku, ani nie będzie prawidłowo porządkował alternatywnych części. Aby to zrobić, musimy zadeklarować, których szablonów używamy do każdego typu zawartości poprzez zastosowanie metody part. W dodatku musimy zadeklarować je w odpowiedniej kolejności.
W poniższym przykładzie, byłyby dwa pliki szablonów, welcome_email_html.erb oraz welcome_email_plain.erb w folderze app/views/user_mailer. Część text/plain musi być wymieniona jako pierwsza oraz w pełni kompatybilna z klientem e-mail. Jeśli text/plain znajduje się na liście po text/html, niektórzy klienci mogą wyświetlać wiadomość zarówno w wersji HTML jak i jako zwykły tekst wiadomości e-mail. Każdy alternatywny tekst musi być zamknięty w części multipart/alternative. Nie przypisuj całej zawartości wiadomości content_type do części multipart/alternative. Warto również nadmienić, iż niektóre rodzaje klientów e-mail mogą ignorować wyświetlanie załączników w formacie PDF.
class UserMailer < ActionMailer::Base
def welcome_email(user)
recipients user.email_address
subject "New account information"
from "system@example.com"
part "multipart/alternative" do |pt|
pt.part "text/plain" do |p|
p.body = render_message("welcome_email_plain", :message => "text content")
end
pt.part "text/html" do |p|
p.body = render_message("welcome_email_html", :message => "<h1>HTML content</h1>")
end
end
attachment :content_type => "image/jpeg",
:body => File.read("an-image.jpg")
attachment "application/pdf" do |a|
a.body = generate_your_pdf_here()
end
end
end
3 Odbieranie wiadomości e-mail
Odbieranie i przetwarzanie e-maili z modułu Action Mailer może być dość skomplikowanym i trudnym przedsięwzięciem. Nim e-mail dociera do aplikacji Rails, musimy skonfigurować system, aby w jakiś sposób wiadomości były przekazywane do aplikacji. Tak więc, aby otrzymywać wiadomości w aplikacji Rails, musimy:
1. Zaimplementować metodę receive w mailerze.
2. Skonfigurować nasz serwer pocztowy do przekazywania wiadomości e-mail z adresu(ów), które chcesz aby aplikacja otrzymywała /path/to/app/script/runner 'UserMailer.receive(STDIN.read)'.
Gdy metoda receive jest już zdefiniowana w każdym mailerze, moduł Action Mailer przetworzy surową wiadomość na obiekt e-mail, zdekoduje go, utworzy nowy mailer i e-mail oraz przekaże obiekt do instancji metody receive naszego mailera.
Oto przykład:
class UserMailer < ActionMailer::Base
def receive(email)
page = Page.find_by_address(email.to.first)
page.emails.create(
:subject => email.subject,
:body => email.body
)
if email.has_attachments?
for attachment in email.attachments
page.attachments.create({
:file => attachment,
:description => email.subject
})
end
end
end
end
4 Korzystanie z helperów modułu Action Mailer
Action Mailer ma 4 dostępne klasy metod pomocniczych:
add_template_helper(helper_module) | Tworzy wszystkie (instancje) metody w module pomocny dla dostępnych szablonów wykonywanych przez ten kontroler |
helper(*args, &block) | Deklarowanie helpera: helper :foo wymaga ‘foo_helper’ i zawiera FooHelper w szablonie klasy. Helper { def foo() “#{bar} is the very best” end } tworzy blok w szablonie klasy, dodając metodę foo. helper(:three, BlindHelper) { def mice() ‘mice’ end } tworzy natomiast wszystkie trzy. |
helper_method | Deklarowanie metody kontrolera jako helpera. Na przykład, helper_method: link_to def link_to (nazwa, opcje) … end tworzy link_to metody kontrolera dostępnego w widoku. |
helper_attr | Deklarowanie atrybutów kontrolera jako helpera. Na przykład, helper_attr :name attr_accessor :name sprawia, że :name i name w metodzie kontrolera umożliwia dostęp do widoku. Jest to wygodny wrapper dla helper_method. |
5 Action Mailer – konfiguracja
Następujące opcje konfiguracyjne są najlepiej wykonane w jednym z plików środowiskowych (environment.rb, production.rb, itp. ..)
template_root | Określa bazę szablonów odniesienia, z których będą one pochodzić |
logger | Logger służy do generowania informacji o uruchomionym mailingu jeśli wiadomości są dostępne. Może on być ustawiony na zero bez logowania. Jest kompatybilny z obydwoma loggerami własnymi Ruby jak i z loggerami Log4r. |
smtp_settings | Umożliwia szczegółową konfigurację dla metody :smtp delivery method: :address – metoda ta pozwala korzystać ze zdalnego serwera poczty. Wystarczy zmienić domyślne ustawienie “localhost”. :port – W przypadku, gdy twój serwer nie działa na porcie 25 możesz go zmienić. :domain – Jeśli musisz określić domenę HELO, możesz zrobić to tutaj. :user_name – Jeśli serwer poczty wymaga uwierzytelniania należy ustawić jego nazwę w tym ustawieniu. :password – Jeśli serwer poczty wymaga uwierzytelniania należy ustawić hasło w tym ustawieniu. :authentication – Jeśli serwer poczty wymaga uwierzytelniani należy określić typ uwierzytelniania tutaj. Jest to jeden z symboli :plain, :login lub :cram_md5. |
sendmail_settings | Pozwala na nadpisanie opcji dla metody dostawy :sendmail. :location – lokacja wykonywania sendmail. Domyślnie w folderze /usr/sbin/sendmail. :arguments – argumenty wiersza poleceń. Domyślnie ustawione z -i -t. |
raise_delivery_errors | Ustawienie decydujące o tym, czy ilość błędów powinna być zwiększona, jeżeli e-mail nie zostanie dostarczony. |
delivery_method | Określa metody dostawy. Możliwe wartości to :smtp (domyślnie), :sendmail oraz :test. |
perform_deliveries | Określa, czy metody deliver_* są rzeczywiście realizowane. Domyślnie są one włączone, ale można je wyłączyć w celu testowania funkcjonalnego. |
deliveries | Trzymają tablicę wszystkich e-maili wysyłanych za pośrednictwem modułu Action Mailer z delivery_method :test. Najbardziej przydatne dla jednego elementu i testowania funkcjonalnego. |
default_charset | Domyślne ustawienie kodowania dla części body oraz odkodowania tematu wiadomości e-mail. Domyślnie jest to kodowanie UTF-8. Możemy również wybrać inne kodowanie. |
default_content_type | Domyślny typ zawartości używany dla głównej części wiadomości. Domyślnie jest on ustawiony na “text/plain”. Można również wybrać inny typ zawartości z wnętrza metody content_type. |
default_mime_version | Domyślna wersja mime wiadomości. Domyślnie ustawiona na 1.0. Można również wybrać inną wartość z wnętrza metody mime_version. |
default_implicit_parts_order | Gdy wiadomość zbudowana została w sposób domyślny, bezwarunkowy (np. różnorodne części są złożone z szablonów które określają typ zawartości w ich nazwach) zmienna ta kontroluje to jak ułożone zostaną te części. Domyślnie ustawione zostaną w takiej kolejności: [“text/html”, “text/enriched”, “text/plain”]. Elementy, które pojawiają się jako pierwsze w tablicy, posiadają wyższy priorytet w kliencie poczty e-mail i pojawiają się na końcu w komunikacie zakodowanym mime. Można również wybrać inny układ z wnętrza metody implicit_parts_order. |
5.1 Przykład konfiguracji Action Mailera
Przykładem może być:
ActionMailer::Base.delivery_method = :sendmail
ActionMailer::Base.sendmail_settings = {
:location => '/usr/sbin/sendmail',
:arguments => '-i -t'
}
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.raise_delivery_errors = true
ActionMailer::Base.default_charset = "iso-8859-1"
5.2 Action Mailer – konfiguracja Gmail
Instrukcja skopiowana z tego wpisu na blogu utworzonego przez Roberta Zottera.
Najpierw należy zainstalować plugin action_mailer_tls. Następnie wszystko co musisz zrobić, to skonfigurować moduł Action Mailer w następujący sposób:
ActionMailer::Base.smtp_settings = {
:address => "smtp.gmail.com",
:port => 587,
:domain => "domain.com",
:user_name => "user@domain.com",
:password => "password",
:authentication => :plain
}
5.3 Skonfiguruj moduł Action Mailer, aby rozpoznać szablony Haml
W pliku config/environment.rb, należy dodać następujący wiersz:
ActionMailer::Base.register_template_extension('haml')
6 Testowanie Mailera
Domyślnie Action Mailer nie wysyła e-maili w środowisku testowym. Są one po prostu dodawane do tablicy ActionMailer::Base.deliveries.
Testowanie mailera obejmuje zazwyczaj dwie rzeczy: Jedna, to informacja, że mail został w kolejce, a druga, że email jest poprawny. Mając to na uwadze, możemy przetestować nasz przykład z góry, tak jak poniżej:
class UserMailerTest < ActionMailer::TestCase
def test_welcome_email
user = users(:some_user_in_your_fixtures)
# Send the email, then test that it got queued
email = UserMailer.deliver_welcome_email(user)
assert !ActionMailer::Base.deliveries.empty?
# Test the body of the sent email contains what we expect it to
assert_equal [user.email], email.to
assert_equal "Welcome to My Awesome Site", email.subject
assert_match /Welcome to example.com, #{user.first_name}/, email.body
end
end
W teście mamy wysłać e-maila i zapisać zwrócony obiekt w zmiennej email. Po czym możemy upewnić się, że wiadomość została wysłana (pierwsze potwierdzenie), następnie stwierdzamy, czy e-mail rzeczywiście zawiera to, czego się spodziewaliśmy.