Screencast: Zeit und Web-Anfragen testen/messen

Zeitmessungen mit der aktuellen Zeit und Anfragen an ggf. externe Seiten sind nicht ganz einfach. In diesem Screencast zeigt Ryan wie er solche Tests mit Timecop und FakeWeb durchführt.

 

Downloads in verschiedenen Formaten:

source code
mp4
m4v
webm
ogv

 

Resourcen:

Gemfile

[ruby]
group :test do
gem "timecop"
gem "fakeweb"
end
[/ruby]

spec_helper.rb

[ruby]
FakeWeb.allow_net_connect = false RSpec.configure do |config|
config.before(:each) do
Timecop.return FakeWeb.clean_registry
end
end
[/ruby]

user_spec.rb

[ruby]
it "saves the time the password reset was sent" do
Timecop.freeze user.send_password_reset Time.use_zone("Paris") do
user.reload.password_reset_sent_at.should eq(Time.zone.now)
end
end
[/ruby]

web_request_spec.rb

[ruby]
it "fetches the content length" do
FakeWeb.register_uri(:head, "http://example.com/", :content_length => 123) WebRequest.new(:url =>"http://example.com/").content_length.should eq(123)
end
[/ruby]

models/web_request.rb

[ruby]
def content_length uri = URI.parse(url)
response = Net::HTTP.start(uri.host, uri.port) { |http| http.request_head(uri.path) }
response["content-length"].to_i
end
[/ruby]

config/application.rb

[ruby]
require ’net/http‘
[/ruby]

Screencast: Testen von Password-Reset und Erinnerung

In der letzten Woche zeigte Ryan wie man selber eine Funktion zum Zurücksetzen von Passwörter bauen kann. In dieser Woche zeigt er wie er diese Funktion mit RSpec, Capybara, Factory Girl und Guard testet.

 

Downloads in verschiedenen Formaten:

source code
mp4
m4v
webm
ogv

 

Resourcen:

bash

[bash]
bundle rails g rspec:install mkdir spec/support spec/models spec/routing guard init rspec gem install rb-fsevent rails g integration_test password_reset rails g controller password_resets new –no-test-framework rails g mailer user_mailer password_reset rails g migration add_password_reset_to_users password_reset_token:string password_reset_sent_at:datetime rake db:migrate
[/bash]

Gemfile

[ruby]
gem "rspec-rails", :group => [:test, :development] group :test do gem "factory_girl_rails" gem "capybara" gem "guard-rspec" end
[/ruby]

spec_helper.rb

[ruby]
require ‚capybara/rspec‘ RSpec.configure do |config| # … config.include(MailerMacros) config.before(:each) { reset_email } end
[/ruby]

spec/support/mailer_macros.rb

[ruby]
module MailerMacros def last_email ActionMailer::Base.deliveries.last end def reset_email ActionMailer::Base.deliveries = [] end end
[/ruby]

config/environments/test.rb

[ruby]
config.action_mailer.default_url_options = { :host => "www.example.com" }
[/ruby]

spec/requests/password_resets_spec.rb

[ruby]
describe "PasswordResets" do it "emails user when requesting password reset" do user = Factory(:user) visit login_path click_link "password" fill_in "Email", :with => user.email click_button "Reset Password" current_path.should eq(root_path) page.should have_content("Email sent") last_email.to.should include(user.email) end it "does not email invalid user when requesting password reset" do visit login_path click_link "password" fill_in "Email", :with => "nobody@example.com" click_button "Reset Password" current_path.should eq(root_path) page.should have_content("Email sent") last_email.should be_nil end it "updates the user password when confirmation matches" do user = Factory(:user, :password_reset_token => "something", :password_reset_sent_at => 1.hour.ago) visit edit_password_reset_path(user.password_reset_token) fill_in "Password", :with => "foobar" click_button "Update Password" page.should have_content("Password doesn’t match confirmation") fill_in "Password", :with => "foobar" fill_in "Password confirmation", :with => "foobar" click_button "Update Password" page.should have_content("Password has been reset") end it "reports when password token has expired" do user = Factory(:user, :password_reset_token => "something", :password_reset_sent_at => 5.hour.ago) visit edit_password_reset_path(user.password_reset_token) fill_in "Password", :with => "foobar" fill_in "Password confirmation", :with => "foobar" click_button "Update Password" page.should have_content("Password reset has expired") end it "raises record not found when password token is invalid" do lambda { visit edit_password_reset_path("invalid") }.should raise_exception(ActiveRecord::RecordNotFound) end end
[/ruby]

spec/models/user_spec.rb

[ruby]
describe User do describe "#send_password_reset" do let(:user) { Factory(:user) } it "generates a unique password_reset_token each time" do user.send_password_reset last_token = user.password_reset_token user.send_password_reset user.password_reset_token.should_not eq(last_token) end it "saves the time the password reset was sent" do user.send_password_reset user.reload.password_reset_sent_at.should be_present end it "delivers email to user" do user.send_password_reset last_email.to.should include(user.email) end end end
[/ruby]

spec/mailers/user_mailer_spec.rb

[ruby]
describe UserMailer do describe "password_reset" do let(:user) { Factory(:user, :password_reset_token => "anything") } let(:mail) { UserMailer.password_reset(user) } it "send user password reset url" do mail.subject.should eq("Password Reset") mail.to.should eq([user.email]) mail.from.should eq(["from@example.com"]) mail.body.encoded.should match(edit_password_reset_path(user.password_reset_token)) end end end
[/ruby]

Screencast: Passwort reset und Erinnerungsfunktion

Passwörter zurücksetzen und Erinnerungsfunktionen, die einen wiederkehrenden Benutzer erkennen, sind inzwischen Standard in den meisten Applikationen. Ryan zeigt wie diese Woche wie in einer selbstgebauten Authentifizierung diese Funktionen auf einfache Weise implementiert werden können.

 

Downloads in verschiedenen Formaten:

source code
mp4
m4v
webm
ogv

 

Resourcen:

bash

[bash]
rails g migration add_auth_token_to_users auth_token:string
rake db:migrate
rails g controller password_resets new
rails g migration add_password_reset_to_users password_reset_token:string password_reset_sent_at:datetime
rails g mailer user_mailer password_reset
[/bash]

models/user.rb

[ruby]
before_create { generate_token(:auth_token) }

def send_password_reset
generate_token(:password_reset_token)
self.password_reset_sent_at = Time.zone.now
save!
UserMailer.password_reset(self).deliver
end

def generate_token(column)
begin
self[column] = SecureRandom.urlsafe_base64
end while User.exists?(column => self[column])
end
[/ruby]

application_controller.rb

[ruby]
def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id]
end
[/ruby]

sessions_controller.rb

[ruby]
def create
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
if params[:remember_me]
cookies.permanent[:auth_token] = user.auth_token
else
cookies[:auth_token] = user.auth_token
end
redirect_to root_url, :notice => "Logged in!"
else
flash.now.alert = "Invalid email or password"
render "new"
end
end

def destroy
cookies.delete(:auth_token)
redirect_to root_url, :notice => "Logged out!"
end
[/ruby]

password_resets_controller.rb

[ruby]

def create
user = User.find_by_email(params[:email])
user.send_password_reset if user
redirect_to root_url, :notice => "Email sent with password reset instructions."
end

def edit
@user = User.find_by_password_reset_token!(params[:id])
end

def update
@user = User.find_by_password_reset_token!(params[:id])
if @user.password_reset_sent_at < 2.hours.ago
redirect_to new_password_reset_path, :alert => "Password reset has expired."
elsif @user.update_attributes(params[:user])
redirect_to root_url, :notice => "Password has been reset!"
else
render :edit
end
end
[/ruby]

sessions/new.html.erb

[html]
<p><%= link_to "forgotten password?", new_password_reset_path %></p>
<div class="field">
<%= check_box_tag :remember_me, 1, params[:remember_me] %>
<%= label_tag :remember_me %>
</div>
[/html]

password_resets/new.html.erb

[html]
<%= form_tag password_resets_path, :method => :post do %>
<div class="field">
<%= label_tag :email %>
<%= text_field_tag :email, params[:email] %>
</div>
<div class="actions"><%= submit_tag "Reset Password" %></div>
<% end %>
[/html]

password_resets/edit.html.erb

[html]
<%= form_for @user, :url => password_reset_path(params[:id]) do |f| %>
<% if @user.errors.any? %>
<div class="error_messages">
<h2>Form is invalid</h2>
<ul>
<% for message in @user.errors.full_messages %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :password %>
<%= f.password_field :password %>
</div>
<div class="field">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>
</div>
<div class="actions"><%= f.submit "Update Password" %></div>
<% end %>
[/html]

config/evinroments/development.rb

[ruby]
config.action_mailer.default_url_options = { :host => "localhost:3000" }
[/ruby]

user_mailer.rb

[ruby]
def password_reset(user)
@user = user
mail :to => user.email, :subject => "Password Reset"
end
[/ruby]

user_mailer/password_reset.text.erb

[html]
To reset your password, click the URL below.

<%= edit_password_reset_url(@user.password_reset_token) %>

If you did not request your password to be reset, just ignore this email and your password will continue to stay the same.
[/html]

Screencast: Geocoder

Geocoder ist ein Ruby-Gem mit welchem man geografischen Daten nutzen, Koordination finden, Abstände zwischen Orten und mehr in Ruby und Rails einsetzen kann. Ryan zeigt diese Woche wie es installiert und in einer Rails-App verwendet wird.

 

Downloads in verschiedenen Formaten:

source code
mp4
m4v
webm
ogv

 

Resourcen:

bash

[bash]
rails g nifty:scaffold location address:string latitude:float longitude:float
rake db:migrate
bundle
[/bash]

Gemfile

[ruby]
gem ‚geocoder‘
[/ruby]

models/location.rb

[ruby]
geocoded_by :address
after_validation :geocode, :if => :address_changed?
[/ruby]

locations_controller.rb

[ruby]
def index
if params[:search].present?
@locations = Location.near(params[:search], 50, :order => :distance)
else
@locations = Location.all
end
end
[/ruby]

locations/show.html.erb

[html]
<%= image_tag "http://maps.google.com/maps/api/staticmap?size=450×300&sensor=false&zoom=16&markers=#{@location.latitude}%2C#{@location.longitude}" %>

<h3>Nearby locations</h3>
<ul>
<% for location in @location.nearbys(10) %>
<li><%= link_to location.address, location %> (<%= location.distance.round(2) %> miles)</li>
<% end %>
</ul>
[/html]

locations/index.html.erb

[html]
<%= form_tag locations_path, :method => :get do %>
<p>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search Near", :name => nil %>
</p>
<% end %>
[/html]