#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 / andyield
не принимают объекты модели, А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-адрес, тогда как первым аргументом toform_for
может быть все, что можно превратить в URLurl_for
-адрес (например, модель)3. Я думаю, что
provide
yield
части и не имеют к этому отношения… это всего лишь уровень косвенности, который не связан с реальной проблемой. Основная проблема заключается в том, используете ли вы:url
vsform_for
для своей@user
переменной.4. Когда я помещаю
@user
withouturl:
, он возвращает ошибку, сообщающую мне, что он не может нарисовать форму из-за отсутствующего атрибута. Когда я собираю их вместе, сайт работает отлично. Проблема появляется только при объединенииurl
yield
иprovide
(последнее с@user
вместоuser_path(@user)
). Я проверил журнал сервера, и там нет ссылок на файлы. В нем говорится только «Ошибка маршрутизации, маршрут не совпадает» /// примечание: должен ли я переписать основной вопрос и изменить структуру, чтобы удалить ненужные вещи и добавить свою новую точку зрения? Я спрашиваю, потому что в комментариях слишком много разговоров, imo. Я все еще новичок 🙂5. Вы не показали нам ошибку «отсутствующий атрибут» (или упоминали об этом раньше), поэтому я не уверен, что это за ошибка — она может (или не может) помочь, если вы это сделаете … пожалуйста, покажите нам несколько строк по обе стороны от ошибки (из журнала сервера), даже если это так …вы никогда не знаете, что может быть полезным в этом, и если мы этого не видим, мы не можем сказать, полезно ли это 🙂 Я рекомендую не переписывать ваш вопрос (тогда некоторые комментарии не имеют смысла, потому что они относятся к тому, что ушло) — просто добавьте новый материал внизу, что позволяет нам увидеть полный контекст разговора.