Вставка yield() в form_for() вызывает ошибку, не обнаруженную тестом

#ruby-on-rails #railstutorial.org #yield #form-for

#ruby-on-rails #railstutorial.org #yield #form-for

Вопрос:

Я следую http://railstutorial.org . Я закончил все до исключения раздела 10.2. Затем я столкнулся с какой-то проблемой. Ниже приведены мои текущие коды приложений.

Упражнение 2 в разделе 7.3.4 заставило меня добавить дополнительный код в приложение. В результате мне пришлось обновить маршруты и создать собственное решение для упражнения 2 в разделе 10.1.1 (автор предложил использовать yield provide методы и, что я и сделал — см. в codes ). После этого я создал тест для успешного и неудачного редактирования пользователя (раздел 10.1.3 и 10.1.4). Оба теста прошли, но мое приложение работало не так, как ожидалось.

Описание проблемы: Когда я вхожу в систему (хотя это и не обязательно, авторизация является целью раздела 10.2) и перехожу на страницу редактирования / users/:id /edit, сайт работает. Когда я нажимаю кнопку «Сохранить изменения», она возвращает ошибку (независимо от того, какого пользователя я пытаюсь отредактировать):

 No route matches [PATCH] "/users/1/edit"
Rails.root: /home/akazecik/workspace/sample_app
  

и все тесты проходят.

С другой стороны, когда я заменяю @user на user_path(@user) in edit.html.erb file , ошибка исчезает, и сайт работает нормально. Все тесты по-прежнему проходят.

В «третьей руке», когда я заменяю yield(:needed_link) на @user in _form.html.erb file (и, следовательно, опускаю использование yield метода из первого случая) и игнорирую непрохождение теста (очевидная вещь):

 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>'
  

Я снова получаю рабочий сайт, и остальные тесты, похоже, проходят.


Итак, мои вопросы:

  • Почему тесты проходят, хотя мой сайт не работает? Почему users_edit_test.rb возможность обновить пользователя, хотя я не могу?
  • В чем разница между @user и yield(:needed_link) с provide(:needed_link, @user) ?
  • В самом начале раздела 10.2 мы читаем, что действия редактируют и обновляют:

они позволяют любому (даже не вошедшим в систему пользователям) получить доступ к любому действию, и любой зарегистрированный пользователь может обновить информацию для любого другого пользователя

Это не так, потому что я могу ОБНОВЛЯТЬ информацию даже как не вошедший в систему пользователь. Чего мне не хватает? Под logged-in я подразумеваю, что logged_in ? метод в app/helpers/sessions_helper.rb возвращает 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 amp;amp; 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
  

Комментарии:

1. привет и добро пожаловать в stack overflow. Обычно мы предпочитаем, чтобы вы разделяли свои вопросы так, чтобы каждый вопрос задавал только один… вопрос 🙂 Но позвольте мне начать с ответа: Why are the tests passing even though my site isn't working? — это происходит, когда ваш тест на самом деле не проверяет то, что сломано.

2. Повторите ваш третий вопрос: когда у вас нет аутентификации, вы не можете отличить вошедшего в систему и не вошедшего в систему пользователя… таким образом, любой пользователь может делать что угодно… пока вы не добавите правильную аутентификацию. Разве это не соответствует тому, что вы уже сделали? если нет, то чем это отличается от того, что вы ожидаете?

3. @TarynEast В следующий раз я постараюсь лучше и либо создам отдельные вопросы, либо перепишу свой монолог так, чтобы в нем был указан только один вопрос 😉 ссылаясь на ваш ответ, вы сразу видите, что не проверено? Я потратил довольно много времени на просмотр кода и не нашел причину

4. хорошо, нам помогло бы, если бы вы могли предоставить нам немного больше информации об ошибке. Прямо сейчас вы выдали нам необработанное сообщение об ошибке… но я не знаю, какой тест не выполняется (успешный или неудачный) или какая строка кода вызывает ошибку. Обычно тестовый вывод ошибки включает в себя несколько других строк, которые указывают, до какой строки кода тест выполнялся на момент сбоя (это будет выглядеть как список имен файлов с номерами после них). Если вы можете скопировать / вставить это в свой вопрос, это было бы действительно полезно 🙂

5. Мои тесты никогда не завершались неудачно (только для регистрации из-за отсутствия пути регистрации) или возвращали какие-либо ошибки. Только сайт отображает ошибку в браузере, когда «предоставляет» @user (первый сценарий). Остается вопрос: почему тест может обновлять пользователя, хотя я не могу? Извините за бесчисленные правки моих комментариев, кстати 🙂

Ответ №1:

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

@user это не ссылка — это экземпляр пользователя. Вы можете передать экземпляр модели методу, генерирующему ссылки (например render , в контроллере или link_to в шаблоне), и метод использует Rails-magic * для превращения модели в ссылку… но это не ссылка сама по себе, поэтому, если вы попытаетесь использовать ее в месте, которое не является одним из специальных (неписаных) методов… это не сработает.

Возможно, именно поэтому :link_needed материал работает не так, как вы ожидаете.

url For a form, вероятно, просто не является одним из этих методов… поэтому вам нужно фактически создать ссылку, используя edit_user_path(@user) вместо этого метод.

* за кулисами некоторые методы rails вызывают url_for модель, чтобы превратить ее в правильную ссылку, но только некоторые методы.

Комментарии:

1. Ну, ввод @user непосредственно в form_for результаты на рабочем сайте. Принимая во внимание это замечание и ваш ответ, означает ли это, что provide методы or / and yield не принимают объекты модели, А form_for метод способен генерировать URL / путь из объекта модели при назначении :url ключу?

2. Well, putting @user directly into form_for results in a working site. это потому form_for , что это один из тех волшебных методов, которые используются url_for за кулисами 🙂 Но я не пробовал делать это, передавая его как :url so, если вы протестировали его, и он не работает, тогда я бы предположил, что это ваш ответ… Я подозреваю, что это не так, потому :url что ожидает полностью сформированный URL-адрес, тогда как первым аргументом to form_for может быть все, что можно превратить в URL url_for -адрес (например, модель)

3. Я думаю, что provide yield части и не имеют к этому отношения… это всего лишь уровень косвенности, который не связан с реальной проблемой. Основная проблема заключается в том, используете ли вы :url vs form_for для своей @user переменной.

4. Когда я помещаю @user without url: , он возвращает ошибку, сообщающую мне, что он не может нарисовать форму из-за отсутствующего атрибута. Когда я собираю их вместе, сайт работает отлично. Проблема появляется только при объединении url yield и provide (последнее с @user вместо user_path(@user) ). Я проверил журнал сервера, и там нет ссылок на файлы. В нем говорится только «Ошибка маршрутизации, маршрут не совпадает» /// примечание: должен ли я переписать основной вопрос и изменить структуру, чтобы удалить ненужные вещи и добавить свою новую точку зрения? Я спрашиваю, потому что в комментариях слишком много разговоров, imo. Я все еще новичок 🙂

5. Вы не показали нам ошибку «отсутствующий атрибут» (или упоминали об этом раньше), поэтому я не уверен, что это за ошибка — она может (или не может) помочь, если вы это сделаете … пожалуйста, покажите нам несколько строк по обе стороны от ошибки (из журнала сервера), даже если это так …вы никогда не знаете, что может быть полезным в этом, и если мы этого не видим, мы не можем сказать, полезно ли это 🙂 Я рекомендую не переписывать ваш вопрос (тогда некоторые комментарии не имеют смысла, потому что они относятся к тому, что ушло) — просто добавьте новый материал внизу, что позволяет нам увидеть полный контекст разговора.