неопределенный метод `to_key 'для #-rails-3 - PullRequest
2 голосов
/ 09 апреля 2011

У меня возникли проблемы с неопределенным методом `to_key 'для формы полиморфной загрузки.

Это частичная форма:

<%= 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 %>

Это контроллер:

class UploadsController < ApplicationController
  before_filter :find_parent

  respond_to :html, :js

  def index
    @uploads = @parent.uploads.all unless @uploads.blank?
    respond_with([@parent, @uploads])
  end

  def new
    @upload = @parent.uploads.new unless @uploads.blank?
  end

  def show
    @upload = @parent.upload.find(params[:upload_id])
  end

  def create
    # Associate the correct MIME type for the file since Flash will change it
    if  params[:Filedata]
      @upload.document = params[:Filedata]
      @upload.content_type = MIME::Types.type_for(@upload.original_filename).to_s
      @upload = @parent.uploads.build(params[:upload])
      if @upload.save
        flash[:notice] = "suceessfully saved upload"
        redirect_to [@parent, :uploads]
      else
        render :action => 'new'
      end
    end
  end

  def edit
    @upload = Upload.where(params[:id])
  end
  private


  def find_parent
    classes ||= []
    params.each do |name ,value|
      if name =~ /(.*?)_id/
        @parent =  classes << $1.pluralize.classify.constantize.find(value)
      end
    end
    return unless classes.blank?
  end
end

Если я изменюсь

<%= form_for [@parent, Upload], :html => { :multipart => true } do |f| %>

до

<%= form_for [parent, Upload], :html => { :multipart => true } do |f| %>

Я получаю новую ошибку: неопределенная локальная переменная или метод `parent 'для # <#: 0x21a30e0>

Это трассировка ошибки:

ActionView::Template::Error (undefined method `to_key' for #<Class:0x2205e88>):
1: <%= render :partial => "uploads/uploadify" %>
2: 
3: <%= form_for [@parent, Upload], :html => { :multipart => true } do |f|  %>
4: 
5: 
6:  <div class="field">

Часть "uploads / uploadify" находится в этой сущности: https://gist.github.com/911635

Любые указатели будут полезны. Спасибо

Ответы [ 2 ]

7 голосов
/ 10 апреля 2011

Из того, что я вижу, ваш 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

Помимо прочего, вы увидите, что это делает несколько вещей:

  1. Избегает инициализации переменной, котораяне требуется.
  2. Содержит оператор if, который помогает удобочитаемости для однострочных условных выражений
  3. Изменяет использование 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 из пользовательского контроллера, так как мы пропускаем его явно.

Надеюсь, что все имеет смысл.Вы можете экстраполировать из этих примеров и проходить через родительский объект и загружать объект везде, где вы хотите отобразить форму загрузки.

0 голосов
/ 09 апреля 2011

[@ parent, Upload] => [@parent,: upload]

<%= form_for [@parent, :upload], :html => { :multipart => true } do |f|  %>

UPD

Вы должны поменяться местами :upload и @parent

<%= form_for [:upload, @parent], :html => { :multipart => true } do |f|  %>
...