I just finished Section 9.3 - Unsuccessful Edits of Michael Hartl's book Ruby on Rails Tutorial. I've been trying for hours to figure out why I can't get the test to pass that checks for an unsuccessful edit.
Here is the code I've added for the chapter:
spec/requests/authentication_pages_spec.rb
require 'spec_helper'
describe "Authentication" do
subject { page }
describe "signin page" do
before { visit signin_path }
it { should have_selector('h1', text: 'Sign in') }
it { should have_selector('title', text: 'Sign in') }
end
describe "signin" do
before { visit signin_path }
describe "with invalid information" do
before { click_button "Sign in" }
it { should have_selector('title', text: 'Sign in') }
it { should have_error_message('Invalid') }
describe "after visiting another page" do
before { click_link "Home" }
it { should_not have_error_message }
end
end
describe "with valid information" do
let(:user) { FactoryGirl.create(:user) }
before { sign_in user }
it { should have_selector('title', text: user.name) }
it { should have_link('Profile', href: user_path(user)) }
it { should have_link('Settings', href: edit_user_path(user)) }
it { should have_link('Sign out', href: signout_path) }
it { should_not have_link('Sign in', href: signin_path) }
describe "followed by signout" do
before { click_link "Sign out" }
it { should have_link('Sign in') }
end
end
end
end
spec/requests/user_pages_spec.rb:
require 'spec_helper'
describe "User pages" do
subject { page }
describe "signup page" do
before { visit signup_path }
it { should have_selector('h1', text: 'Sign up') }
it { should have_selector('title', text: 'Sign up') }
end
describe "profile page" do
let(:user) { FactoryGirl.create(:user) }
before { visit user_path(user) }
it { should have_selector('h1', text: user.name) }
it { should have_selector('title', text: user.name) }
end
describe "signup" do
before { visit signup_path }
let(:submit) { "Create my account" }
describe "with invalid information" do
it "should not create a user" do
expect { click_button submit }.not_to change(User, :count)
end
describe "after submission" do
before { click_button submit }
it { should have_selector('title', text: 'Sign up') }
it { should have_content("Name can't be blank") }
it { should have_content("Email can't be blank") }
it { should have_content("Email is invalid") }
it { should have_content("Password can't be blank") }
it { should have_content("Password is too short") }
it { should have_content("Password confirmation can't be blank") }
end
end
describe "with valid information" do
before do
fill_in "Name", with: "Example User"
fill_in "Email", with: "[email protected]"
fill_in "Password", with: "foobar"
fill_in "Confirmation", with: "foobar"
end
it "should create a user" do
expect { click_button submit }.to change(User, :count).by(1)
end
describe "after saving the user" do
before { click_button submit }
let(:user) { User.find_by_email('[email protected]') }
it { should have_selector('title', text: user.name) }
it { should have_selector('div.alert.alert-success', text: 'Welcome') }
it { should have_link('Sign out') }
end
end
end
describe "edit" do
let(:user) { FactoryGirl.create(:user) }
before { visit edit_user_path(user) }
describe "page" do
it { should have_selector('h1', text: "Update your profile") }
it { should have_selector('title', text: "Edit user") }
it { should have_link('change', href: 'http://gravatar.com/emails') }
end
describe "with invalid information" do
before { click_button "Save changes" }
it { should have_content('error') }
end
end
end
spec/support/utilities.rb:
include ApplicationHelper
def full_title(page_title)
base_title = "Ruby on Rails Tutorial Sample App"
if page_title.empty?
base_title
else
"#{base_title} | #{page_title}"
end
end
def valid_signin(user)
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
def sign_in(user)
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
# Sign in when not using Capybara as well.
cookies[:remember_token] = user.remember_token
end
RSpec::Matchers.define :have_error_message do |message|
match do |page|
page.should have_selector('div.alert.alert-error', text: message)
end
end
app/controllers/users_controllers.rb:
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
def create
@user = User.new(params[:user])
if @user.save
sign_in @user
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
end
def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
if @user.update_attributes(params[:user])
# Handle a successful update.
else
render 'edit'
end
end
end
app/models/users.rb
class User < ActiveRecord::Base
attr_accessible :email, :name, :password, :password_confirmation
has_secure_password
before_save { self.email.downcase! }
before_save :create_remember_token
# Validate that a name is not blank and is no longer than50 characters
validates :name, presence: true, length: { maximum: 50 }
# Validate that an email address is not blank, contains a valid pattern, and
# is not already in the database
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
#Validate the password is there and is at least 6 characters
validates :password, length: { minimum: 6 }, on: :create
validates :password_confirmation, presence: true, on: :create
# Everything below this private is only visibile to this user model
private
# Creates a remember token for the user (not a local variable)
# Used to keep a user login active indefinitely after sign in
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<%= link_to "sample app", root_path, id: "logo" %>
<nav>
<ul class="nav pull-right">
<li><%= link_to "Home", root_path %></li>
<li><%= link_to "Help", help_path %></li>
<% if signed_in? %>
<li><%= link_to "Users", '#' %></li>
<li id="fat-menu" class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
Account <b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><%= link_to "Profile", current_user %></li>
<li><%= link_to "Settings", edit_user_path(current_user) %></li>
<li class="divider"></li>
<li>
<%= link_to "Sign out", signout_path, method: "delete" %>
</li>
</ul>
</li>
<% else %>
<li><%= link_to "Sign in", signin_path %></li>
<% end %>
</ul>
</nav>
</div>
</div>
</header>
app/views/users/edit.html.erb:
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for(@user) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Confirm Password" %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Save changes", class: "btn btn-large btn-primary" %>
<% end %>
<%= gravatar_for @user %>
<a href="http://gravatar.com/emails">change</a>
</div>
</div>
I'm receiving the following errors:
Failures:
1) User pages edit with invalid information
Failure/Error: before { click_button "Save changes" }
ActionView::MissingTemplate:
Missing template users/update, application/update with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :coffee]}. Searched in:
* "/home/cbachich/Development/rails_projects/sample_app/app/views"
# (eval):2:in `click_button'
# ./spec/requests/user_pages_spec.rb:79:in `block (4 levels) in <top (required)>'
Finished in 2.65 seconds
67 examples, 1 failure
Failed examples:
rspec ./spec/requests/user_pages_spec.rb:81 # User pages edit with invalid information
Your controller doesn't do anything in the case of a successful user update, which means Rails is going to render the users/update view. Since you don't have this view, that's the error you see. Saverio's note is good - you should add a redirect_to to get to the show action. That'll get rid of this error.
But there's a bigger problem. Why is your controller taking the successful update path when you expect it to take the fail path and render the edit view?
Based on my experience, the edit page fills in the email and email but leaves the passwords blank. You're expecting the update to fail because there are no passwords, but the update succeeds. Why? If you look at the model, your password validations are only on: :create. This means these validations aren't performed on the update. You're expecting the password validations to be performed on the update, but they aren't, which is why the edit succeeds instead of fails.
If you update the model to perform those validations for the create and update actions (probably something like on: { :create, :update } ), you should be back in business. Your update with invalid data will fail like it should.
Do you have a template for the UsersController's update action?
It should be in app/view/users/update.html.erb.
On a side note, normally one ends up redirecting on a successful update, so you may not need that template, if you add a proper redirect_to in the update action.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With