对于#<Class:0x17a6408> -rails-3,未定义的方法`to_key'

brg*_*brg 2 methods undefined ruby-on-rails-3

在多态上传的表单上,我遇到了未定义的方法`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 %>
Run Code Online (Sandbox Code Playgroud)

这是控制器:

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
Run Code Online (Sandbox Code Playgroud)

如果我改变了

<%= form_for [@parent, Upload], :html => { :multipart => true } do |f| %>
Run Code Online (Sandbox Code Playgroud)

<%= form_for [parent, Upload], :html => { :multipart => true } do |f| %>
Run Code Online (Sandbox Code Playgroud)

我收到一个新错误:未定义的局部变量或###:0x21a30e0>的方法`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">
Run Code Online (Sandbox Code Playgroud)

"uploads/uploadify"部分在这个要点:https://gist.github.com/911635

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

luk*_*all 7

从我所看到的,你form_for应该是一些东西

<%= form_for [@parent, @upload], :html => { :multipart => true } do |f| %>
Run Code Online (Sandbox Code Playgroud)

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

resources :posts do
  resources :uploads
end
Run Code Online (Sandbox Code Playgroud)

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

在您的情况下,您在控制器的新操作中创建一个新的上传对象,因此form_for将检查数组,获取@parent的类和id,然后获取@upload的类和id.但是,因为@upload没有id,所以它将POST /parent_class/parent_id/upload而不是PUTting parent_class/parent_id/upload/upload_id.

让我知道如果这不起作用,我们将进一步弄清楚:)

- 编辑 - 评论后 -

这意味着@parent或@upload中的一个是nil.要检查,您可以在视图中添加以下内容

<%= debug @parent %>
Run Code Online (Sandbox Code Playgroud)

和@upload一样,看看哪个是零.但是,我猜测@upload是nil,因为你的控制器中有这一行:

# UploadsController#new
@upload = @parent.uploads.new unless @uploads.blank?
Run Code Online (Sandbox Code Playgroud)

特别是unless @uploads.blank?部分.除非你在ApplicationController中初始化它,@ uploads总是为nil,这意味着@ uploads.blank?将永远是真的,这反过来意味着永远不会初始化@upload.将行更改为读取

@upload = @parent.uploads.new
Run Code Online (Sandbox Code Playgroud)

这个问题有望得到解决.您使用过的其他方法也是如此unless @uploads.blank?.

在半相关的注释中,在UploadsController#find_parent中,您有这一行

classes ||= []
Run Code Online (Sandbox Code Playgroud)

因为变量是find_parent方法的本地变量,所以可以确保它没有被初始化,而应该写class = [].

此外,你有这行代码

return unless classes.blank?
Run Code Online (Sandbox Code Playgroud)

就在方法结束之前.你是否添加了这个,以便在初始化@parent后从方法返回?如果是这样,该行应该在每个块内.

此外,由于类不在方法之外使用,为什么要定义它?代码可以如下所示,仍然具有相同的行为

def find_parent
  params.each do |name ,value|
    @parent = $1.pluralize.classify.constantize.find(value) if name =~ /(.*?)_id/
    return if @parent
  end
end
Run Code Online (Sandbox Code Playgroud)

除此之外,你会发现这会做一些事情:

  1. 避免初始化不需要的变量.
  2. 内联if语句,有助于单行条件的可读性
  3. 改变使用unless variable.blankif variable.除非你的变量是一个布尔值,否则这会完成同样的事情,但会减少认知负担,因为前者基本上是你的大脑必须解析的双重否定.

- 编辑 - 关于该问题的电子邮件交流 -

你是对的 - if @parent如果父初始化,将返回true.正如我在SO上提到的那样,例外情况是@parent被初始化并设置为false.基本上它意味着在Ruby中,除了nil和false之外的所有值都被认为是真的.当一个实例变量尚未初始化时,它的默认值为nil,这就是该行代码工作的原因.那有意义吗?

在在UsersController中呈现表单的每个操作中设置@parent,这些是在索引操作上执行此操作的正确方法.我已经尝试了所有3但是有错误

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

users_controller.rb

def index @upload = @ parent.uploads @parent = @user = User.all end

  or
Run Code Online (Sandbox Code Playgroud)

def index#@ user = @ parent.user.all @parent = @user = User.all end

  or
Run Code Online (Sandbox Code Playgroud)

def index @parent = @upload = @ parent.uploads @users = User.all
end

我会尽快向您介绍我所做的更改.在开始之前,我应该解释一下

<%= render "partial_name", :variable1 => a_variable, :variable2 => another_variable %>
Run Code Online (Sandbox Code Playgroud)

相当于这样做

<%= render :partial => "partial_name", :locals => {:variable1 => a_variable, :variable2 => another_variable} %>
Run Code Online (Sandbox Code Playgroud)

并且只是一种更短(并且更清洁)的渲染方式.同样,在控制器中,您可以这样做

render "new"
Run Code Online (Sandbox Code Playgroud)

代替

render :action => "new"
Run Code Online (Sandbox Code Playgroud)

您可以在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%>
Run Code Online (Sandbox Code Playgroud)

在上传表单中,您会看到我将@parent和@upload更改为父级并上传.这意味着您需要在呈现表单时传递变量,而不是查找由控制器设置的实例变量的表单.您会看到这允许我们执行以下操作:

#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>
Run Code Online (Sandbox Code Playgroud)

在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>
Run Code Online (Sandbox Code Playgroud)

这类似于上面的更改,我们传入父对象并上传对象.

 #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
Run Code Online (Sandbox Code Playgroud)

您会看到我将上传内容删除为路由中的顶级资源.这是因为上传需要某种父级,因此不能是顶级.

#app/views/uploads/new.html.erb
<%= render 'form', :parent => @parent, :upload => @upload %>
Run Code Online (Sandbox Code Playgroud)

我做了与上面相同的更改,传递父级并明确上传.显然,无论您在何处呈现表单,都需要这样做.

#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
Run Code Online (Sandbox Code Playgroud)

我已经从用户控制器中删除了对@parent的任何提及,因为我们明确地传递了它.

希望一切都有意义.您可以从这些示例中进行推断,并在您要呈现上载表单的任何位置传递父对象并上传对象.