2011-04-09 103 views
2

我在遇到未定義的方法`to_key'時遇到了多態上傳表單的問題。未定義的方法`to_key'爲#<Class:0x17a6408> -rails-3

這是形式局部:

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

這是錯誤跟蹤:

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

的‘上傳/ uploadify’部分是在這要點:https://gist.github.com/911635

任何指針都會有幫助。由於

回答

7

從什麼我可以看到,你的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一樣,看哪個是零。不過,我猜@upload是零,因爲這條線在你的控制器:

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

特別是unless @uploads.blank?部分。除非你在ApplicationController中初始化它,@uploads總是零,這意味着@ uploads.blank?將永遠是真實的,這又意味着@upload永遠不會被初始化。改行

@upload = @parent.uploads.new 

問題將有望得到解決。其他使用unless @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. unless variable.blank更改爲if variable的使用。除非你的變量是一個布爾值,否則這會完成同樣的事情,但是會減少認知負擔,因爲前者基本上是你的大腦必須分析的雙重否定。

- 編輯 - 從有關問題的電子郵件交流 -

你是正確的 - 如果父母被初始化if @parent將返回true。正如我在SO上提到的,這是一個例外,如果@parent被初始化並設置爲false。本質上,它的意思是在Ruby中,除nil和false之外的所有值都被認爲是真實的。當一個實例變量沒有被初始化時,它的默認值是nil,這就是代碼行的原因。那有意義嗎?

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

請記住,@parent和@upload都必須是ActiveRecord(AR)對象的實例。在第一種情況下,您將@parent設置爲User.all,它是一個AR對象數組,它不起作用。此外,您嘗試在@parent初始化之前調用@ parent.uploads,這會導致無方法錯誤。但是,即使您要交換兩行,當父數組是數組時,也會調用@ parent.uploads。請記住,uploads方法是在單個AR對象上定義的,而不是在它們的數組上定義的。由於你的所有三個索引實現都做類似的事情,所以上述注意事項以各種形式適用於它們。

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

我會盡快引導您完成所做的更改。我開始之前,我解釋說,這

<%= 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更改爲父級並上傳。這意味着您需要在渲染表單時傳遞變量,而不是查找由控制器設置的實例變量。您會看到,這允許我們執行以下操作:

#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,因爲我們通過顯式傳遞它。

希望大家都有道理。您可以從這些示例中推斷,並通過父級和上傳對象,無論您想要呈現上傳表單。

+0

看起來你是對的。 'upload'不是方法,而是對象:) – fl00r 2011-04-10 14:45:49

+0

@luke感謝您的時間和指導。是的,在路由中,上傳對象嵌套在另一個對象中。我已經實現了這個改變,但是它現在返回**未定義的方法'model_name'用於NilClass:Class **。以下是錯誤消息的簡要摘錄:**提取的源代碼(第3行左右):**。這是第3行** <%= form_for [@parent,@upload],:html => {:multipart => true} do | f | %> **。謝謝。 – brg 2011-04-10 15:18:18

+0

@brg我已經更新了上面的答案。 – 2011-04-10 15:42:56

0

[@parent,上傳] => [@parent,:上傳]

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

UPD

你應該改變的地方:upload@parent

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

@ floor感謝您的建議。我已經實現了這一變化,但它現在給了我這個新的錯誤,這是我從之前發佈的唯一變化:**未定義的方法'model_name'爲符號:類** – brg 2011-04-09 19:41:03

+0

好的,請參閱我的更新。 – fl00r 2011-04-09 19:44:11

+0

@flOOr謝謝你的時間。我通過交換位置來實現新的更新,但錯誤仍然返回。這是**未定義的方法'model_name'爲NilClass:Class **之前**符號:Class **。再次感謝 – brg 2011-04-09 19:47:00

相關問題