Sending Email with Rails

8,372 views
8,191 views

Published on

This was the eight speech of a three day Rails training I gave in Tulsa, OK in the spring 2010.

Published in: Technology
0 Comments
7 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
8,372
On SlideShare
0
From Embeds
0
Number of Embeds
42
Actions
Shares
0
Downloads
180
Comments
0
Likes
7
Embeds 0
No embeds

No notes for slide























































































































  • Sending Email with Rails

    1. 1. Sending Email With Callbacks A look at ActionMailer and the ActiveRecord life cycle
    2. 2. ActionMailer The email library that comes with Rails
    3. 3. Mailers
    4. 4. Mailers Mailers can be used to send email from Rails
    5. 5. Mailers Mailers can be used to send email from Rails Supports plain text, HTML, and multi-part emails
    6. 6. Mailers Mailers can be used to send email from Rails Supports plain text, HTML, and multi-part emails Supports attachments
    7. 7. Mailers Mailers can be used to send email from Rails Supports plain text, HTML, and multi-part emails Supports attachments Multiple send modes including: sendmail and SMTP
    8. 8. Mailers Mailers can be used to send email from Rails Supports plain text, HTML, and multi-part emails Supports attachments Multiple send modes including: sendmail and SMTP Mailers can also be used to receive emails
    9. 9. Mailers Mailers can be used to send email from Rails Supports plain text, HTML, and multi-part emails Supports attachments Multiple send modes including: sendmail and SMTP Mailers can also be used to receive emails Parses the email data into a Ruby object
    10. 10. M and V, Without the C
    11. 11. M and V, Without the C Mailer structure is a bit different than other parts of Rails
    12. 12. M and V, Without the C Mailer structure is a bit different than other parts of Rails It’s a model
    13. 13. M and V, Without the C Mailer structure is a bit different than other parts of Rails It’s a model But it has views
    14. 14. M and V, Without the C Mailer structure is a bit different than other parts of Rails It’s a model But it has views You can think of it as rendering content for the user, just not through a browser
    15. 15. Email Example
    16. 16. Email Example Let’s say I have an application built that requires users to login to see any content
    17. 17. Email Example Let’s say I have an application built that requires users to login to see any content I have created a User model and built the controller that allows them to sign-up
    18. 18. Email Example Let’s say I have an application built that requires users to login to see any content I have created a User model and built the controller that allows them to sign-up I have also wired up a login system using Authlogic
    19. 19. Email Example Let’s say I have an application built that requires users to login to see any content I have created a User model and built the controller that allows them to sign-up I have also wired up a login system using Authlogic The whole thing works now
    20. 20. A Problem
    21. 21. A Problem I really need to make sure users give me a valid email
    22. 22. A Problem I really need to make sure users give me a valid email Authlogic checks the email address format, but you can only know an email is valid by sending a message
    23. 23. Adding Authentication
    24. 24. Adding Authentication When a user signs up:
    25. 25. Adding Authentication When a user signs up: We will send them an email with a special link in it
    26. 26. Adding Authentication When a user signs up: We will send them an email with a special link in it Clicking that link will authenticate their address
    27. 27. Adding Authentication When a user signs up: We will send them an email with a special link in it Clicking that link will authenticate their address We won’t allow non-authenticated users access to the site
    28. 28. Adding Authentication When a user signs up: We will send them an email with a special link in it Clicking that link will authenticate their address We won’t allow non-authenticated users access to the site After authentication, their account will work normally
    29. 29. Generate a Mailer
    30. 30. Generate a Mailer We call script/ generate as usual $ ruby script/generate mailer user_notifier activation exists app/models/ create app/views/user_notifier exists test/unit/ create test/fixtures/user_notifier create app/models/user_notifier.rb create test/unit/user_notifier_test.rb create app/views/user_notifier/activation.erb create test/fixtures/user_notifier/activation
    31. 31. Generate a Mailer We call script/ generate as usual We ask for a mailer $ ruby script/generate mailer user_notifier activation exists app/models/ and name it create app/views/user_notifier exists test/unit/ create test/fixtures/user_notifier user_notifier create app/models/user_notifier.rb create test/unit/user_notifier_test.rb create app/views/user_notifier/activation.erb create test/fixtures/user_notifier/activation
    32. 32. Generate a Mailer We call script/ generate as usual We ask for a mailer $ ruby script/generate mailer user_notifier activation exists app/models/ and name it create app/views/user_notifier exists test/unit/ create test/fixtures/user_notifier user_notifier create app/models/user_notifier.rb create test/unit/user_notifier_test.rb create app/views/user_notifier/activation.erb create test/fixtures/user_notifier/activation Optionally, we can also pass the names of emails to create
    33. 33. app/models/user_notifier.rb The generated mailer gets us started
    34. 34. class UserNotifier < ActionMailer::Base def activation(sent_at = Time.now) subject 'UserNotifier#activation' recipients '' from '' sent_on sent_at body :greeting => 'Hi,' end end app/models/user_notifier.rb The generated mailer gets us started
    35. 35. class UserNotifier < ActionMailer::Base def activation(sent_at = Time.now) subject 'UserNotifier#activation' recipients '' from '' sent_on sent_at body :greeting => 'Hi,' end end app/models/user_notifier.rb The generated mailer gets us started
    36. 36. class UserNotifier < ActionMailer::Base def activation(sent_at = Time.now) subject 'UserNotifier#activation' recipients '' from '' sent_on sent_at body :greeting => 'Hi,' end end app/models/user_notifier.rb The generated mailer gets us started
    37. 37. class UserNotifier < ActionMailer::Base def activation(sent_at = Time.now) subject 'UserNotifier#activation' recipients '' from '' sent_on sent_at body :greeting => 'Hi,' end end app/models/user_notifier.rb The generated mailer gets us started
    38. 38. class UserNotifier < ActionMailer::Base def activation(sent_at = Time.now) subject 'UserNotifier#activation' recipients '' from '' sent_on sent_at body :greeting => 'Hi,' end end app/models/user_notifier.rb The generated mailer gets us started
    39. 39. Customized to our Needs We will work with a User since that makes sense for what we are trying to do
    40. 40. class UserNotifier < ActionMailer::Base def activation(user) subject 'Activate Your Account' recipients user.email from 'admin@secureapp.com' sent_on Time.now body :user => user end end Customized to our Needs We will work with a User since that makes sense for what we are trying to do
    41. 41. class UserNotifier < ActionMailer::Base def activation(user) subject 'Activate Your Account' recipients user.email from 'admin@secureapp.com' sent_on Time.now body :user => user end end Customized to our Needs We will work with a User since that makes sense for what we are trying to do
    42. 42. class UserNotifier < ActionMailer::Base def activation(user) subject 'Activate Your Account' recipients user.email from 'admin@secureapp.com' sent_on Time.now body :user => user end end Customized to our Needs We will work with a User since that makes sense for what we are trying to do
    43. 43. class UserNotifier < ActionMailer::Base def activation(user) subject 'Activate Your Account' recipients user.email from 'admin@secureapp.com' sent_on Time.now body :user => user end end Customized to our Needs We will work with a User since that makes sense for what we are trying to do
    44. 44. The Email Content This is the code from app/views/user_notifier/activation.erb
    45. 45. Welcome to the Secure Application. Please click the following link to activate your account: <%= activate_url(:token => @user.perishable_token, :host => "localhost:3000") %> The Email Content This is the code from app/views/user_notifier/activation.erb
    46. 46. Welcome to the Secure Application. Please click the following link to activate your account: <%= activate_url(:token => @user.perishable_token, :host => "localhost:3000") %> The Email Content This is the code from app/views/user_notifier/activation.erb
    47. 47. Welcome to the Secure Application. Please click the following link to activate your account: <%= activate_url(:token => @user.perishable_token, :host => "localhost:3000") %> The Email Content This is the code from app/views/user_notifier/activation.erb
    48. 48. Welcome to the Secure Application. Please click the following link to activate your account: <%= activate_url(:token => @user.perishable_token, :host => "localhost:3000") %> The Email Content This is the code from app/views/user_notifier/activation.erb
    49. 49. Sending an Email
    50. 50. Sending an Email You can send an email from anywhere in the application
    51. 51. Sending an Email You can send an email from anywhere in the application UserNotifier.deliver_activation(user) Just call deliver_EMAIL() where EMAIL is the name of the message
    52. 52. Mailers in Production
    53. 53. Mailers in Production By default, ActionMailer will try to use sendmail to deliver emails in production
    54. 54. Mailers in Production By default, ActionMailer will try to use sendmail to deliver emails in production This works on a lot of servers but is not robust
    55. 55. Mailers in Production By default, ActionMailer will try to use sendmail to deliver emails in production This works on a lot of servers but is not robust I recommend setting up a Gmail account and configuring ActionMailer to send via SMTP
    56. 56. Mailers in Production By default, ActionMailer will try to use sendmail to deliver emails in production This works on a lot of servers but is not robust I recommend setting up a Gmail account and configuring ActionMailer to send via SMTP You may also wish to shut off ActionMailer’s default error raising behavior
    57. 57. Callbacks Taking actions during the ActiveRecord life cycle
    58. 58. The ActiveRecord Life Cycle
    59. 59. The ActiveRecord Life Cycle Models have a life cycle
    60. 60. The ActiveRecord Life Cycle Models have a life cycle They are created
    61. 61. The ActiveRecord Life Cycle Models have a life cycle They are created Read from the database
    62. 62. The ActiveRecord Life Cycle Models have a life cycle They are created Read from the database Updated
    63. 63. The ActiveRecord Life Cycle Models have a life cycle They are created Read from the database Updated Destroyed
    64. 64. The ActiveRecord Life Cycle Models have a life cycle They are created Read from the database Updated Destroyed Callbacks allow us to run code at points in this cycle
    65. 65. The Callback Hooks
    66. 66. The Callback Hooks after_initialize*
    67. 67. The Callback Hooks after_initialize* before_save
    68. 68. The Callback Hooks after_initialize* before_save before_create/update
    69. 69. The Callback Hooks after_initialize* before_save before_create/update before_validation
    70. 70. The Callback Hooks after_initialize* before_save before_create/update before_validation before_validation_on_ create/update
    71. 71. The Callback Hooks after_initialize* before_save before_create/update before_validation before_validation_on_ create/update after_validation
    72. 72. The Callback Hooks after_initialize* after_validation_on_cr eate/update before_save before_create/update before_validation before_validation_on_ create/update after_validation
    73. 73. The Callback Hooks after_initialize* after_validation_on_cr eate/update before_save after_save before_create/update before_validation before_validation_on_ create/update after_validation
    74. 74. The Callback Hooks after_initialize* after_validation_on_cr eate/update before_save after_save before_create/update after_create/update before_validation before_validation_on_ create/update after_validation
    75. 75. The Callback Hooks after_initialize* after_validation_on_cr eate/update before_save after_save before_create/update after_create/update before_validation after_find* before_validation_on_ create/update after_validation
    76. 76. The Callback Hooks after_initialize* after_validation_on_cr eate/update before_save after_save before_create/update after_create/update before_validation after_find* before_validation_on_ create/update before_destroy after_validation
    77. 77. The Callback Hooks after_initialize* after_validation_on_cr eate/update before_save after_save before_create/update after_create/update before_validation after_find* before_validation_on_ create/update before_destroy after_validation after_destroy
    78. 78. Building a Callback Just choose the type of callback, name a method, and write a matching Ruby method
    79. 79. class User < ActiveRecord::Base acts_as_authentic after_create :send_activation_email def send_activation_email reset_perishable_token! UserNotifier.deliver_activation(self) end end Building a Callback Just choose the type of callback, name a method, and write a matching Ruby method
    80. 80. class User < ActiveRecord::Base acts_as_authentic after_create :send_activation_email def send_activation_email reset_perishable_token! UserNotifier.deliver_activation(self) end end Building a Callback Just choose the type of callback, name a method, and write a matching Ruby method
    81. 81. class User < ActiveRecord::Base acts_as_authentic after_create :send_activation_email def send_activation_email reset_perishable_token! UserNotifier.deliver_activation(self) end end Building a Callback Just choose the type of callback, name a method, and write a matching Ruby method
    82. 82. class User < ActiveRecord::Base acts_as_authentic after_create :send_activation_email def send_activation_email reset_perishable_token! UserNotifier.deliver_activation(self) end end Building a Callback Just choose the type of callback, name a method, and write a matching Ruby method
    83. 83. Not Just for Email
    84. 84. Not Just for Email Sending email using callbacks is a common usage
    85. 85. Not Just for Email Sending email using callbacks is a common usage However, callbacks are a general tool with many uses
    86. 86. Not Just for Email Sending email using callbacks is a common usage However, callbacks are a general tool with many uses For example:
    87. 87. Not Just for Email Sending email using callbacks is a common usage However, callbacks are a general tool with many uses For example: You might update an average_review_rating column with an after_save on Review
    88. 88. Not Just for Email Sending email using callbacks is a common usage However, callbacks are a general tool with many uses For example: You might update an average_review_rating column with an after_save on Review You might generate a login column from a provided email address in a before_validation on User
    89. 89. Completing the Example We need to make some minor changes and add a controller to get activation working
    90. 90. Migrating in Activation Fields Rails migrations are pretty smart and can guess where you want to add the fields
    91. 91. $ ruby script/generate migration add_activation_fields_to_users perishable_token:string active:boolean Migrating in Activation Fields Rails migrations are pretty smart and can guess where you want to add the fields
    92. 92. $ ruby script/generate migration add_activation_fields_to_users perishable_token:string active:boolean Migrating in Activation Fields Rails migrations are pretty smart and can guess where you want to add the fields
    93. 93. $ ruby script/generate migration add_activation_fields_to_users perishable_token:string active:boolean Migrating in Activation Fields Rails migrations are pretty smart and can guess where you want to add the fields
    94. 94. $ ruby script/generate migration add_activation_fields_to_users perishable_token:string active:boolean class AddActivationFieldsToUsers < ActiveRecord::Migration def self.up add_column :users, :perishable_token, :string add_column :users, :active, :boolean, :default => false, :null => false end def self.down remove_column :users, :active remove_column :users, :perishable_token end end Migrating in Activation Fields Rails migrations are pretty smart and can guess where you want to add the fields
    95. 95. $ ruby script/generate migration add_activation_fields_to_users perishable_token:string active:boolean class AddActivationFieldsToUsers < ActiveRecord::Migration def self.up add_column :users, :perishable_token, :string add_column :users, :active, :boolean, :default => false, :null => false end def self.down remove_column :users, :active remove_column :users, :perishable_token end end Migrating in Activation Fields Rails migrations are pretty smart and can guess where you want to add the fields
    96. 96. $ ruby script/generate migration add_activation_fields_to_users perishable_token:string active:boolean class AddActivationFieldsToUsers < ActiveRecord::Migration def self.up add_column :users, :perishable_token, :string add_column :users, :active, :boolean, :default => false, :null => false end def self.down remove_column :users, :active remove_column :users, :perishable_token end end $ rake db:migrate Migrating in Activation Fields Rails migrations are pretty smart and can guess where you want to add the fields
    97. 97. Adding Activations We look the user up by token, activate them, and log them in
    98. 98. $ ruby script/generate controller activations Adding Activations We look the user up by token, activate them, and log them in
    99. 99. $ ruby script/generate controller activations class ActivationsController < ApplicationController def create if @user = User.find_using_perishable_token(params[:token]) @user.active = true # activate the user @user.save UserSession.create(@user) # log them in flash[:notice] = "User activated." else flash[:error] = "User not found." end redirect_to root_path end end Adding Activations We look the user up by token, activate them, and log them in
    100. 100. $ ruby script/generate controller activations class ActivationsController < ApplicationController def create if @user = User.find_using_perishable_token(params[:token]) @user.active = true # activate the user @user.save UserSession.create(@user) # log them in flash[:notice] = "User activated." else flash[:error] = "User not found." end redirect_to root_path end end Adding Activations We look the user up by token, activate them, and log them in
    101. 101. $ ruby script/generate controller activations class ActivationsController < ApplicationController def create if @user = User.find_using_perishable_token(params[:token]) @user.active = true # activate the user @user.save UserSession.create(@user) # log them in flash[:notice] = "User activated." else flash[:error] = "User not found." end redirect_to root_path end end Adding Activations We look the user up by token, activate them, and log them in
    102. 102. $ ruby script/generate controller activations class ActivationsController < ApplicationController def create if @user = User.find_using_perishable_token(params[:token]) @user.active = true # activate the user @user.save UserSession.create(@user) # log them in flash[:notice] = "User activated." else flash[:error] = "User not found." end redirect_to root_path end end Adding Activations We look the user up by token, activate them, and log them in
    103. 103. Email Routing We can’t make an email link POST, so I created a custom route for the action
    104. 104. ActionController::Routing::Routes.draw do |map| map.resources :users map.resource :user_session map.login "login", :controller => "user_sessions", :action => "new" map.logout "logout", :controller => "user_sessions", :action => "destroy" map.activate "activate/:token", :controller => "activations", :action => "create" map.root :controller => "home" map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' end Email Routing We can’t make an email link POST, so I created a custom route for the action
    105. 105. ActionController::Routing::Routes.draw do |map| map.resources :users map.resource :user_session map.login "login", :controller => "user_sessions", :action => "new" map.logout "logout", :controller => "user_sessions", :action => "destroy" map.activate "activate/:token", :controller => "activations", :action => "create" map.root :controller => "home" map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' end Email Routing We can’t make an email link POST, so I created a custom route for the action
    106. 106. The Old Sign-up Our old controller logs them in as they are created and we can’t have that
    107. 107. class UsersController < ApplicationController def new @user = User.new end def create @user = User.new(params[:user]) if @user.save flash[:notice] = "Welcome!" redirect_to root_path else flash.now[:error] = "Sign-up could not be completed." render :action => :new end end end The Old Sign-up Our old controller logs them in as they are created and we can’t have that
    108. 108. class UsersController < ApplicationController def new @user = User.new end def create @user = User.new(params[:user]) if @user.save flash[:notice] = "Welcome!" redirect_to root_path else flash.now[:error] = "Sign-up could not be completed." render :action => :new end end end The Old Sign-up Our old controller logs them in as they are created and we can’t have that
    109. 109. class UsersController < ApplicationController def new @user = User.new end def create @user = User.new(params[:user]) if @user.save flash[:notice] = "Welcome!" redirect_to root_path else flash.now[:error] = "Sign-up could not be completed." render :action => :new end end end The Old Sign-up Our old controller logs them in as they are created and we can’t have that
    110. 110. Creation Without Login Now we don’t log them in and we tell them to check their email
    111. 111. class UsersController < ApplicationController def new @user = User.new end def create @user = User.new(params[:user]) if @user.save_without_session_maintenance flash[:notice] = "Please check your email to activate your account." redirect_to login_path else flash.now[:error] = "Sign-up could not be completed." render :action => :new end end end Creation Without Login Now we don’t log them in and we tell them to check their email
    112. 112. class UsersController < ApplicationController def new @user = User.new end def create @user = User.new(params[:user]) if @user.save_without_session_maintenance flash[:notice] = "Please check your email to activate your account." redirect_to login_path else flash.now[:error] = "Sign-up could not be completed." render :action => :new end end end Creation Without Login Now we don’t log them in and we tell them to check their email
    113. 113. class UsersController < ApplicationController def new @user = User.new end def create @user = User.new(params[:user]) if @user.save_without_session_maintenance flash[:notice] = "Please check your email to activate your account." redirect_to login_path else flash.now[:error] = "Sign-up could not be completed." render :action => :new end end end Creation Without Login Now we don’t log them in and we tell them to check their email
    114. 114. Activations in Action Let’s see what we have created
    115. 115. Signing Up I can create a new user with my email address and desired password
    116. 116. Signing Up I can create a new user with my email address and desired password
    117. 117. Time to Check Email I wasn’t logged in, as planned
    118. 118. Time to Check Email I wasn’t logged in, as planned
    119. 119. Can’t Login Yet I can’t login yet either, since my account isn’t active yet
    120. 120. Can’t Login Yet I can’t login yet either, since my account isn’t active yet
    121. 121. log/development.rb In development mode, emails are printed to the log file (clear logs with: rake log:clear)
    122. 122. Sent mail to james@graysoftinc.com Date: Sun, 7 Mar 2010 15:10:11 -0600 From: admin@secureapp.com To: james@graysoftinc.com Subject: Activate Your Account Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8 Welcome to the Secure Application. Please click the following link to activate your account: http://localhost:3000/activate/5HkeFFwiInKfjA4x25q9 log/development.rb In development mode, emails are printed to the log file (clear logs with: rake log:clear)
    123. 123. Activated and Logged In Visiting the URL completes the process
    124. 124. Activated and Logged In Visiting the URL completes the process
    125. 125. Questions?
    126. 126. User Emails Lab Your book has instructions for how to add email notifications to your application

    ×