我在多态上传表单上遇到未定义方法“ 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”

这是错误跟踪:

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


“要上传/要上传”部分在以下要点中:https://gist.github.com/911635

任何指针都会有所帮助。谢谢

最佳答案

据我所知,您的form_for应该类似于

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


我假设您的上传对象嵌套在另一个对象中,类似于以下内容:

resources :posts do
  resources :uploads
end


当form_for传递这样的数组时,其作用是根据给定对象的类以及它们是否为新记录来构造相关路径。

在您的情况下,您将在控制器的新操作中创建一个新的上载对象,因此form_for将检查该数组,获取@parent的类和ID,然后获取@upload的类和ID。但是,由于@upload没有ID,因此它将POST到/parent_class/parent_id/upload而不是放置到parent_class/parent_id/upload/upload_id

让我知道那是否行不通,我们将进一步解决:)

-编辑-评论后-

这意味着@parent或@upload中的一个为nil。要检查,您可以在视图中放入以下内容

<%= debug @parent %>


和@upload一样,看看哪个为零。但是,由于控制器中的这一行,我猜测@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方法的局部变量,所以可以确保它没有初始化,而应该编写class = []。

另外,您有这行代码

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。除非您的变量是布尔值,否则它会完成相同的事情,但会减轻认知负担,因为前者本质上是您的大脑必须解析的双负数。


-编辑-通过电子邮件交换有关此问题-

您是正确的-如果父级已初始化,if @parent将返回true。正如我在SO上提到的,这是@parent初始化并设置为false的例外。从本质上讲,这意味着在Ruby中,除nil和false外的所有值都被视为true。当实例变量尚未初始化时,其默认值为nil,这就是该行代码起作用的原因。那有意义吗?


就在UsersController中呈现表单的每个动作中设置@parent而言,以下哪一种是对index动作执行此操作的正确方法。我已经尝试了所有3个但出现错误


请记住,@ parent和@upload都必须是ActiveRecord(AR)对象的实例。在第一种情况下,将@parent设置为User.all,这是AR对象的数组,将不起作用。另外,您尝试在@parent初始化之前调用@ parent.uploads,这将导致没有方法错误。但是,即使要交换两行,当parent是数组时,您也会调用@ parent.uploads。请记住,uploads方法是在单个AR对象上定义的,而不是在它们的数组上定义的。由于您的index的所有三个实现都做类似的事情,因此以上警告适用于所有它们的各种形式。


users_controller.rb

定义指数
@upload = @ parent.uploads
@parent = @user = User.all
结束

  or


定义指数
#@user = @ parent.user.all
@parent = @user = User.all
结束

  or


定义指数
@parent = @upload = @ parent.uploads
@users = User.all
结束


我将快速引导您完成所做的更改。在开始之前,我应该解释一下

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


这与上面的更改相似,我们传入了父对象并上传了对象。

 #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的任何提及,因为我们将其明确传递。

希望一切都有意义。您可以从这些示例中推断出结果,并在要呈现上载表单的任何地方通过父对象和上载对象。

关于ruby-on-rails-3 - #<Class:0x17a6408> -rails-3的未定义方法`to_key',我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/5606970/

10-11 22:54
查看更多