I'm following the http://railstutorial.org. I finished everything up to 10.2 section excluded. Then I encountered some kind of a problem. My current application codes are below.
Exercise 2 in section 7.3.4 made me add some additional code to application. As a result I had to update routes and create my very own solution to exercise 2 in section 10.1.1 (author suggested using yield
and provide
methods, which I did - seen in codes). After doing so I created a test for successful and unsuccessful edit of user (section 10.1.3 and 10.1.4). Both tests passed, but my application wasn't working as expected.
Problem description: When I log in (not required though, authorization is a goal of 10.2 section) and go to edit page /users/:id/edit, site works. When I press "Save changes" button, it returns an error (no matter which user I try to edit):
No route matches [PATCH] "/users/1/edit"
Rails.root: /home/akazecik/workspace/sample_app
and all tests pass.
On the other hand, when I replace @user
with user_path(@user)
in edit.html.erb
file, the error is gone and site works fine. All test still pass.
On the 'third hand' when I replace yield(:needed_link)
with @user
in _form.html.erb
file (and therefore omit the use of yield
method from first case) and ignore non-passing test (obvious thing):
FAIL["test_invalid_signup_information", UsersSignupTest, 0.5256564110004547]
test_invalid_signup_information#UsersSignupTest (0.53s)
Expected at least 1 element matching "form[action="/signup"]", found 0..
Expected 0 to be >= 1.
test/integration/users_signup_test.rb:6:in `block in <class:UsersSignupTest>'
I again get working site and the rest of tests seem to pass.
So my questions are:
- Why are the tests passing even though my site isn't working? Why is the
users_edit_test.rb
able to update the user, even though I can't? - What is the difference between
@user
andyield(:needed_link)
withprovide(:needed_link, @user)
? - Right at the beginning of section 10.2 we read that actions edit and update:
they allow anyone (even non-logged-in users) to access either action, and any logged-in user can update the information for any other user
That is not the case, because I can UPDATE information even as a non-logged-in user. What am I missing? By logged-in, I mean that logged_in? method in app/helpers/sessions_helper.rb
returns true.
/app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<% provide(:button_text, 'Create my account') %>
<% provide(:needed_link, signup_path) %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= render 'form' %>
</div>
</div>
/app/views/users/edit.html.erb
<% provide(:title, 'Edit user') %>
<% provide(:button_text, 'Save changes') %>
<% provide(:needed_link, @user) %>
<h1>Update your profile</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= render 'form' %>
<div class="gravatar_edit">
<%= gravatar_for @user %>
<a href="http://gravatar.com/emails" target="_blank">Change</a>
</div>
</div>
</div>
/app/views/users/_form.html.erb
<%= form_for(@user, url: yield(:needed_link)) do |f| %>
<%= render 'shared/error_messages', object: @user %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>
app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
def create
@user = User.new(user_params) # Not the final implementation!
if @user.save
log_in @user
flash[:success] = "Welcome to the Sample App!"
redirect_to user_url(@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(user_params)
flash[:success] = "Profile updated"
redirect_to user_path(@user)
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
test/integration/users_edit_test.rb
require 'test_helper'
class UsersEditTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
test "unsuccessful edit" do
get edit_user_path(@user)
assert_template 'users/edit'
patch user_path(@user), params: {user: { name: "", email: "foo@invalid", password: "foo", password_confirmation: "bar" } }
assert_template 'users/edit'
assert_select 'div.alert', "The form contains 4 errors."
end
test "successful edit" do
get edit_user_path(@user)
assert_template 'users/edit'
name = "Foo Bar"
email = "foo@bar.com"
patch user_path(@user), params: { user: { name: name,
email: email,
password: "",
password_confirmation: "" } }
assert_not flash.empty?
assert_redirected_to @user
@user.reload
assert_equal name, @user.name
assert_equal email, @user.email
end
end
test/fixtures/users.yml
michael:
name: Michael Example
email: michael@example.com
password_digest: <%= User.digest('password') %>
app/helpers/sessions_helper.rb
module SessionsHelper
# Logs in the given user.
def log_in(user)
session[:user_id] = user.id
end
# Remembers a user in a persistent session.
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
# Returns the current logged-in user (if any).
def current_user
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])
log_in user
@current_user = user
end
end
end
# Returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
# Forgets a persistent session.
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
# Logs out the current user.
def log_out
forget(current_user)
session.delete(:user_id)
@current_user = nil
end
end