소개
이 튜토리얼의 첫 번째 파트에서는 Devise를 설치하고 Rails 애플리케이션에 설정하는 방법을 배웠습니다. 이번 두 번째 파트에서는 DeviseInvitable을 통합하는 방법을 살펴보겠습니다.
DeviseInvitable은 Devise와 함께 작동하는 확장 젬(gem)입니다. 애플리케이션에 DeviseInvitable을 적용하면 사용자가 이메일을 통해 친구를 초대할 수 있습니다. 협업형 애플리케이션을 개발하고 있다면 반드시 도입하면 좋은 강력한 기능입니다.
DeviseInvitable 설정하기
Gemfile을 열고 다음 젬을 추가합니다:
#Gemfile ... gem 'devise_invitable'
이어서 bundle install 명령어를 실행해 젬을 설치합니다.
그다음 생성기(generator) 명령어를 실행하여 Devise 설정 파일에 DeviseInvitable의 설정 옵션을 추가합니다.
rails generate devise_invitable:install
텍스트 에디터로 config/initializers/devise.rb 파일을 열면 새로 추가된 설정 내용을 확인할 수 있습니다.
다음으로 User 모델에 DeviseInvitable을 추가해 보겠습니다.
rails generate devise_invitable User
위 명령어는 모델에 :invitable 플래그를 추가합니다. 실행 후 User 모델은 다음과 같은 모습이 됩니다:
#app/models/user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
동시에 아래와 같은 마이그레이션 파일도 자동 생성됩니다:
class DeviseInvitableAddToUsers < ActiveRecord::Migration
def up
change_table :users do |t|
t.string :invitation_token
t.datetime :invitation_created_at
t.datetime :invitation_sent_at
t.datetime :invitation_accepted_at
t.integer :invitation_limit
t.references :invited_by, polymorphic: true
t.integer :invitations_count, default: 0
t.index :invitations_count
t.index :invitation_token, unique: true # for invitable
t.index :invited_by_id
end
end
def down
change_table :users do |t|
t.remove_references :invited_by, polymorphic: true
t.remove :invitations_count, :invitation_limit, :invitation_sent_at, :invitation_accepted_at, :invitation_token, :invitation_created_at
end
end
end
이제 rake db:migrate 명령어로 데이터베이스를 마이그레이션합니다.
DeviseInvitable을 위한 컨트롤러 설정
초대장을 보낼 때 DeviseInvitable은 일부 매개변수를 전달받아야 합니다. 이를 위해 필요한 매개변수를 허용 목록(whitelist)에 등록해야 합니다. 텍스트 에디터에서 app/controllers/application_controller.rb 파일을 열어 아래와 같이 수정합니다:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
added_attrs = [:username, :email, :password, :password_confirmation, :remember_me]
devise_parameter_sanitizer.permit :sign_up, keys: added_attrs
devise_parameter_sanitizer.permit :account_update, keys: added_attrs
devise_parameter_sanitizer.permit :accept_invitation, keys: [:email]
end
end
위 코드에서 볼 수 있듯이 DeviseInvitable을 위해 :email 매개변수가 허용 목록에 등록되었습니다.
이제 콘솔에서 실제 동작을 확인해 보겠습니다. 터미널에서 rails console을 실행한 뒤 아래 코드를 입력합니다.
[1] pry(main)> User.invite!(:email => "johndoe@example.com")
환경에 따라 세부 내용은 조금씩 다르지만, 대체로 아래와 같은 출력 결과가 나타납니다.
[2] pry(main)> User Load (78.3ms) SELECT "users".* FROM "users" WHERE "users"."email" = ? ORDER BY "users"."id" ASC LIMIT 1 [["email", "johndoe@example.com"]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."invitation_token" = ? ORDER BY "users"."id" ASC LIMIT 1 [["invitation_token", "658da470d5fcbb2275f30bc1fb66f5771b889cec2f1e56f536319d2fd1ef4a92"]]
(0.1ms) begin transaction
SQL (67.8ms) INSERT INTO "users" ("email", "encrypted_password", "invitation_token", "invitation_created_at", "invitation_sent_at", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?) [["email", "johndoe@example.com"], ["encrypted_password", "$2a$11$0sLfqvfFDsebcmcQTUXzlOuqNIooL5z8niXeza8OUwNK3gZY/iRum"], ["invitation_token", "658da470d5fcbb2275f30bc1fb66f5771b889cec2f1e56f536319d2fd1ef4a92"], ["invitation_created_at", "2016-10-07 07:41:51.254047"], ["invitation_sent_at", "2016-10-07 07:41:51.254047"], ["created_at", "2016-10-07 07:41:51.255700"], ["updated_at", "2016-10-07 07:41:51.255700"]]
(220.5ms) commit transaction
Rendered /home/kinsomicrote/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/devise_invitable-1.7.0/app/views/devise/mailer/invitation_instructions.html.erb (2.5ms)
Rendered /home/kinsomicrote/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/devise_invitable-1.7.0/app/views/devise/mailer/invitation_instructions.text.erb (88.0ms)
Devise::Mailer#invitation_instructions: processed outbound mail in 247.1ms
Sent mail to johndoe@example.com (74.3ms)
Date: Fri, 07 Oct 2016 08:41:51 +0100
From: please-change-me-at-config-initializers-devise@example.com
Reply-To: please-change-me-at-config-initializers-devise@example.com
To: johndoe@example.com
Subject: Invitation instructions
Mime-Version: 1.0
Content-Type: multipart/alternative;
charset=UTF-8
Content-Transfer-Encoding: 7bit
----==_mimepart_57f751bfcc725_18022ac6c272b12840524
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: 7bit
Hello johndoe@example.com
Someone has invited you to https://localhost:3000/, you can accept it through the link below.
https://localhost:3000/users/invitation/accept?invitation_token=xmW9uRfyafptmeFMmFBy
If you don't want to accept the invitation, please ignore this email.
Your account won't be created until you access the link above and set your password.
----==_mimepart_57f751bfcc725_18022ac6c272b12840524--
=> #<User:0x00558d875fa798
id: 4,
email: "johndoe@example.com",
encrypted_password: "$2a$11$0sLfqvfFDsebcmcQTUXzlOuqNIooL5z8niXeza8OUwNK3gZY/iRum",
reset_password_token: nil,
reset_password_sent_at: nil,
remember_created_at: nil,
sign_in_count: 0,
current_sign_in_at: nil,
last_sign_in_at: nil,
current_sign_in_ip: nil,
last_sign_in_ip: nil,
created_at: Fri, 07 Oct 2016 07:41:51 UTC +00:00,
updated_at: Fri, 07 Oct 2016 07:41:51 UTC +00:00,
username: nil,
invitation_token: "658da470d5fcbb2275f30bc1fb66f5771b889cec2f1e56f536319d2fd1ef4a92",
invitation_created_at: Fri, 07 Oct 2016 07:41:51 UTC +00:00,
invitation_sent_at: Fri, 07 Oct 2016 07:41:51 UTC +00:00,
invitation_accepted_at: nil,
invitation_limit: nil,
invited_by_id: nil,
invited_by_type: nil,
invitations_count: 0>
[3] pry(main)>
계획대로 정상적으로 작동했습니다.
물론 실제 서비스에서는 사용자가 명령줄로 초대장을 보내게 할 수 없습니다. 따라서 프런트엔드에서 DeviseInvitable이 작동하도록 설정하는 것이 중요합니다. 방법은 아주 간단합니다. 다음 생성기 명령어를 실행하여 DeviseInvitable의 뷰(view)를 생성하세요.
rails generate devise_invitable:views users
또한 애플리케이션 어딘가에 초대장 발송 페이지(app/views/users/invitations/new.html.erb)로 연결되는 링크를 추가해야 합니다.
이 애플리케이션에서는 내비게이션 파일에 해당 링크를 추가하겠습니다. 필자가 작성한 코드는 다음과 같습니다:
#app/views/shared/_navigation.html.erb
<nav class="navbar navbar-inverse">
<div class="container">
<div class="navbar-header">
<%= link_to 'Tutsplus Devise', root_path, class: 'navbar-brand' %>
</div>
<div id="navbar">
<ul class="nav navbar-nav">
<li><%= link_to 'Home', root_path %></li>
</ul>
<ul class="nav navbar-nav pull-right">
<% if user_signed_in? %>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<%= current_user.username %>
<span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu">
<li><%= link_to 'Invite', new_user_invitation_path %></li>
<li><%= link_to 'Profile', edit_user_registration_path %></li>
<li><%= link_to 'Log out', destroy_user_session_path, method: :delete %></li>
</ul>
</li>
<% else %>
<li><%= link_to 'Log In', new_user_session_path %></li>
<li><%= link_to 'Sign Up', new_user_registration_path %></li>
<% end %>
</ul>
</div>
</div>
</nav>
DeviseInvitable이 제공하는 라우트를 확인하려면 rake routes | grep invit 명령어를 실행하세요. 출력 결과는 다음과 같습니다.
cancel_user_registration GET /users/cancel(.:format) devise_invitable/registrations#cancel
user_registration POST /users(.:format) devise_invitable/registrations#create
new_user_registration GET /users/sign_up(.:format) devise_invitable/registrations#new
edit_user_registration GET /users/edit(.:format) devise_invitable/registrations#edit
PATCH /users(.:format) devise_invitable/registrations#update
PUT /users(.:format) devise_invitable/registrations#update
DELETE /users(.:format) devise_invitable/registrations#destroy
accept_user_invitation GET /users/invitation/accept(.:format) devise/invitations#edit
remove_user_invitation GET /users/invitation/remove(.:format) devise/invitations#destroy
user_invitation POST /users/invitation(.:format) devise/invitations#create
new_user_invitation GET /users/invitation/new(.:format) devise/invitations#new
PATCH /users/invitation(.:format) devise/invitations#update
PUT /users/invitation(.:format) devise/invitations#update
지금까지의 작업 결과를 직접 확인해 보겠습니다. rails server 명령어로 서버를 시작하세요.
브라우저에서 https://localhost:3000/users/invitation/new 페이지로 이동합니다. 화면에 표시된 폼에 이메일 주소를 입력하고 버튼을 클릭하면 초대장이 발송됩니다! 서버 로그를 확인하면 초대장을 보낼 때 생성된 출력을 볼 수 있으며, 그 안에는 초대를 수락할 수 있는 링크도 포함되어 있습니다.
발송된 이메일을 브라우저에서 바로 확인할 수 있다면 훨씬 편리하겠죠? 지금부터 그 방법을 알아보겠습니다.
Letter Opener 통합하기
Letter Opener는 기본 브라우저에서 이메일을 미리 볼 수 있게 해주는 젬입니다. 이것을 사용하면 개발 환경에서 별도의 메일 발송 시스템을 구축하지 않아도 됩니다.
Gemfile을 열고 아래 젬을 추가합니다:
gem 'letter_opener'
bundle install을 실행한 뒤, 텍스트 에디터에서 config/environments/development.rb 파일을 열어 아래 코드를 추가합니다.
#config/environments/development.rb ... config.action_mailer.delivery_method = :letter_opener end
Rails 서버를 재시작한 후, 브라우저에서 https://localhost:3000/users/invitation/new 페이지로 이동하여 폼을 작성하고 제출해 보세요. 이번에는 초대 이메일이 담긴 새로운 페이지가 열리는 것을 확인할 수 있습니다.
기본 로그인·로그아웃 라우트 변경하기
Devise를 사용할 때 기본 sign_in 및 sign_out 라우트는 다음과 같습니다:
sign_in: https://localhost:3000/users/sign_in
sign_out: https://localhost:3000/users/sign_out
이를 변경하려면 config/routes.rb 파일에 아래 내용을 추가합니다:
#config/routes.rb
as :user do
get 'signin' => 'devise/sessions#new'
post 'signin' => 'devise/sessions#create'
delete 'signout' => 'devise/sessions#destroy'
end
이제 브라우저에서 https://localhost:3000/signin 주소로 접속할 수 있습니다.
마무리
이제 DeviseInvitable을 활용하는 방법을 익혔습니다. 더불어 letter_opener 젬을 사용해 개발 환경에서 이메일을 손쉽게 미리 보는 방법도 배웠습니다. Devise로 할 수 있는 일은 무궁무진하니, Wiki 문서를 참고하여 더 많은 기능을 탐색해 보시기 바랍니다. 끝까지 읽어주셔서 감사합니다.