简体   繁体   中英

Learn Ruby on Rails: NoMethodError (undefined method `[]' for nil:NilClass):

I am working through Daniel Kehoe's tutorial on rails and have run into a problem that I do not understand and cannot find an answer to online. I get the NoMethodError described in the title of this post when using a flash message with params.

Here is the controller

class ContactsController < ApplicationController

  def process_form
    Rails.logger.debug "DEBUG: params are #{params.inspect}"
    flash[:notice] = "Received request from #{params[:contact][:name]}"
    redirect_to root_path
  end
end

It works if I remove [:contact] and leave [:name]. If I remove [:name] and leave [:contact] it redirects but doesn't display the name. If I have both [:contact] and [:name] as the controller listing shows I get the NoMethodError and it does not redirect.

Here is my view page

<% content_for :title do %>Contact<% end %>
<h3>Contact</h3>
<div class="form">
  <%= form_with( url: contact_path) do |form| %>
    <%= form.label :name %>
    <%= form.text_field :name, autofocus: true %>
    <br/>
    <br/>
    <%= form.label :email %>
    <%= form.email_field :email %>
    <br/>
    <br/>
    <%= form.label 'message' %>
    <%= form.text_area :content, size: '40x5' %>
    <br/>
    <br/>
    <%= form.submit 'Submit', class: 'submit' %>
  <% end %>
</div>

Here is my routes.rb

Rails.application.routes.draw do
  post 'contact', to: 'contacts#process_form'
  root to: 'visitors#new'
end

Here is my log when I get the NoMethodError :

Started POST "/contact" 2017-07-23 23:25:11 +0000 Processing by ContactsController#process_form as JS Parameters: {"utf8"=>"✓", "authenticity_token"=>"...", "name"=>"Jane Doe", "email"=>"jd@example.com", "content"=>"oiuwroiueroieroiuerw", "commit"=>"Submit"} DEBUG: params are "✓", "authenticity_token"=>"...", "name"=>"Jane Doe", "email"=>"jd@example.com", "content"=>"oiuwroiueroieroiuerw", "commit"=>"Submit", "controller"=>"contacts", "action"=>"process_form"} permitted: false> Completed 500 Internal Server Error in 3ms

NoMethodError (undefined method `[]' for nil:NilClass):

If you want to get values as object like params[:contact][:name] , you should specify the object to the form helper. Example using form_for helper:

<%= form_for(@contact) do |f| %>
    <div class="field">
        <%= f.label :name %><br>
        <%= f.text_field :name %>
    </div>
    <div class="field">
        <%= f.label :email %><br>
        <%= f.text_field :email %>
    </div>
    <div class="actions">
        <%= f.submit %>
    </div>
<% end %>

Here is a quick reference to see how to deal with objects in form:

http://guides.rubyonrails.org/form_helpers.html#binding-a-form-to-an-object

If you don't have any idea about how it works, i suggest you to generate the CRUD using the scaffold generator. Example:

rails g scaffold Contact name:string email:string

This command will generate all files (controller, views) related to the CRUD. So you can use this as reference. It will be already following the Rails convention.

Your params looks something like this

params{
   :content => 'Words',
   :email => 'example.@gmail.com',
   :name => 'Joey Smith'
}

Side note, do you intend 'content' to be 'contact'?

Params is an object and this

params[:contact][:name]

Would work if your params was structured like:

params {
      :contact => {
         :name => 'Joey Smith'
      }
}

Which it is not.

Not sure what you are trying to do because I don't see a 'contact'

I would guess that what you want is

flash[:notice] = "Received request from #{params[:name]}"

form_with(url: contact_path) won't add the resource root to your inputs within the form rendered, take a look:

<%= form_with(url: contact_path) do |form| %>
  <%= form.text_field :name %>
  ...
<% end %>

# Will give you
<form action="/contact" method="post" data-remote="true">
  <input type="text" name="name">
</form> 

And form_with(model: Model.new) :

<%= form_with(model: Model.new) do |form| %>
  <%= form.text_field :name %>
<% end %>

# Will give you
<form action="/contact" method="post" data-remote="true">
 <input type="text" name="model[name]">
</form>

What you need is to pass the resource to be created, you should have a new method in your controller, and there create an instance variable which you can then use in the model option of your form_with .

See also Unification of form_for and form_tag into form_with .

Since form_with is the "new standard" in Rails 5.1, I'd continue to use it.

To use form_with successfully, you have to use syntax like this:

<%= form_with scope: :post, url: posts_path do |form| %>
  <%= form.text_field :title %>
<% end %>

<%# Will generate %>

<form action="/posts" method="post" data-remote="true">
  <input type="text" name="post[title]">
</form>

Or this:

<%= form_with model: Post.new do |form| %>
  <%= form.text_field :title %>
<% end %>

<%# Will generate %>

<form action="/posts" method="post" data-remote="true">
  <input type="text" name="post[title]">
</form>

For your particular situation, you should probably be doing something like this:

<% content_for :title do %>Contact<% end %>
<h3>Contact</h3>
<div class="form">
  <%= form_with( scope: :contact, url: contact_path) do |form| %>
    <%= form.label :name %>
    <%= form.text_field :name, autofocus: true %>
    <br/>
    <br/>
    <%= form.label :email %>
    <%= form.email_field :email %>
    <br/>
    <br/>
    <%= form.label 'message' %>
    <%= form.text_area :content, size: '40x5' %>
    <br/>
    <br/>
    <%= form.submit 'Submit', class: 'submit' %>
  <% end %>
</div>

By doing this, it would modify the name of each input element from being content , email , name , etc to become contact[content] , contact[email] , contact[name] , etc. This would then allow your controller code to work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM