How does ActiveRecord translate parameter hash to values of a class instance's properties

Hey all,

Let’s say you have this method:

def signup
@user = User.new(params[:user])
if request.post?
if @user.save
session[:user] = User.authenticate(@user.login, @user.password)
flash[:message] = “Signup successful”
redirect_to :action => “welcome”
else
flash[:warning] = “Signup unsuccessful”
end
end
end

Since User class inherits from ActiveRecord class, I presume
ActiveRecord contains a constructor that takes the key/value pairs of a
hash from the parameters of web form and checks if the keys of the hash
match the instance methods available in current class instance which
were generated from field names from a table of the same name (e.g.
Users).

Basically all that happening with this line:
@user = User.new(params[:user])

Hence, you can now do @user.login - should login be a field in the
database. If a value for login was captured in the param hash, then
@user.login will return value passed from params hash.

Does anyone have a general description of what ActiveRecord does behind
the scenes to achieve this?

Now the second question. What about parameter’s that are not part of the
table field names? ActiveRecord doesn’t create getter and setter methods
for these parameters by default.

You have to manually do it:

attr_accessor :password #should a password field not exist in users
table

So basically when a new instance is created, and we accept a hash
parameter, which contains a password key that doesn’t translate to field
in database, ruby immediately checks if such a value has a setter and
getter (attr_accessor :password) and if it does, then it calls the below
method since it needs to resolve the password object:

def password=(pass)
@password=pass
self.salt = User.random_string(10) if !self.salt?
self.hashed_password = User.encrypt(@password, self.salt)
end

So is that all that happens here or does ActiveRecord do something else
behind the scenes? This is a different question from my first question.

ALso, why do you think this method is authenticating the user as soon as
they are created? The fact that they just been created suggests that
they are real.

Thanks for response.

On Dec 16, 9:49pm, John M. [email protected] wrote:

Hence, you can now do @user.login - should login be a field in the
database. If a value for login was captured in the param hash, then
@user.login will return value passed from params hash.

Does anyone have a general description of what ActiveRecord does behind
the scenes to achieve this?

Barring some complication around protected attributes, multi parameter
assignments etc… the code behind all this boils down to

hash_of_attributes.each do |name, value|
send “#{name}=”, value
end

Whether the method called is one backed by a database attribute or not
doesn’t matter

Fred

John M. wrote in post #968948:

Since User class inherits from ActiveRecord class, I presume
ActiveRecord contains a constructor that takes the key/value pairs of a
hash from the parameters of web form and checks if the keys of the hash
match the instance methods available in current class instance which
were generated from field names from a table of the same name (e.g.
Users).

Basically all that happening with this line:
@user = User.new(params[:user])

Hence, you can now do @user.login - should login be a field in the
database. If a value for login was captured in the param hash, then
@user.login will return value passed from params hash.

Does anyone have a general description of what ActiveRecord does behind
the scenes to achieve this?

You pretty much just described it. There’s likely a good bit of
meta-programming happing behind the scenes, but as a general overview
what you describe is a good way to think about it.

If you really want to know then clone the Rails project and read the
code. It is open source after all.

Now the second question. What about parameter’s that are not part of the
table field names? ActiveRecord doesn’t create getter and setter methods
for these parameters by default.

You have to manually do it:

attr_accessor :password #should a password field not exist in users
table

You can think of attr_accessor, attr_reader, attr_writer as shortcuts
for writing out the individual accessor methods for dynamically
generated instance variables. No real magic is going on here other that
Ruby being dynamic and creating what it needs when it needs it at
runtime.

BTW: What you’re describing here is often referred to as “virtual
attributes” when related to ActiveRecord. Everywhere else, AFAIK, they
are simply called accessors backed by instance variables.

Still a little confused in terms of sequence of operation:

  1. A request for URL is made (e.g. www.mysite.com/login). The URL is
    matched against a route to find the corresponding controller. So I have
    this:

map.signup “signup”, :controller => “users”, :action => “signup”

Hence, the signup URL string is mapped against the UsersController. Now
Rails knows that the file containing the controller can be found as
apps/controllers/users_controller.rb. The signup portion of query string
is matched against the signup action of the UsersController, which is
represented by a signup method in the controller.
2) Hence, at this point that method is invoked:

def signup
@user = User.new(params[:user])
if request.post?
if @user.save
session[:user] = User.authenticate(@user.login, @user.password)
flash[:message] = “Signup successful”
redirect_to :action => “root”
else
flash[:warning] = “Signup unsuccessful”
end
end
end

We instantiate new User object, passing it arguments from the parameter
hash passed via query sring. When page first loads, these parameters
will all be equal to nil since no assignment has been made to the
properties of the User object (because no user input occurred yet):
<User id: nil, login: nil, hashed_password: nil, email: nil, salt: nil,
created_at: nil>
Nevertheless these properties are “own members” (instance methods) of
the Object instance, courtesy of the hash iteration described above
where setters and getter methods are dynamically created at run time:

def login=(p)
@p = p #if no parameter passed, nil returned implicitly in Ruby?
end

def login
@p
end

@user.login #nil

User now fills out form and submits it:

<% content_for :signup do %>
<% form_for @user, :url => { :action => “signup” } do |f| %>
<%= error_messages_for ‘user’ %>

<%= f.label(:user_login, “Username”)%>
<%= f.text_field(:login) %>

<%= f.label(:user_password, “User Password”)%>
<%= f.password_field(:password) %>

<%= f.label(:user_password_confirmation, “Password Confirmation”)%>
<%= f.password_field(:password_confirmation) %>

<%= f.label(:user_email, “User Email”)%>
<%= f.text_field(:email)%>
<%= f.submit(“Sign Up”) %>
<% end %>
<% end %>

Here, the signup method is called again but this time it’s not a GET
request. It’s a POST request. We also now have values for our key hash.
And interestingly there is a method here called password that gets as
parameter of predefined password_field method. There is no password
method in our users table. Ruby must resolve it, so it checks the
instance methods of our user instance and indeed it finds it:
attr_accessor :password, :password_confirmation

 def password=(pass)
   @password=pass
   self.salt = User.random_string(10) if !self.salt?
   self.hashed_password = User.encrypt(@password, self.salt)
 end

Basically this just populates our salt and hashed_password properties to
ensure protection of our user. Ultimately, we now have values for our
properties of our instance. Since we are dealing with post request, we
save the user calling predefined save method of ApplicationController??
(who knows).

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

And store the user as part of current session.

Is this a correct analysis in terms of sequence of events?

A real gray area for me is when the instance variable @user stores the
correct value in signup.html.erb with an appropriate reference to
current instance. Does this it become available as soon as the method is
called in controller? Or is there some more magic going on that makes it
available?

Thanks for response.