Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id

Hey all,

I know that this:

Called id for nil, which would mistakenly be 4 -- if you really wanted
the id of nil, use object_id

means that I have an instance variable that has not been initialized.
However, I'm a little confused as to what to do in a signup form for
form_for:

<% content_for :login do %>
   <% form_for @user, :url => { :action => "login" } do |f| %>
    <%= error_messages_for 'user' %><br/>
    <%= f.label(:user_login, "Username")%>
     <%= f.text_field(:login) %><br/>
     <%= f.label(:user_password, "User Password")%>
    <%= f.password_field(:password) %><br/>
    <%= f.submit("Sign Up") %>

    <%= link_to 'Register', :action => 'signup' %> |
      <%= link_to 'Forgot my password', :action => 'forgot_password' %>
  <% end %>
<% end %>

Because I didn't declare @user in login method:

  def login
    if request.post?
      if session[:user] = User.authenticate(params[:user][:login],
params[:user][:password])
        flash[:message] = "Login successful"
        redirect_to_root
      else
        flash[:warning] = "Login unsuccessful"
      end
    end

  end

There is no @user and so interpreter throws the exception. However, what
can I do in order to allow someone the opportunity to signin when using
a form? Do I create a temporary blank user: @user = User.new?

Thanks for response.

You could do something like....

<%= form_for :user, :url=>{ blablabla } do |f| %>
  (...)
<% end %>

:slight_smile:

John Merlino wrote in post #969678:

Hey all,

I know that this:

Called id for nil, which would mistakenly be 4 -- if you really wanted
the id of nil, use object_id

means that I have an instance variable that has not been initialized.

Well, it means that something tried to call nil.id .

However, I'm a little confused as to what to do in a signup form for
form_for:

[...]

There is no @user and so interpreter throws the exception. However, what
can I do in order to allow someone the opportunity to signin when using
a form? Do I create a temporary blank user: @user = User.new?

That's one common way of doing it. Almost every Rails tutorial does
that at some point.

Thanks for response.

Best,

AFAIK, form_for need the @user object to determine the url.

if its a new then it would generate /users and POST method

if its already exist it generate /users with PUT method

i think that you can specify the URL like daze said,

if you create new @user, it wil redirect to create method (if you have REST route)

unless you specify the route otherwise

That's one common way of doing it.

Yet, it worked. Basically, visitor makes a request URL, which Rails
translates into a controller/action sequence. In order to determine fate
of URL request, we create a rule that matches the URL path of the
request and determine where to direct that request. It maps requests to
action methods inside the controllers. The mapper method parses the
query string as a hash. So when login is part of query string action
based on user clicking link, a URL request is made, the mapper method in
turn is called, and points the request to the login method of the users
class:

  map.login "login", :controller => "users", :action => "login"

Since we are using named routing, Rails builds a helper method:

  <%= link_to "login", login_path %>

login method is called of UsersController class:

def login
    @user = User.new
    if request.post?
      if session[:user] = User.authenticate(params[:user][:login],
params[:user][:password])
        flash[:message] = "Login successful"
        redirect_to :root
      else
        flash[:warning] = "Username or password does not exist"
      end
    end

  end

We instantiate User class and assign the new instance to instance
variable @user. Hence, we have a @user object and since during
instantiation, our new instance inherits from ActiveRecord, the "blank"
instance in reality inherits getter setter methods translated from users
table in database. Hence, we accept parameters in the query string that
will translate to the data we need (e.g. login and password). Since the
instance variable inherits from ActiveRecord, it has access to some of
the built in rails helper methods like label, text_field,
password_field, submit:

<% content_for :login do %>
   <% form_for @user, :url => { :action => "login" } do |f| %>
    <%= error_messages_for 'user' %><br/>
    <%= f.label(:user_login, "Username")%>
     <%= f.text_field(:login) %><br/>
     <%= f.label(:user_password, "User Password")%>
    <%= f.password_field(:password) %><br/>
    <%= f.submit("Login") %>

    <%= link_to 'Register', :action => 'signup' %> |
      <%= link_to 'Forgot my password', :action => 'forgot_password' %>
  <% end %>
<% end %>

The helper methods render html output. User inserts value and gets
converted to a hash with key/value pairs corresponding to field in table
to its value. Hence, we do the following in controller:

    if request.post?
      if session[:user] = User.authenticate(params[:user][:login],
params[:user][:password])

When the form is submitted a POST HTTP request is made, and we call the
class method authenticate on the User class object. We pass in the
relevant query string information using the params hash.

The authenticate method in turn is called:

  def self.authenticate(login, pass)
       u=find(:first, :conditions=>["login = ?", login])
       return nil if u.nil?
       return u if User.encrypt(pass, u.salt)==u.hashed_password
       nil
     end

We use special syntax self to indicate this is a class method and not an
instance method and therefore can only be called on the class itself. We
pass our query string arguments into the authenticate argument
definition as parameters (local variables) login and pass. We perform a
sql query to our mysql database, searching first record of login field
that matches value passed. We assign result to local variabnle u. If u
is nil, we return nil, and the if statement's argument will be converted
to BOOLEAN false (which I believe is object not a primitive in ruby?):

if session[:user] = User.authenticate(params[:user][:login],
params[:user][:password])

Hence, else statement is invoked:

        flash[:warning] = "Username or password does not exist"

Otherwise, we return u (the data set of current user):

       return u if User.encrypt(pass, u.salt)==u.hashed_password

We do this if the encrupt method returns a value equal to what the
hashed_password method's value is.

So returning u will convert the if booleon to true and if statement
returns immediate block:

        flash[:message] = "Login successful"
        redirect_to :root

The user is logged in successful.

One thing I forgot to mention is we assign the local variable u to the
session :user:

      if session[:user] = User.authenticate(params[:user][:login],
params[:user][:password])

Hence, the session has this user stored.

THis is a little unclear to me though:
session[:user]

We are storing a symbol called :user in sesion associative array. So
:user is initiaized with vaue of u (whcih is dataset we returned).
What's the purpose of using symbol here? Why can't we use something else
like a variable?

THanks for response.

One thing I forgot to mention is we assign the local variable u to the
session :user:

  if session\[:user\] = User\.authenticate\(params\[:user\]\[:login\],

params[:user][:password])

Hence, the session has this user stored.

THis is a little unclear to me though:
session[:user]

We are storing a symbol called :user in sesion associative array. So
:user is initiaized with vaue of u (whcih is dataset we returned).
What's the purpose of using symbol here? Why can't we use something else
like a variable?

Why would you want to? If you're going to store something in the
session you might as well use a constant as the key. You don't have
to, but you'd just be making life more complicated

Fred