Search code examples
ruby-on-railsrubyruby-on-rails-3nomethoderror

NoMethodError in Articles#new in form_for using Rails 4.2.5 tutorial


I am trying to complete the official Rails tutorial and on step 5.2. The instructions are to do a form_with, but i got an error, looked up this StackOverflow post: Rails form_with results in a NoMethodError. I changed the form_with to form_for like the answer said, and i get this error:

NoMethodError in Articles#new

Showing /home/ubuntu/workspace/app/views/articles/new.html.erb where line #2 raised:

undefined method 'model_name' for {:scope=>:article, :url=>"/articles", :local=>true}:Hash

Extracted source (around line #2):

1. <h1>New Article</h1>
2. <%= form_for scope: :article, url: articles_path, local: true do |form| %>
3.   <p>
4.     <%= form.label :title %><br>
5.     <%= form.text_field :title %>
6.   </p>

Rails.root: /home/ubuntu/workspace

app/views/articles/new.html.erb:2:in '_app_views_articles_new_html_erb__1707235537542743350_40377020'

I am using Cloud9, if it helps. Here is my code for the view and controller:
View:

<h1>New Article</h1>
<%= form_for scope: :article, url: articles_path, local: true do |form| %>
  <p>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </p>

  <p>
    <%= form.label :text %><br>
    <%= form.text_area :text %>
  </p>

  <p>
    <%= form.submit %>
  </p>
<% end %>

Controller:

class ArticlesController < ApplicationController
  def new
  end

  def create
    render plain: params[:article].inspect
  end
end

Solution

  • I would have thought your ArticleController would look like:

    class ArticlesController < ApplicationController
      def new
        @article = Article.new
      end
    
      def create
        render plain: params[:article].inspect
      end
    end
    

    And your view would look like:

    <h1>New Article</h1>
    <%= form_for @article do |form| %>
      <p>
        <%= form.label :title %><br>
        <%= form.text_field :title %>
      </p>
    
      <p>
        <%= form.label :text %><br>
        <%= form.text_area :text %>
      </p>
    
      <p>
        <%= form.submit %>
      </p>
    <% end %>
    

    Given an @article, form_for will correctly infer the url as long as:

    the record passed to form_for is a resource, i.e. it corresponds to a set of RESTful routes, e.g. defined using the resources method in config/routes.rb

    According to the docs.

    It is also worth noting, perhaps, that the signature for form_for is (again, according to the docs):

    form_for(record, options = {}, &block)

    Which means that form_for expects to be passed (1) a record, (2) zero or more options, and (3) a block. A 'record' can actually be one of a number of things (such as an instance, a string, or a symbol), as described in the docs.

    When you do:

    <%= form_for scope: :article, url: articles_path, local: true do |form| %>
    

    I believe what is going on is that form_for is accepting the hash {scope: :article, url: articles_path, local: true} as the record and attempting to infer the url by calling model_name on the hash. A hash, naturally, does not respond to model_name, and so you get:

    undefined method 'model_name' for {:scope=>:article, :url=>"/articles", :local=>true}:Hash