未定义的方法“to_key”对于#<类 :0x17a6408>-rails-3

发布于 2024-10-31 21:56:36 字数 2367 浏览 0 评论 0原文

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

,我会收到一个新错误:#<#: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">

“uploads/uploadify”部分在此要点中: https://gist.github.com /911635

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

I am experiencing problems with undefined method `to_key' for, on a form for polymorphic upload.

This is the form partial:

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

This is the controller:

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

If i change

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

to

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

I get a new error: undefined local variable or method `parent' for #<#:0x21a30e0>

This is the error trace:

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

The "uploads/uploadify" partial is in this gist: https://gist.github.com/911635

Any pointers will be helpful. Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

葬花如无物 2024-11-07 21:56:36

据我所知,您的 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,而不是PUTting到parent_class/parent_id/upload/upload_id

如果这不起作用,请告诉我,我们会进一步解决:)

- 编辑 - 评论后 -

这意味着 @parent 或 @upload 之一为零。要进行检查,您可以将以下内容放入您的视图中

<%= debug @parent %>

,对于 @upload 也是如此,看看哪个是 nil。但是,我猜测 @upload 为零,因为控制器中的这一行:

# UploadsController#new
@upload = @parent.uploads.new unless @uploads.blank?

特别是 unless @uploads.blank? 部分。除非你在ApplicationController中初始化它,否则@uploads总是nil,这意味着@uploads.blank?永远是 true,这又意味着 @upload 永远不会被初始化。更改读取的行

@upload = @parent.uploads.new

,问题有望得到解决。您使用的其他方法也是如此,除非@uploads.blank?

在一个半相关的说明中,在 UploadsController#find_parent 中,您有这一行

classes ||= []

,因为该变量是 find_parent 方法的本地变量,您可以放心它没有初始化,而应该编写classes = []。

有这行代码

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. unlessvariable.blank 的使用更改为 ifvariable。除非你的变量是布尔值,否则这会完成同样的事情,但会减少认知负担,因为前者本质上是你的大脑必须解析的双重否定。

-- 编辑 - 来自有关该问题的电子邮件交换 --

您是正确的 - 如果父级已初始化,if @parent 将返回 true。然而,正如我在 SO 中提到的,例外情况是 @parent 已初始化并设置为 false。本质上它的意思是在 Ruby 中,除了 nil 和 false 之外的所有值都被认为是 true。当实例变量尚未初始化时,它的默认值为 nil,这就是该行代码起作用的原因。这有道理吗?

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

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

users_controller.rb

定义索引
@upload = @parent.uploads
@parent = @user = User.all
结束

<前><代码>或

定义索引
# @user = @parent.user.all
@parent = @user = User.all
结束

<前><代码>或

定义索引
@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"

操作 : .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 和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>

在 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 的任何提及,因为我们显式地传递了它。

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

From what I can see, your form_for should be something along the lines of

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

as I'm assuming your upload object is nested within another object, similar to the following:

resources :posts do
  resources :uploads
end

What form_for does when passed an array like this is construct the relevant path based on the class of the given objects and whether they are new records.

In your case, you create a new upload object in the new action of your controller, so form_for will inspect the array, get the class and id of @parent, then get the class and id of @upload. However, because @upload has no id, it will POST to /parent_class/parent_id/upload instead of PUTting to parent_class/parent_id/upload/upload_id.

Let me know if that doesn't work and we'll figure it out further :)

-- EDIT - after comments --

This means that one of @parent or @upload is nil. To check, you can put the following in your view

<%= debug @parent %>

and the same for @upload and see which is nil. However, I'm guessing that @upload is nil, because of this line in your controller:

# UploadsController#new
@upload = @parent.uploads.new unless @uploads.blank?

specifically the unless @uploads.blank? part. Unless you initialize it in the ApplicationController, @uploads is always nil, which means @uploads.blank? will always be true, which in turn means @upload will never be initialized. Change the line to read

@upload = @parent.uploads.new

and the problem will hopefully be resolved. The same is true of the other methods where you have used unless @uploads.blank?.

On a semi-related note, in UploadsController#find_parent, you have this line

classes ||= []

because the variable is local to the find_parent method, you can be assured that it is not initialized, and should rather write classes = [].

Also, you have this line of code

return unless classes.blank?

right before the end of the method. Did you add that in so that you return from the method once @parent has been initialized? If so, that line should be inside the each block.

Further, since classes isn't used outside of the method, why define it at all? The code could read as follows and still have the same behaviour

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

Amongst other things, you'll see that this does a few things:

  1. Avoids initializing a variable that is not needed.
  2. Inlines the if statement, which helps readability for single line conditionals
  3. Changes use of unless variable.blank to if variable. Unless your variable is a boolean, this accomplishes the same thing, but reduces the cognitive load, as the former is essentially a double negative which your brain has to parse.

-- EDIT - from email exchange about the issue --

You are correct - if @parent will return true if parent is initialized. As I mentioned on SO however, the exception to this is if @parent is initialized and set to false. Essentially what it means is that in Ruby, all values except nil and false are considered true. When an instance variable has not been initialized, it's default value is nil, which is why that line of code works. Does that make sense?

In terms of setting @parent in each action that renders form in the UsersController, which of these is the correct way to do this on the index action. I have tried all 3 but got errors

Remember that both @parent and @upload must be instances of ActiveRecord (AR) objects. In the first case, you set @parent to User.all, which is an array of AR objects, which will not work. Also, you try to call @parent.uploads before @parent is initialized, which will give a no method error. However, even if you were to swap the two lines around, you are calling @parent.uploads when parent is an array. Remember that the uploads method is defined on individual AR objects, and not on an array of them. Since all three of your implementations of index do similar things, the above caveats apply to all of them in various forms.

users_controller.rb

def index
@upload = @parent.uploads
@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

I'll quickly walk you through the changes I made. Before I start, I should explain that this

<%= render "partial_name", :variable1 => a_variable, :variable2 => another_variable %>

is equivalent to doing this

<%= render :partial => "partial_name", :locals => {:variable1 => a_variable, :variable2 => another_variable} %>

and is just a shorter (and somewhat cleaner) way of rendering. Likewise, in a controller, you can do

render "new"

instead of

render :action => "new"

You can read more about this at http://guides.rubyonrails.org/layouts_and_rendering.html Now on to the code.

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

On the uploads form, you'll see that I changed @parent and @upload to parent and upload. This means you need to pass the variables in when you render the form instead of the form looking for instance variable set by the controller. You'll see that this allows us to do the following:

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

Add an upload form for each user in UsersController#index. You'll notice that because we now explicitly pass in parent and upload, we can have multiple upload forms on the same page. This is a much cleaner and more extensible approach to embedding partials, as it becomes immediately obvious what parent and upload are being set to. With the instance variable approach, people unfamiliar with the code base might struggle to determine where @parent and @upload are being set, etc.

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

This is similar to the changes above, where we pass in the parent and upload objects.

 #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

You'll see that I removed uploads as a top level resources in the routes. This is because uploads requires a parent of some sort, and so cannot be top level.

#app/views/uploads/new.html.erb
<%= render 'form', :parent => @parent, :upload => @upload %>

I made the same changes as above, passing parent and upload through explicitly. You'll obviously need to do this wherever you render the form.

#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

I've removed any mention of @parent from the user controller, as we pass it through explicitly.

Hopefully that all makes sense. You can extrapolate from these examples and pass through the parent and upload object wherever you want to render an upload form.

烟─花易冷 2024-11-07 21:56:36

[@parent,上传] => [@parent, :upload]

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

UPD

您应该更改位置 :upload@parent

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

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

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

UPD

You should change places :upload and @parent

<%= form_for [:upload, @parent], :html => { :multipart => true } do |f|  %>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文