16

I am using Single Table Inheritance for managing different types of projects.

Models:

class Project < ActiveRecord::Base
end

class SiteDesign < Project
end

class TechDesign < Project
end

Edit action from projects_controller:

def edit
   @project = Project.find(params[:id])
end

View edit.html.erb:

<% form_for(@project, :url => {:controller => "projects",:action => "update"}) do |f| %>
    ...
    <%= submit_tag 'Update' %>
<% end %>

Update action of projects_controller:

def update
    @project = Project.find(params[:id])
    respond_to do |format|
      if @project.update_attributes(params[:project])
        @project.type = params[:project][:type]
        @project.save
        flash[:notice] = 'Project was successfully updated.'
        format.html { redirect_to(@project) }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @project.errors, :status => :unprocessable_entity }
      end
    end
  end

Then i do some edits of TechDesign entry on edit view and get error:

NoMethodError in ProjectsController#update

You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]

In parametrs it is obvious that instead of project parameter name i have tech_design Parameters:

{"commit"=>"Update",
 "_method"=>"put",
 "authenticity_token"=>"pd9Mf7VBw+dv9MGWphe6BYwGDRJHEJ1x0RrG9hzirs8=",
 "id"=>"15",
 "tech_design"=>{"name"=>"ech",
 "concept"=>"efds",
 "type"=>"TechDesign",
 "client_id"=>"41",
 "description"=>"tech"}}

How to fix it?

ebsbk
  • 4,512
  • 3
  • 24
  • 29

4 Answers4

26

Here's the source of your problem. This is setting @project as an instance of a TechDesign object.

def edit
   @project = Project.find(params[:id])
end

You can ensure things work the way you want by specifying :project for a name in the form_for call.

<% form_for(:project, @project, :url => {:controller => "projects",:action => "update"}) do |f| %>
    ...
    <%= submit_tag 'Update' %>
<% end %>
EmFi
  • 23,435
  • 3
  • 57
  • 68
  • Thanks for your reply. After those chages i've got this error : No action responded to 1. Actions: create, destroy, edit, index, new, show, and update – ebsbk Oct 17 '09 at 18:11
  • 1
    After adding this map.connect "projects/:id/edit", :controller => "projects", :action => "edit" to routes.rb error disapeared. Question is why it does not worked without it if map.resources :projects already presents in routes? – ebsbk Oct 17 '09 at 18:23
  • 11
    In Rails 3.1, write the form_for statement as <%= form_for @project, :as => :project do |f| %> – Cygwin98 May 18 '11 at 20:19
  • 1
    Old thread, but since I found it when Googling, this is what I did: http://henrik.nyh.se/2012/08/rails-sti-and-form-for/ Note that `:as => :project` will only influence the params, not the URL. – Henrik N Aug 14 '12 at 10:22
7

For Rails 3

<% form_for(@project, :as => :project, :url => {:controller => "projects",:action => "update"}) do |f| %>
...
   <%= submit_tag 'Update' %>
<% end %>
William Wong Garay
  • 1,921
  • 18
  • 14
2

For Rails 4, I have confirmed the only thing that has seemed to work for me is to explicitly specify the URL and the AS parameters:

<% form_for(@project, as: :project, url: {controller: :projects, action: :update}) do |f| %>
    ...
<% end %>

Seems ugly to me!

Tom Rossi
  • 11,604
  • 5
  • 65
  • 96
2

A random note: If you are using single table inheritance (STI) and forget to remove the initialize method from your subclass definitions you will get a similar "nil object when you didn't expect it" exception.

For example:

class Parent < ActiveRecord::Base
end

class Child < Parent
  def initialize
    # you MUST call *super* here or get rid of the initialize block
  end
end

In my case, I used my IDE to create the child classes and the IDE created the initialize method. Took me forever to track down...

GSP
  • 3,763
  • 3
  • 32
  • 54