0

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 and yield(:needed_link) with provide(: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
AkaZecik
  • 980
  • 2
  • 11
  • 16
  • 1
    hi and welcome to stack overflow. Normally we prefer you to split up your questions so each question asks only one... question :) But let me start off by answering: `Why are the tests passing even though my site isn't working?` - this happens when your test doesn't actually check the thing that is broken. – Taryn East Oct 06 '16 at 23:42
  • Re your third question: when you have no authentication, you can't tell the difference between a logged-in and non-logged-in user... so any user can do anything... until you add proper authentication. Isn't this matching exactly what you have so far done? if not how does it differ from what you expect? – Taryn East Oct 06 '16 at 23:44
  • 1
    @TarynEast I will try better next time and either create separate questions or rewrite my monologue, so that it states only one question ;) referring to your answer, do you see at glance what is not tested? I have spent quite some time going through the code and I didn't find the reason – AkaZecik Oct 06 '16 at 23:47
  • ok, it'd help us if you can give us a bit more infor about the error. Right now you've given us the raw error message... but I don't know *which* test is failing (successful or unsuccessful) or which line of code is causing the error. Usually test output of an error includes a few other lines that indicate which line of code the test was up to when it failed (it will look like a list of filenames with numbers after them). If you can copy/paste that into your question it'd be really helpful :) – Taryn East Oct 07 '16 at 00:00
  • 1
    My tests never failed (only the one for signing up, due to the missing signup_path) or returned any errors. Only the site displays the error it browser, when 'providing' a @user (first scenario). The question remains: why is the test able to update the user, even though I can't? Sorry for countless edits of my comments, btw :) – AkaZecik Oct 07 '16 at 00:20
  • ok, so the error is in the site... which means you need to look into your server logs (`log/development.log`) or your terminal window to see the stacktrace (which is the list of files with numbers). – Taryn East Oct 07 '16 at 02:26

1 Answers1

0
<% provide(:needed_link, @user) %>

@user isn't a link - it is a user instance. You can pass an instance of a model to a link-generating-method (eg render in a controller or link_to in a template) and the method uses Rails-magic* to turn the model into a link... but it isn't a link of itself, so if you try to use it in a place that isn't one of the special (unwritten) methods... it won't work.

This might be why the :link_needed stuff isn't working the way you'd expect.

The url for a form is probably just not one of those methods... so you need to actually create the link using the edit_user_path(@user) method instead.

* behind-the-scenes, some rails methods call url_for on a model to turn it into a proper link - but only some methods.

Taryn East
  • 27,486
  • 9
  • 86
  • 108
  • Well, putting `@user` directly into `form_for` results in a working site. Taking into account this remark and your answer, does it mean that `provide` or/and `yield` methods don't accept model objects AND `form_for` method is capable of generating a url/path from a model object when assigned to `:url` key? – AkaZecik Oct 07 '16 at 00:10
  • `Well, putting @user directly into form_for results in a working site.` that's because `form_for` is one of those magic methods that uses `url_for` behind the scenes :) But I haven't tried doing that by passing it as `:url` so if you tested it and it doesn't work, then I'd guess that's your answer... I suspect it doesn't because `:url` expects a fully-formed url, whereas the first argument to `form_for` can be anything that can be turned into a url with `url_for` (eg a model) – Taryn East Oct 07 '16 at 02:22
  • I think that the `provide` and `yield` parts are irrelevant to this... they are just a layer of indirection that are unrelated to the actual issue. The main issue is whether you use `:url` vs `form_for` for your `@user` variable. – Taryn East Oct 07 '16 at 02:25
  • When I put `@user` without `url:`, it returns an error telling me it cannot draw a form due to the missing attribute. When I put them together, site works perfectly. Problem appears only when I combine `url`, `yield` and `provide` (the last one with `@user` instead of `user_path(@user)`). I checked server log and there are no file references. It says only "Routing error, No route matches" /// side note: should I rewrite main question and change the structure to remove irrelevant things and add my new point of view? I'm asking because there is too much talking in comments, imo. I'm still new :) – AkaZecik Oct 07 '16 at 03:45
  • You haven't shown us the "missing attribute" error (or mentioned it before now) so I'm not sure what that error is - it may (or may not) help if you do... please show us a few lines either side of the error (from the server log) even so... you never know what might be useful in that and if we can't see it, we can't tell if it's useful :) I recommend not re-writing your question (it then makes some comments make no sense because they refer to stuff that's gone) - just adding new stuff to the bottom - which lets us see the full context of the conversation. – Taryn East Oct 07 '16 at 04:24