未定义的方法“to_key”对于#<类 :0x17a6408>-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 %>
这是控制器:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
据我所知,您的
form_for
应该类似于我假设您的上传对象嵌套在另一个对象中,类似于以下内容:
当传递这样的数组时,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 之一为零。要进行检查,您可以将以下内容放入您的视图中
,对于 @upload 也是如此,看看哪个是 nil。但是,我猜测 @upload 为零,因为控制器中的这一行:
特别是
unless @uploads.blank?
部分。除非你在ApplicationController中初始化它,否则@uploads总是nil,这意味着@uploads.blank?永远是 true,这又意味着 @upload 永远不会被初始化。更改读取的行,问题有望得到解决。您使用的其他方法也是如此,
除非@uploads.blank?
。在一个半相关的说明中,在 UploadsController#find_parent 中,您有这一行
,因为该变量是 find_parent 方法的本地变量,您可以放心它没有初始化,而应该编写classes = []。
有这行代码
另外,在方法结束之前 。您是否添加了它,以便在 @parent 初始化后从该方法返回?如果是这样,该行应该位于每个块内。
此外,由于类不在方法之外使用,为什么还要定义它呢?代码可以如下所示,并且仍然具有相同的行为
除其他事项外,您将看到这做了一些事情:
unlessvariable.blank
的使用更改为ifvariable
。除非你的变量是布尔值,否则这会完成同样的事情,但会减少认知负担,因为前者本质上是你的大脑必须解析的双重否定。-- 编辑 - 来自有关该问题的电子邮件交换 --
您是正确的 - 如果父级已初始化,
if @parent
将返回 true。然而,正如我在 SO 中提到的,例外情况是 @parent 已初始化并设置为 false。本质上它的意思是在 Ruby 中,除了 nil 和 false 之外的所有值都被认为是 true。当实例变量尚未初始化时,它的默认值为 nil,这就是该行代码起作用的原因。这有道理吗?请记住,@parent 和 @upload 都必须是 ActiveRecord (AR) 对象的实例。在第一种情况下,您将@parent设置为User.all,这是一个AR对象数组,这是行不通的。另外,您尝试在 @parent 初始化之前调用 @parent.uploads ,这将给出 no method 错误。但是,即使您要交换这两行,当parent是一个数组时,您也会调用@parent.uploads。请记住,uploads 方法是在单个 AR 对象上定义的,而不是在它们的数组上定义的。由于索引的所有三个实现都执行类似的操作,因此上述注意事项以各种形式适用于所有这些实现。
快速引导您完成我所做的更改。在开始之前,我应该解释一下,这
与执行此操作等效
,只是一种更短(并且更干净)的渲染方式。同样,在控制器中,您可以执行
以下
操作 : .org/layouts_and_rendering.html 现在来看代码。
在上传表单上,您会看到我将@parent 和@upload 更改为parent 和upload。这意味着您需要在渲染表单时传递变量,而不是让表单查找控制器设置的实例变量。您将看到这允许我们执行以下操作:
在 UsersController#index 中为每个用户添加上传表单。您会注意到,因为我们现在显式传入父级并上传,所以我们可以在同一页面上有多个上传表单。这是一种更干净、更可扩展的嵌入部分的方法,因为父级和上传的设置立即变得显而易见。使用实例变量方法,不熟悉代码库的人可能很难确定@parent和@upload的设置位置等。
这与上面的更改类似,我们传入父对象和上传对象。
您会看到我删除了作为路由中顶级资源的上传。这是因为上传需要某种父级,因此不能是顶级的。
我做了与上面相同的更改,显式传递父级并上传。显然,无论您在何处呈现表单,都需要执行此操作。
我已经从用户控制器中删除了对 @parent 的任何提及,因为我们显式地传递了它。
希望这一切都有道理。您可以从这些示例中进行推断,并在您想要呈现上传表单的任何位置传递父对象和上传对象。
From what I can see, your
form_for
should be something along the lines ofas I'm assuming your upload object is nested within another object, similar to the following:
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 toparent_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
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:
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 readand 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
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
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
Amongst other things, you'll see that this does a few things:
unless variable.blank
toif 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?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.
I'll quickly walk you through the changes I made. Before I start, I should explain that this
is equivalent to doing this
and is just a shorter (and somewhat cleaner) way of rendering. Likewise, in a controller, you can do
instead of
You can read more about this at http://guides.rubyonrails.org/layouts_and_rendering.html Now on to the code.
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:
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.
This is similar to the changes above, where we pass in the parent and upload objects.
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.
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.
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.
[@parent,上传] => [@parent, :upload]
UPD
您应该更改位置
:upload
和@parent
[@parent, Upload] => [@parent, :upload]
UPD
You should change places
:upload
and@parent