Login action: from 2 user structures (@user, user), to one
Posted over 2 years ago by Raul Parolari
Hi, everybody,
I am a new blogger for RailsSpace. Two words about me; I am Italian (so forgive the occasional slip-up) and US resident (Bay Area). After years of real-time apps (network mgmt, Snmp), I began a sabbatical into scripting languages; after a furious flirt with Perl, I finally met Ruby (and nothing was ever the same again..). Recently, I began to read RailsSpace, and I was granted the chance to comment some aspects of the code.
I start discussing the Login action, at its inception, in chapter 6:
def login
if request.post? and params(:user)
@user = User.new(params[:user])
user = User.find_by_screen_name_and_password(@user.screen_name,
@user.password)
if user
session[:user_id] = user.id
# flash and redirect
else
@user.password = nil
# flash
end
end
end
The weak point is this juggling between the @user (the user received from the form) and user (the one from the database) structures at every statement. This usage only intensifies in the next chapters as the method becomes progressively more complex. Could we have only 1 structure? Yes, if we collect the user credentials from params:
def login
if request.post? and params(:user)
screen_name = params[:user][:screen_name]
password = params[:user][:password]
@user = User.find_by_screen_name_and_password(screen_name, password)
if @user
session[:user_id] = @user.id
# flash and redirect
else
# bad credentials: return data rcvd, but wout password
@user = User.new(params[:user])
@user.password = nil
# flash
end
end
end
Notice that we now need to create the user in the else branch; but the benefit is that there is only 1 structure across the whole method (which, as said, will become quite complex later). And the method is more efficient, as (in the succesful case) we call only once ActiveRecord to build the user.
Ah, some may say, we do have only 1 structure, but we now access a nested hash!: is this what you call simplification?..
Answer: we can hide the access to the data in a method; it will be generic for any object and any nr of attributes; so the usage will be:
name, pwd = params_for(:user, :screen_name, :password)
# retrieves named params from object
def params_for(object, *names)
obj_hash = params[object.to_sym]
return [ ] unless obj_hash
names.inject([]) { |arr, name| arr << obj_hash[name.to_sym] }
end
This method iterates across the array of names received, extracting the values from the nested hash, and feeding the result array (inject is a cute way to iterate accumulating a result, without creating extra local variables; very idiomatic inside Rails code).
All right; so the login looks like:
def login
if request.post? and params(:user)
name, pwd = params_for(:user, :screen_name, :password)
@user = User.find_by_screen_name_and_password(name, pwd)
# all the rest of the logic is as above
end
Now the resulting method uses only 1 user structure, and the extraction of parameters is friendly enough. As the method complexity escalates in following chapters (‘remember_me’, cookies, etc), the programmer can concentrate on the features (without worrying about which structure to access at each statement).
Comments
There are 7 comments on this post.
hi there, I’m new to ruby. I have a hard time understanding this
name, pwd = …
how does ruby knows which variables belongs to which?
Hi, Fadhli
it is a good question; first something very simple. I am sure that you can guess what will be the values of a, b here below:
a, b = [ 4, 16]
p a # => 4
p b # => 16
This is obvious, right? (play with irb, that is a great learning tool). And now, next step; let us try to imitate a bit the method params_for:
def get_values
values = [ ]
values << 4
values << 16
values
end
Did you follow on irb? you will have seen that again, a is 4, b is 16. Of course! because we pushed the values on that order, right?
Well, the logic that you saw in my post is doing the same thing. I know, I coded it using ‘inject’ (I could not resist), instead of something simpler; but here is a friendlier version:
def params_for(object, *names)
hsh = params[object.to_sym]
return [ ] unless hsh
list = [ ]
names.each { |n| list << hsh[n.to_sym] }
list
end
The method iterates on the array ‘names’, pushing into array ‘list’ the values of those names (extracting them from the hash). So if we call the method with:
name, pwd = params_for(:user, :screen_name, :password)
It is clear that ‘name’ will get the value for ‘screen_name’ (I mean, the value that screen_name has in the hash), and ‘pwd’ the value for ‘password’ (remember that :user does not count, it is only used to access the inner hash). Do you see?
We can also obtain the identical result with:
pwd, name = params_for(:user, :password, :screen_name)
If the notation *names troubles you a bit, read the Pickaxe, chapter 6 (p.80 2nd edition), “Variable-Length Argument Lists”, and let us know if something is still not clear.
Raul
Hi, thanks Raul for taking the time to answer a newbie question. It sure helps a lot!
Why in the RailsSpace 2.0 source code is the post a “get” instead of a “post”? Will this work with Rails 2.0?
Hi, Todd,
> Why .. is the post a “get” instead of a “post”?
Where have you seen that?
Raul
Dos code makes me feel like a dummy, its so confusing. I rather Use online personals, love calculator & create blogs, dating forums. Online 3D dating community for falling in love.
Look to my code in page www.space4love.com It good :)