How update model from within its own finder method?

Wondering how I'd go about updating a model under this scenario which is authenticating a user, and updating the user record for tracking 3-strikes lockout.

controller makes a call like this:

    @user = User.authenticate(acct, pswd)

to this model (missing much code to shorten it up):

This should work:

begin
    current_user.update_attributes!( :recentAttempts => userAttempts,
:lockoutTime => lockoutTime)
rescue ActiveRecord::RecordInvalid => e
    #do something with e if you want
end

-Bill

Greg Willits wrote:

Opps, missed the closing }

`

current_user.update_attributes!( :recentAttempts => userAttempts,
:lockoutTime => lockoutTime })`

-Bill

William Pratt wrote:

Hmm. That tries to do an INSERT and it gripes about datetime fields
not having values (they don't need values as they're not being updated).

I would think that should work for @user up in the controller, but
you're expecting it to work on the local var currentUser? Well, yeah,
if I didn't capture currentUser then that result is exactly what
would have been returned to @user, so OK maybe I can go ahead with
the standard ActiveRecord methods on currentUser. I can experiment.
(done some finding with AR, but not writing yet).

thx.

-- gw

No problem. One thing to note, if you want to write dates or datetimes
back to the db from a Date or Time object, use it’s .to_s(:db) method
to format it properly. As far as it doing an insert, the only reason I
can think of would be if currentUser was a new record instead of an
existing one or those attributes are associations that do not exist,
otherwise, that should do an update.

As far as the controller / model confusion, it doesn’t matter where you
are or whether the variable is a local or instance variable, all of the
ActiveRecord methods are available to all ActiveRecord objects. You
typically will be doing more with them in the controller, but what you
are doing here should work fine.

-Bill

Greg Willits wrote:

I figured it out.

Something I did not show in my code is that I did this:

    currentUser = find.....

    # after I establish there is only one found record I did this

    currentUser = currentUser[0]

So, I changed it to

    foundUsers = find.....
    currentUser = foundUsers[0]

then I changed the updating to use foundUsers[0] and it works.

Thanks!

-- gw

update_attributes! didn't work, but if I do a prozaic

foundUsers[0].userAttempts = userAttempts
foundUsers[0].userLockTime = userLockTime
foundUsers[0].save

It semi-works.

So, the relevant sequence is like this:

currentUser = foundUsers[0]

$requestTrace.info(" attempts: " + currentUser.userAttempts.to_s) # => 4
$requestTrace.info(" attempts: " + currentUser.userAttempts.class.to_s) #=> Fixnum

userAttempts = currentUser.userAttempts + 1 # with or without a .to_i

$requestTrace.info(" New attempts: " + userAttempts.to_s) # => 1

foundUsers[0].userAttempts = userAttempts
foundUsers[0].userLockTime = userLockTime
foundUsers[0].save

No matter what the database has in it for an integer, I get a log entry of that value as I expect, but then when the record is updated the value is always set to 1.

What's wrong with this line?

userAttempts = currentUser.userAttempts + 1

It doesn't work like this either

userAttempts = currentUser.userAttempts.to_i + 1

-- gw

Hmm. Works fine if I use foundUsers[0] for everything and abandon using the currentUser alias.

bummer.

-- gw

So, the relevant sequence is like this:

currentUser = foundUsers[0]

$requestTrace.info(" attempts: " + currentUser.userAttempts.to_s) #
=> 4
$requestTrace.info(" attempts: " +
currentUser.userAttempts.class.to_s) #=> Fixnum

userAttempts = currentUser.userAttempts + 1 # with or without a .to_i

$requestTrace.info(" New attempts: " + userAttempts.to_s) # => 1

foundUsers[0].userAttempts = userAttempts
foundUsers[0].userLockTime = userLockTime
foundUsers[0].save

No matter what the database has in it for an integer, I get a log
entry of that value as I expect, but then when the record is updated
the value is always set to 1.

What's wrong with this line?

userAttempts = currentUser.userAttempts + 1

It doesn't work like this either

userAttempts = currentUser.userAttempts.to_i + 1

Hmm. Works fine if I use foundUsers[0] for everything and abandon
using the currentUser alias.

Oh! Pfft. Idiot. My fault.

After determining the login was invalid, I just set currentUser to a new empty user. Of course I did that above the record
update. Moron.

that's what I get for not showing all the code :stuck_out_tongue:

-- gw

I assume you are looking the user up by login name. Why not do this:

`

current_user = User.find_by_login(params[:login]) #or whatever the
parameter is`

The worry about multiple records should be handled upon user creation
by validations in the User model. This way you will only get a single
user object and won’t have to mess with array to user conversion, etc.

-Bill

Greg Willits wrote:

The worry about multiple records should be handled upon user creation by validations in the User model.

understood, but this is a system that is central to many apps, so I am taking care of this scenario at both ends. Logging/alerting duplicate accounts so they can be tracked down just in case…

It’s a few lines of code for assurance that I just feel better having but yes, in theory the system does prevent it up front as well.

current_user = User.find_by_login(params[:login]) #or whatever the parameter is

This brings up a separate topic for me (filtering params to prevent form input injection).

http://www.ruby-forum.com/topic/129882

– gw

All prameters are escaped for you when you use the custom finder methods or conditions with placeholders like:

:conditions => [‘foo like ?’, bar]

The find_by_login method below will sanitize that data.

-Bill

Sorry, I just looked at your post. If you want to filter the columns returned from a query, you can use :select. For eliminating columns a user can “search” on, you can just create a small class method that lists the allowed fields in an array and use include? to verify. If you want to prevent users from updating certain columns via update_attributes, set those attributes to protected in your model and they will not be updated unless you explicitly update them through their setter method.

Hope this helps

-Bill

I’m not referring to escaping strings to prevent SQL/XSS/HTML injection.

I’m talking about forms being rewritten and submitted with extra inputs added to them.

Virtually every discussion about web & forms I see (Rails no exception) I see the “magic” of passing a params-type function to an SQL query generator, and no one ever talks about field injection which will modify the intended query.

In my Lasso framework I dealt with that, but I don’t see a similar purposed system mentioned in Rails. At least not in AWDWR – I haven’t yet dug deep into all the RDocs (is there some better implementation of these than the default web pages?).

Granted, I haven’t fully explored all of AR’s capabilities yet, so I’m reserving a final opinion, but at first glance I’m finding Rails’ :conditions system to be more limited than the system I developed for myself, and am therefore accustomed to using / thinking in terms of. Obviously the Rails authors are no idiots, so there has to be some accommodation for it, I just haven’t found it, and I’m surprised it didn’t seem to be talked about in AWDWR except for a slight warning.

– gw

Sorry, I just looked at your post. If you want to filter the columns returned from a query, you can use :select.

sure.

For eliminating columns a user can "search" on, you can just create a small class method that lists the allowed fields in an array and use include? to verify.

This sounds something like what I'm looking for, but very little info on it in AWDWR, so I'll look further and see what happens.

Thanks.

-- gw

Oh. I see that's just a Ruby keyword (still learning what's Ruby and what's Rails).

Yeah, essentially that's what I see, I have to build my own system for doing this filtering. No problem, I just find it odd there's no framework-level tool for that. I consider it a fairly fundamental security thing.

-- gw