7
votes

I have a form that i want to appear at the top of every page so i've included it in the /app/views/layouts/application.html.erb file and i get the error undefined methodmodel_name' for NilClass:Class` when trying to load the page.

Here's the form snippet in application.html.erb

 <%= form_for @user do |f| %>
    <h3>Add new contact</h3>
    <%= f.text_field :first_name %><br />
    <%= f.text_field :last_name %>
    <%= f.text_field :email %>
    <hr />
    <%= f.submit "Add Contact" %>
<% end %>

Here's my /app/controllers/user_controller.rb

class UserController < ApplicationController

    def index

    end

    def new
        @user = User.new
    end

end

I'm thinking that i'm hitting this error because since the form is in the application.html.erb file, i need to somehow specify the path, but then again i'm very new to rails.

Let me know if you need anything else posted.

EDIT Following Ola's suggestion, In my app/views/layouts/application.html.erb file I have something like this:

<div>
<%= yield :add_user %>
</div>

and in my app/views/users/new.html.erb file I have this

<% content_for :add_user do %>
    <%= form_for @user do |f| %>
        <h3>Add new contact</h3>
        First Name<br />
        <%= f.text_field :first_name %><br />
        Last Name<br />
        <%= f.text_field :last_name %><br />
        Email<br />
        <%= f.text_field :email %>
        <hr />
        <%= f.submit "Add Contact" %>
    <% end %>
<% end %>

The form is not rendered because my url is http://localhost:3000/admin/index and so it's looking for the add_user content_for in app/views/admin/index.html.erb

5
I presume "when trying to load the page" you mean /usersDavid J.
Sorry actually i'm loading this page http://localhost:3000/admin/indexCatfish
Yes, I thought as much, in the example above /users/new should work (kind of), though you've got your view structure all wrong - see my answer below.Ola Tuvesson
I'm afraid you've got the yield directive upside down too - trust me I know how confusing all this stuff is when you first hit the rails. <%= yield :something %> will render output from the current action, which would vary depending on what page you're on. Great for when you want each controller/action to set the contents of some universal page element (often used to set the document title) but you actually want the complete opposite - to render some component the same on every page. For this you should use <%= render :partial => "thing/partial" %> in your template.Ola Tuvesson

5 Answers

13
votes

If the form is included for multiple actions (pages) you need to set @user to something or the form_for directive will fail (it won't have anything to create a form for).

A much better approach (and the Rails default) is to have separate views for each action, each including only the elements required by that action, and loaded into your application.html.erb layout with <%= yield %>. So you would have a app/views/users/new.html.erb view which has the form in it. You never very rarely need to define any load paths in Rails, they are all derived from the model, controller and action names - or from your routes.rb. This is a core part of the convention over configuration paradigm which runs so deep in Rails.

Edit: If you do need to have a form for creating a new object on every page (often used for UserSessions for example), you can rewrite your form_for so that it doesn't depend on an object being present:

form_for(User.new)

or if you need to force it to post to the create method

form_for(User.new, :url => { :action => "create" }) 

You can read more about resource driven form_for in the Ruby On Rails API docs.

13
votes

Did you try debugging first? I ask because you say you are new to Rails. If not, here is my approach. I want to share it, because I'm surprised how many people don't (for whatever reason) debug carefully.

(I created a test app so that I could walk through your problem. First, I visited http://localhost:3000/users.)

First, look at the stack trace.

NoMethodError in Users#index

Showing /Users/david/dev/testapp/app/views/layouts/application.html.erb where line #11 raised:

undefined method `model_name' for NilClass:Class Extracted source (around line #11):

8: </head>
9: <body>
10: 
11: <%= form_for @user do |f| %>
12:     <h3>Add new contact</h3>
13:     <%= f.text_field :first_name %><br />
14:     <%= f.text_field :last_name %>

This tells you line 11 in your code is the problem. Think -- what could be the issue? Routing is unlikely because you are just rendering the page. Not a syntax error, that would have triggered another error. Ok, so keep reading:

Rails.root: /Users/david/dev/testapp

Application Trace | Framework Trace | Full Trace

Now click "framework trace" and you will see:

activemodel (3.2.6) lib/active_model/naming.rb:163:in model_name_from_record_or_class' activemodel (3.2.6) lib/active_model/naming.rb:158:inparam_key' actionpack (3.2.6) lib/action_view/helpers/form_helper.rb:369:in `form_for'

You may not understand all this -- few people do -- but you will see the error comes from model_name_from_record_or_class which comes from param_key which comes from form_for. That is a big clue!

I recommend the use of the open_gem gem to make it easy to dig into source code:

gem install open_gem

Now, take a look at activemodel, since it is the topmost gem in the stack trace:

gem open activemodel

Now, search for this with regular expression searching enabled:

def .*model_name_from_record_or_class

(Why? you want to find class methods -- e.g. definitions that start with self as well.)

This will take you to line 162 in naming.rb.

def self.model_name_from_record_or_class(record_or_class)
  (record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name
end

Even without understanding all of this, it does give a big hint... the method tries to return the model name from the record or class. Why can't it? Because the parameter you are passing, @user, hasn't been set. The value of unset instance variables is nil, so it won't complain until you try to do something with it.

I hope this helps for this question and for future ones.

7
votes

This answer is really just Someth's answer corrected, but the solution is to set @user for every controller action, not just those pertaining to a specific controller. This can be done by using a before_filter in the ApplicationController, i.e.

class ApplicationController < ActionController::Base
  before_filter :initialize_user

  def initialize_user
    @user = User.new
  end
end

OR by creating a new model in the form_for helper itself (note: this is ugly in terms of separation of concerns, but it works...)

 <%= form_for User.new do |f| %>
   <h3>Add new contact</h3>
   <%= f.text_field :first_name %><br />
   <%= f.text_field :last_name %>
   <%= f.text_field :email %>
   <hr />
   <%= f.submit "Add Contact" %>
 <% end %>
0
votes

Your problem is you are trying to add form to every action on your page, but didn't initialize user in all action. Try to initialize user in all actions:

class UserController < ApplicationController
    before_filter :initialize_user

    def index

    end

    def initialize_user
        @user = User.new
    end

end
0
votes

in the usercontroller:

def index
   @user = User.new
end