Leem.fin
Leem.fin

Reputation: 42602

A very interesting thing when submit a simple form to create new model instance

I am developing a Rails v2.3.2 app.

I have a Model with validations:

class Person < ActiveRecord::Base 
  validates_presence_of :name, :email

  # Please notice this method
  def pwd=(pwd)
      encrypted_pwd = pwd + randn(127)
      self.password = encrypted_pwd  
  end
end 

NOTE: The above model has attributes: name, email and password (not pwd), where password is not directly get and store to database, instead, the user input pwd value will be encrypted and then store as password in database. In database table, the column(attribute) is password. I use def pwd=(pwd) ... to achieve this.

My Controller:

class PersonsController < ApplicationController
   def create
     @person = Person.new(params[:person])
     if @person.save
       redirect_to persons_path
     else
       render :action=> :new
     end
   end

   def new
     #empty
   end

   def index
     @persons = Person.all
   end
end

The view (views/persons/new.html.erb):

 <% form_for :person, @person, :url => persons_url, :html=>{:method=>:post} do |f| %>
    <table>
      <tr>
        <td>Name:</td>
        <td><%= f.text_field :name %></td>
      </tr>
      <tr>
        <td>Email:</td>
        <td><%= f.text_field :email %></td>
      </tr>
      <tr>
        <td>password:</td>
        <td><%= f.text_field :pwd %></td> <!--NOTE: here I use :pwd not :password-->
      </tr>
    </table>
    <%= f.submit "Save"%>
<% end %>

The interesting & strange thing is , if in the view form, I input values to every field then submit the form, my new instance created successfully.

BUT, if I leave name and email empty, only input password field (which will be pwd value) in the form, then the application got broken, with the error message:

ActionView::TemplateError (undefined method `pwd' for #<Person:0xa0ec0e0>) on line #25 of app/views/persons/new.html.erb:
22:       </tr>
23:       <tr>
24:         <td>password:</td>
25:         <td><%= f.text_field :pwd %></td>
26:       </tr>
27:     </table>

I also tried from Rails console, I can create and save object like:

Person.new(:name=>'John', :email=>'[email protected]', :pwd=>'test')

So:

1. Why I can successfully create a model instance if all fields have been filled with values in the form, BUT I got that error when I leave name and email empty but input value to password(pwd) field?

2. Why I do not get error message which complains the name and email should not be empty?

Upvotes: 0

Views: 115

Answers (2)

jbrowning
jbrowning

Reputation: 623

To answer your second question, you're not getting the validation errors because you are not checking for them in your view. When validation fails, the error messages are added to an errors hash associated with the object you were trying to save. To optionally display them, add something similar to this to your view:

<% form_for :person, @person, :url => persons_url, :html=>{:method=>:post} do |f| %>
  <% if @person.errors.any? %>
    <ul id="errors">
    <% @person.errors.full_messages.each do |error| %>
      <li>
        <%= error %>
      </li>
    <% end %>
    </ul>
  <% end %>

Rest of your form goes here...

Upvotes: 0

Behrang Saeedzadeh
Behrang Saeedzadeh

Reputation: 47933

The reason is your model is missing an attribute reader (getter) for pwd.

Here's what's happening:

As name and email are required (courtesy of validates_presence_of :name, :email), when you do not enter them and submit the form, an error occurs and the form is rendered again and the view wants to fill the pwd field, but as you haven't defined it, you get that error message: undefined method 'pwd' for #<Person:0xa0ec0e0>.

So you can add a getter like this to your Person class:

def pwd
  "" # or whatever you like
end

And your form should work again. If you want your model to remember the unencrypted password when an error occurs, you can have something like this instead:

def pwd=(pwd)
  @pwd = pwd
  encrypted_pwd = pwd + randn(127)
  self.password = encrypted_pwd
end

def pwd
  @pwd || ""
end

Upvotes: 2

Related Questions