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