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.

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 Merlino 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' %><br/>     <%= f.label(:user_login, "Username")%>      <%= f.text_field(:login) %><br/>      <%= f.label(:user_password, "User Password")%>     <%= f.password_field(:password) %><br/>     <%= f.label(:user_password_confirmation, "Password Confirmation")%>     <%= f.password_field(:password_confirmation) %><br/>     <%= 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.