KEMBAR78
Desenvolvimento web com Ruby on Rails (parte 6) | PDF
Desenvolvimento
Web com Ruby on
Rails
João Lucas Pereira de Santana
gtalk | linkedin | twitter: jlucasps
Devise
@jlucasps
Solução flexível para autenticação de usuários
Segue o padrão MVC
Totalmente integrada com o Rails
Permite várias roles autenticadas ao mesmo
tempo (user, admin, member)
Baseada em conceitos de módulos
● Database Authenticatable
○ Criptografar a senha e armazenar em
banco de dados
○ Auntenticação pode ser feita via POST ou
HTTP Basic Authentication
Devise
@jlucasps
● Token Authenticatable
○ Autenticar o usuário baseado em um token
de acesso
○ Token pode ser enviado via query string ou
HTTP Basic Authentication
Devise
@jlucasps
Devise
@jlucasps
● Omniauthable
○ Framework de autenticação compatível
com diversos providers (facebook, twitter,
openId, google, github), além dos
tradicionais username e password
Devise
@jlucasps
● Confirmable
○ Enviar email com as instruções de
confirmação de cadastro
● Recoverable
○ Alterar password do usuário e enviar
instruções de alteração
Devise
@jlucasps
● Registerable
○ Permite cadastar usuários para utilizarem
uma aplicação
○ Editar as informações de cadastro do
usuário
○ Excluir o cadastro
Devise
@jlucasps
● Rememberable
○ Mecanimo para salvar cookies e permitir
manter usuário autenticado na aplicação
● Trackable
○ Registrar quantidade de acessos, hora do
acesso e IP de origem
Devise
@jlucasps
● Timeoutable
○ Expirar a sessão caso o usuário fique um
período inativo
● Validatable
○ Validação de email e password
○ Mecanismo opcional e customizável
Devise
@jlucasps
● Lockable
○ Bloquear a conta do usuário caso haja um
certo número de tentativas frustradas de
acesso
○ Desbloqueio pode ser feito via email ou
após um período de tempo
Devise
@jlucasps
# https://github.com/plataformatec/devise
# Flexible authentication solution for Rails with Warden
gem 'devise'
Atualizar Gemfile
Atualizar config/environments/development.rb
config.action_mailer.default_url_options = { :host =>
'localhost:3000' }
MailCatcher
@jlucasps
# https://github.com/sj26/mailcatcher
# Catches mail and serves it through a dream.
gem 'mailcatcher'
Adicionar MailCatcher ao Gemfile
Atualizar config/environments/development.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = { :address => "localhost", :
port => 1025 }
jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ mailcatcher
Starting MailCatcher
==> smtp://127.0.0.1:1025
==> http://127.0.0.1:1080
*** MailCatcher runs as a daemon by default. Go to the web interface to
quit.
Devise
@jlucasps
Após adicionar Devise ao Gemfile, execute o
generator
jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rails g devise:
install
create config/initializers/devise.rb
create config/locales/devise.en.yml
===========================================
====================
Some setup you must do manually if you haven't yet:
....
Devise
@jlucasps
<!DOCTYPE html>
<html>
<head>
<title>FirstApp</title>
<%= stylesheet_link_tag "application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
</head>
<body>
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
<%= render :partial => 'shared/menu_top' %>
<div class="container-fluid">
<div class="row-fluid">
<%= yield :sidebar %>
<%= yield %>
</div>
<%= render :partial => 'shared/footer' %>
</div>
</body>
</html>
Configurar /app/views/layouts/application.html.erb
Devise
@jlucasps
jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rails g devise
User
invoke active_record
create db/migrate/20130619172147_add_devise_to_users.rb
insert app/models/user.rb
route devise_for :users
Adicionar Devise a algum model
Configurar Migration gerada e executá-la
jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rake db:migrate
== AddDeviseToUsers: migrating
===============================================
-- change_table(:users)
-> 0.0590s
-- add_index(:users, :reset_password_token, {:unique=>true})
-> 0.0007s
== AddDeviseToUsers: migrated (0.0600s)
======================================
Devise
@jlucasps
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :authenticate_user!
end
Configurar ApplicationController
Configurar arquivo /config/initializers/devise.rb
# ==> Scopes configuration
# Turn scoped views on. Before rendering "sessions/new", it will first check for
# "users/sessions/new". It's turned off by default because it's slower if you
# are using only default views.
config.scoped_views = true
Devise
@jlucasps
user_signed_in?
current_user
user_session
Métodos helpers gerados pelo Devise
Caso o model seja Member
before_filter :authenticate_member!
member_signed_in?
current_member
member_session
Devise
@jlucasps
jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rails generate devise:views users
invoke Devise::Generators::SharedViewsGenerator
create app/views/users/shared
create app/views/users/shared/_links.erb
invoke form_for
create app/views/users/confirmations
create app/views/users/confirmations/new.html.erb
create app/views/users/passwords
create app/views/users/passwords/edit.html.erb
Customizar as views utilizadas pelo Devise
Devise
@jlucasps
class WelcomeController < ApplicationController
before_filter :authenticate_user!, :except => [:index, :about, :contact]
def index
end
def black
render :layout => 'application_black'
end
def about
end
def contact
end
end
Caso queira liberar acesso para actions do WelcomeController
Devise
@jlucasps
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container-fluid">
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<%= link_to "Project name", index_path, :class => "brand" %>
<div class="nav-collapse collapse">
<p class="navbar-text pull-right">
<% if user_signed_in? %>
Logged in as <%= link_to current_user.name edit_user_registration_path(current_user), :
class => "navbar-link" %>
<% else %>
<%= link_to "login", new_user_session_path, :class => "btn" %>
<% end %>
</p>
<ul class="nav">
<li class="active"><%= link_to "Home", index_path %></li>
<li><%= link_to "About", about_path %></li>
<li><%= link_to "Contact", contact_path %></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
</div>
Exibir link para login e usuário logado: /app/views/shared/_menu_top.html.erb
Devise
@jlucasps
<h2>Sign up</h2>
<%= form_for(resource, :as => resource_name, :url => registration_path
(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :name %><br />
<%= f.text_field :name, :autofocus => true %></div>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.label :password %><br />
<%= f.password_field :password %></div>
<div><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %></div>
<div><%= f.submit "Sign up" %></div>
<% end %>
<%= render "users/shared/links" %>
Alterar tela de cadastro de usuários: /app/views/users/registrations/new.html.erb
Devise
@jlucasps
RSpec.configure do |config|
# ## Mock Framework
#
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
#
# config.mock_with :mocha
# config.mock_with :flexmock
# config.mock_with :rr
config.include Capybara::DSL
config.include Devise::TestHelpers, :type => :controller
config.include Rails.application.routes.url_helpers
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
#config.fixture_path = "#{::Rails.root}/spec/fixtures"
Testes automatizados com Devise: /spec/spec_helper.rb
Devise
@jlucasps
include Warden::Test::Helpers
def create_logged_in_user(user_sym)
user = FactoryGirl.find_or_create(user_sym)
login_as(user, scope: :user)
user
end
class ActiveRecord::Base
mattr_accessor :shared_connection
@@shared_connection = nil
def self.connection
@@shared_connection || retrieve_connection
end
end
ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection
Devise support /spec/support/devise.rb
FactoryGirl
@jlucasps
# Factory_girl is a fixtures replacement with a straightforward
definition syntax
# https://github.com/thoughtbot/factory_girl_rails
gem 'factory_girl_rails', "~> 4.0"
Adicionar factory_girl ao Gemfile
Importar factory_girl no spec_helper
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'
require 'factory_girl'
require 'factory_girl_patch'
require 'capybara/rails'
require 'capybara/rspec'
FactoryGirl.register_strategy(:find_or_create, FactoryGirlPatch)
FactoryGirl
@jlucasps
FactoryGirl.define do
factory :user_bart, :class => User do
name "Bart Simpson"
email "bart@simpson.com"
password "dirty_boy"
password_confirmation "dirty_boy"
encrypted_password BCrypt::Password.create("dirty_boy", :cost => 10)
end
factory :user_lisa, :class => User do
name "Lisa Simpson"
email "lisa@simpson.com"
password "smart_girl"
password_confirmation "smart_girl"
encrypted_password BCrypt::Password.create("smart_girl", :cost => 10)
end
end
Criar factories
FactoryGirl
@jlucasps
class FactoryGirlPatch
def association(runner)
runner.run
end
def result(evaluation)
evaluation.object.tap do |instance|
evaluation.notify(:after_build, instance)
evaluation.notify(:before_create, instance)
saved_object = instance.class.where(instance.attributes.except("id", "created_at", "updated_at")).first
if saved_object.present?
instance.id = saved_object.id
instance.created_at = saved_object.created_at if instance.respond_to?(:created_at)
instance.updated_at = saved_object.updated_at if instance.respond_to?(:updated_at)
else
evaluation.create(instance)
evaluation.notify(:after_create, instance)
end
end
end
end
Estratégia find_or_create: /lib/factory_girl_patch.rb
Devise
@jlucasps
require 'spec_helper'
describe UsersController do
let(:user_bart) { FactoryGirl.find_or_create(:user_bart)}
before(:each) do
sign_in user_bart
end
describe "GET index" do
it "assigns @users" do
saved_users = [FactoryGirl.find_or_create(:user_bart), FactoryGirl.find_or_create(:
user_lisa)]
get :index
assigns(:users).should eq(saved_users)
end
end
end
Testes no controller /spec/controllers/users_controller_spec.rb
Devise
@jlucasps
Testes no controller /spec/controllers/users_controller_spec.rb
jlucasps@lotus:/media/first_app$ rspec
spec/controllers/users_controller_spec.rb
.
Finished in 0.331 seconds
1 example, 0 failures
Randomized with seed 5290
Desenvolvimento
Web com Ruby on
Rails
João Lucas Pereira de Santana
gtalk | linkedin | twitter: jlucasps
Obrigado!

Desenvolvimento web com Ruby on Rails (parte 6)

  • 1.
    Desenvolvimento Web com Rubyon Rails João Lucas Pereira de Santana gtalk | linkedin | twitter: jlucasps
  • 2.
    Devise @jlucasps Solução flexível paraautenticação de usuários Segue o padrão MVC Totalmente integrada com o Rails Permite várias roles autenticadas ao mesmo tempo (user, admin, member) Baseada em conceitos de módulos
  • 3.
    ● Database Authenticatable ○Criptografar a senha e armazenar em banco de dados ○ Auntenticação pode ser feita via POST ou HTTP Basic Authentication Devise @jlucasps
  • 4.
    ● Token Authenticatable ○Autenticar o usuário baseado em um token de acesso ○ Token pode ser enviado via query string ou HTTP Basic Authentication Devise @jlucasps
  • 5.
    Devise @jlucasps ● Omniauthable ○ Frameworkde autenticação compatível com diversos providers (facebook, twitter, openId, google, github), além dos tradicionais username e password
  • 6.
    Devise @jlucasps ● Confirmable ○ Enviaremail com as instruções de confirmação de cadastro ● Recoverable ○ Alterar password do usuário e enviar instruções de alteração
  • 7.
    Devise @jlucasps ● Registerable ○ Permitecadastar usuários para utilizarem uma aplicação ○ Editar as informações de cadastro do usuário ○ Excluir o cadastro
  • 8.
    Devise @jlucasps ● Rememberable ○ Mecanimopara salvar cookies e permitir manter usuário autenticado na aplicação ● Trackable ○ Registrar quantidade de acessos, hora do acesso e IP de origem
  • 9.
    Devise @jlucasps ● Timeoutable ○ Expirara sessão caso o usuário fique um período inativo ● Validatable ○ Validação de email e password ○ Mecanismo opcional e customizável
  • 10.
    Devise @jlucasps ● Lockable ○ Bloqueara conta do usuário caso haja um certo número de tentativas frustradas de acesso ○ Desbloqueio pode ser feito via email ou após um período de tempo
  • 11.
    Devise @jlucasps # https://github.com/plataformatec/devise # Flexibleauthentication solution for Rails with Warden gem 'devise' Atualizar Gemfile Atualizar config/environments/development.rb config.action_mailer.default_url_options = { :host => 'localhost:3000' }
  • 12.
    MailCatcher @jlucasps # https://github.com/sj26/mailcatcher # Catchesmail and serves it through a dream. gem 'mailcatcher' Adicionar MailCatcher ao Gemfile Atualizar config/environments/development.rb config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { :address => "localhost", : port => 1025 } jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ mailcatcher Starting MailCatcher ==> smtp://127.0.0.1:1025 ==> http://127.0.0.1:1080 *** MailCatcher runs as a daemon by default. Go to the web interface to quit.
  • 13.
    Devise @jlucasps Após adicionar Deviseao Gemfile, execute o generator jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rails g devise: install create config/initializers/devise.rb create config/locales/devise.en.yml =========================================== ==================== Some setup you must do manually if you haven't yet: ....
  • 14.
    Devise @jlucasps <!DOCTYPE html> <html> <head> <title>FirstApp</title> <%= stylesheet_link_tag"application", :media => "all" %> <%= javascript_include_tag "application" %> <%= csrf_meta_tags %> </head> <body> <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> <%= render :partial => 'shared/menu_top' %> <div class="container-fluid"> <div class="row-fluid"> <%= yield :sidebar %> <%= yield %> </div> <%= render :partial => 'shared/footer' %> </div> </body> </html> Configurar /app/views/layouts/application.html.erb
  • 15.
    Devise @jlucasps jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rails gdevise User invoke active_record create db/migrate/20130619172147_add_devise_to_users.rb insert app/models/user.rb route devise_for :users Adicionar Devise a algum model Configurar Migration gerada e executá-la jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rake db:migrate == AddDeviseToUsers: migrating =============================================== -- change_table(:users) -> 0.0590s -- add_index(:users, :reset_password_token, {:unique=>true}) -> 0.0007s == AddDeviseToUsers: migrated (0.0600s) ======================================
  • 16.
    Devise @jlucasps class ApplicationController <ActionController::Base protect_from_forgery before_filter :authenticate_user! end Configurar ApplicationController Configurar arquivo /config/initializers/devise.rb # ==> Scopes configuration # Turn scoped views on. Before rendering "sessions/new", it will first check for # "users/sessions/new". It's turned off by default because it's slower if you # are using only default views. config.scoped_views = true
  • 17.
    Devise @jlucasps user_signed_in? current_user user_session Métodos helpers geradospelo Devise Caso o model seja Member before_filter :authenticate_member! member_signed_in? current_member member_session
  • 18.
    Devise @jlucasps jlucasps@lotus:/media/truecrypt1/handsonrails/first_app$ rails generatedevise:views users invoke Devise::Generators::SharedViewsGenerator create app/views/users/shared create app/views/users/shared/_links.erb invoke form_for create app/views/users/confirmations create app/views/users/confirmations/new.html.erb create app/views/users/passwords create app/views/users/passwords/edit.html.erb Customizar as views utilizadas pelo Devise
  • 19.
    Devise @jlucasps class WelcomeController <ApplicationController before_filter :authenticate_user!, :except => [:index, :about, :contact] def index end def black render :layout => 'application_black' end def about end def contact end end Caso queira liberar acesso para actions do WelcomeController
  • 20.
    Devise @jlucasps <div class="navbar navbar-inversenavbar-fixed-top"> <div class="navbar-inner"> <div class="container-fluid"> <button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <%= link_to "Project name", index_path, :class => "brand" %> <div class="nav-collapse collapse"> <p class="navbar-text pull-right"> <% if user_signed_in? %> Logged in as <%= link_to current_user.name edit_user_registration_path(current_user), : class => "navbar-link" %> <% else %> <%= link_to "login", new_user_session_path, :class => "btn" %> <% end %> </p> <ul class="nav"> <li class="active"><%= link_to "Home", index_path %></li> <li><%= link_to "About", about_path %></li> <li><%= link_to "Contact", contact_path %></li> </ul> </div><!--/.nav-collapse --> </div> </div> </div> Exibir link para login e usuário logado: /app/views/shared/_menu_top.html.erb
  • 21.
    Devise @jlucasps <h2>Sign up</h2> <%= form_for(resource,:as => resource_name, :url => registration_path (resource_name)) do |f| %> <%= devise_error_messages! %> <div><%= f.label :name %><br /> <%= f.text_field :name, :autofocus => true %></div> <div><%= f.label :email %><br /> <%= f.email_field :email %></div> <div><%= f.label :password %><br /> <%= f.password_field :password %></div> <div><%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation %></div> <div><%= f.submit "Sign up" %></div> <% end %> <%= render "users/shared/links" %> Alterar tela de cadastro de usuários: /app/views/users/registrations/new.html.erb
  • 22.
    Devise @jlucasps RSpec.configure do |config| ### Mock Framework # # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: # # config.mock_with :mocha # config.mock_with :flexmock # config.mock_with :rr config.include Capybara::DSL config.include Devise::TestHelpers, :type => :controller config.include Rails.application.routes.url_helpers # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures #config.fixture_path = "#{::Rails.root}/spec/fixtures" Testes automatizados com Devise: /spec/spec_helper.rb
  • 23.
    Devise @jlucasps include Warden::Test::Helpers def create_logged_in_user(user_sym) user= FactoryGirl.find_or_create(user_sym) login_as(user, scope: :user) user end class ActiveRecord::Base mattr_accessor :shared_connection @@shared_connection = nil def self.connection @@shared_connection || retrieve_connection end end ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection Devise support /spec/support/devise.rb
  • 24.
    FactoryGirl @jlucasps # Factory_girl isa fixtures replacement with a straightforward definition syntax # https://github.com/thoughtbot/factory_girl_rails gem 'factory_girl_rails', "~> 4.0" Adicionar factory_girl ao Gemfile Importar factory_girl no spec_helper # This file is copied to spec/ when you run 'rails generate rspec:install' ENV["RAILS_ENV"] ||= 'test' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'rspec/autorun' require 'factory_girl' require 'factory_girl_patch' require 'capybara/rails' require 'capybara/rspec' FactoryGirl.register_strategy(:find_or_create, FactoryGirlPatch)
  • 25.
    FactoryGirl @jlucasps FactoryGirl.define do factory :user_bart,:class => User do name "Bart Simpson" email "bart@simpson.com" password "dirty_boy" password_confirmation "dirty_boy" encrypted_password BCrypt::Password.create("dirty_boy", :cost => 10) end factory :user_lisa, :class => User do name "Lisa Simpson" email "lisa@simpson.com" password "smart_girl" password_confirmation "smart_girl" encrypted_password BCrypt::Password.create("smart_girl", :cost => 10) end end Criar factories
  • 26.
    FactoryGirl @jlucasps class FactoryGirlPatch def association(runner) runner.run end defresult(evaluation) evaluation.object.tap do |instance| evaluation.notify(:after_build, instance) evaluation.notify(:before_create, instance) saved_object = instance.class.where(instance.attributes.except("id", "created_at", "updated_at")).first if saved_object.present? instance.id = saved_object.id instance.created_at = saved_object.created_at if instance.respond_to?(:created_at) instance.updated_at = saved_object.updated_at if instance.respond_to?(:updated_at) else evaluation.create(instance) evaluation.notify(:after_create, instance) end end end end Estratégia find_or_create: /lib/factory_girl_patch.rb
  • 27.
    Devise @jlucasps require 'spec_helper' describe UsersControllerdo let(:user_bart) { FactoryGirl.find_or_create(:user_bart)} before(:each) do sign_in user_bart end describe "GET index" do it "assigns @users" do saved_users = [FactoryGirl.find_or_create(:user_bart), FactoryGirl.find_or_create(: user_lisa)] get :index assigns(:users).should eq(saved_users) end end end Testes no controller /spec/controllers/users_controller_spec.rb
  • 28.
    Devise @jlucasps Testes no controller/spec/controllers/users_controller_spec.rb jlucasps@lotus:/media/first_app$ rspec spec/controllers/users_controller_spec.rb . Finished in 0.331 seconds 1 example, 0 failures Randomized with seed 5290
  • 29.
    Desenvolvimento Web com Rubyon Rails João Lucas Pereira de Santana gtalk | linkedin | twitter: jlucasps Obrigado!