Из того, что я вижу, ваш form_for
должен быть чем-то вроде
<%= form_for [@parent, @upload], :html => { :multipart => true } do |f| %>
, так как я предполагаю, что ваш загружаемый объект вложен в другой объект, аналогично следующему:
resources :posts do
resources :uploads
end
То, что делает form_for при передаче такого массива, - это создание соответствующего пути на основе класса заданных объектов и того, являются ли они новыми записями.
В вашем случае вы создаете новыйВыгрузите объект в новом действии вашего контроллера, поэтому form_for проверит массив, получит класс и идентификатор @parent, затем получит класс и идентификатор @upload.Однако, поскольку @upload не имеет идентификатора, он будет POST к /parent_class/parent_id/upload
вместо PUTting к parent_class/parent_id/upload/upload_id
.
Дайте мне знать, если это не сработает, и мы разберемся с этим дальше:)
- РЕДАКТИРОВАТЬ - после комментариев -
Это означает, что один из @parent или @upload равен nil.Чтобы проверить, вы можете поставить следующее:
<%= debug @parent %>
и то же самое для @upload и посмотреть, какое значение равно nil.Тем не менее, я предполагаю, что @upload равен nil из-за этой строки в вашем контроллере:
# UploadsController#new
@upload = @parent.uploads.new unless @uploads.blank?
, в частности, часть unless @uploads.blank?
.Если вы не инициализируете его в ApplicationController, @uploads всегда равен nil, что означает @ uploads.blank?всегда будет true, что, в свою очередь, означает, что @upload никогда не будет инициализирован.Измените строку на
@upload = @parent.uploads.new
, и проблема, надеюсь, будет решена.То же самое относится и к другим методам, в которых вы использовали unless @uploads.blank?
.
. В полусвязанной заметке в UploadsController # find_parent есть строка
classes ||= []
, поскольку переменнаяявляется локальным по отношению к методу find_parent, вы можете быть уверены, что он не инициализирован, и вам следует писать классы = [].
Кроме того, у вас есть эта строка кода
return unless classes.blank?
праводо конца метода.Вы добавили это, чтобы вернуться из метода после инициализации @parent?Если это так, эта строка должна быть внутри каждого блока.
Далее, поскольку классы не используются вне метода, зачем вообще его определять?Код может выглядеть следующим образом и при этом иметь то же поведение
def find_parent
params.each do |name ,value|
@parent = $1.pluralize.classify.constantize.find(value) if name =~ /(.*?)_id/
return if @parent
end
end
Помимо прочего, вы увидите, что это делает несколько вещей:
- Избегает инициализации переменной, котораяне требуется.
- Содержит оператор if, который помогает удобочитаемости для однострочных условных выражений
- Изменяет использование
unless variable.blank
на if variable
.Если ваша переменная не является логическим значением, это выполняет то же самое, но снижает когнитивную нагрузку, поскольку первая по сути является двойным отрицательным, который ваш мозг должен проанализировать.
- EDIT - из обмена электронной почтойо проблеме -
Вы правы - if @parent
вернет true, если родительский объект инициализирован.Однако, как я упоминал в SO, исключение составляет случай инициализации @parent и установки значения false.По сути, это означает, что в Ruby все значения, кроме nil и false, считаются истинными.Когда переменная экземпляра не была инициализирована, ее значением по умолчанию является ноль, поэтому эта строка кода работает.Имеет ли это смысл?
С точки зрения установки @parent в каждом действии, которое отображает форму в UsersController, какой из них является правильным способом сделать это в действии index.Я перепробовал все 3, но получил ошибки
Помните, что @parent и @upload должны быть экземплярами объектов ActiveRecord (AR).В первом случае вы устанавливаете @parent в User.all, который является массивом объектов AR, которые не будут работать.Кроме того, вы пытаетесь вызвать @ parent.uploads до инициализации @parent, что не приведет к ошибке метода.Однако, даже если вам нужно поменять две строки, вы вызываете @ parent.uploads, когда parent является массивом.Помните, что метод загрузки определяется для отдельных объектов AR, а не для их массива.Поскольку все три ваши реализации индекса делают похожие вещи, приведенные выше предостережения применимы ко всем из них в различных формах.
users_controller.rb
def index @upload = @parent.добавления@parent = @user = User.all end
or
def index # @user = @ parent.user.all @parent = @user = User.all end
or
def index @parent = @upload = @ parent.uploads @users = User.all
end
Я быстро проведу вас через сделанные мной изменения.Прежде чем начать, я должен объяснить, что этот
<%= render "partial_name", :variable1 => a_variable, :variable2 => another_variable %>
эквивалентен этому
<%= render :partial => "partial_name", :locals => {:variable1 => a_variable, :variable2 => another_variable} %>
и является просто более коротким (и несколько более чистым) способом рендеринга.Аналогично, в контроллере вы можете сделать
render "new"
вместо
render :action => "new"
Подробнее об этом можно прочитать на http://guides.rubyonrails.org/layouts_and_rendering.html Теперь перейдите к коду.
#app/views/users/_form.html.erb
<%= render :partial => "uploads/uploadify" %>
<%= form_for [parent, upload], :html => { :multipart => true } do |f| %>
<div class="field">
<%= f.label :document %><br />
<%= f.file_field :document %>
</div>
<div class="actions">
<%= f.submit "Upload"%>
</div>
<%end%>
В форме загрузки вы увидите, что я изменил @parent и @upload на parent и загружаю.Это означает, что вам нужно передавать переменные при отображении формы, а не при поиске переменной экземпляра, установленной контроллером.Вы увидите, что это позволяет нам делать следующее:
#app/views/users/index.html.erb
<h1>Users</h1>
<table>
<% @users.each do |user| %>
<tr>
<td><%= link_to user.email %></td>
<td><%= render 'uploads/form', :parent => user, :upload => user.uploads.new %></td>
</tr>
<% end %>
</table>
Добавить форму загрузки для каждого пользователя в UsersController # index.Вы заметите, что, поскольку мы теперь явно передаем родительский элемент и загружаем, мы можем иметь несколько форм загрузки на одной странице.Это гораздо более понятный и более расширяемый подход к встраиванию партиалов, так как сразу становится очевидным, на что настроены родитель и загрузка.При подходе к переменной экземпляра люди, незнакомые с базой кода, могут с трудом определить, где устанавливаются @parent и @upload и т. Д.
#app/views/users/show.html.erb
<div>
<% @user.email %>
<h3 id="photos_count"><%= pluralize(@user.uploads.size, "Photo")%></h3>
<div id="uploads">
<%= image_tag @user.upload.document.url(:small)%>
<em>on <%= @user.upload.created_at.strftime('%b %d, %Y at %H:%M') %></em>
</div>
<h3>Upload a Photo</h3>
<%= render "upload/form", :parent => @user, :upload => user.uploads.new %>
</div>
Это похоже на изменения, описанные выше, где мы передаемобъекты parent и upload.
#config/routes.rb
Uploader::Application.routes.draw do
resources :users do
resources :uploads
end
devise_for :users
resources :posts do
resources :uploads
end
root :to => 'users#index'
end
Вы увидите, что я удалил закачки как ресурсы верхнего уровня в маршрутах.Это связано с тем, что для загрузки требуется некоторый родительский элемент, и поэтому он не может быть верхнего уровня.
#app/views/uploads/new.html.erb
<%= render 'form', :parent => @parent, :upload => @upload %>
Я внес те же изменения, что и выше, передавая родительский элемент и явно передавая данные.Очевидно, вам придется делать это везде, где вы визуализируете форму.
#app/controllers/users_controller.rb
class UsersController < ApplicationController
respond_to :html, :js
def index
@users = User.all
end
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
def create
@user = User.new(params[:user])
if @user.save
redirect_to users_path
else
render :action => 'new'
end
end
def update
@user = User.find_by_id(params[:id])
@user.update_attributes(params[:user])
respond_with(@user)
end
def destroy
@user = User.find_by_id(params[:id])
@user.destroy
respond_with(@user)
end
end
Я удалил любое упоминание @parent из пользовательского контроллера, так как мы пропускаем его явно.
Надеюсь, что все имеет смысл.Вы можете экстраполировать из этих примеров и проходить через родительский объект и загружать объект везде, где вы хотите отобразить форму загрузки.