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):


class User < ActiveRecord::Base

@@maxAttempts = 3

def self.authenticate(acct, pswd)

currentUser = find(:all,… ) # fetch based on match in acct only

assume we have one match and we’re past the tests for nil, > 1

match, etc

assume we’ve reciphered the password by now

if recipheredPswd != currentUser.pswd

 userAttempts = currentUser.recentAttempts + 1

 userAttempts >= @@ maxAttempts ?
   lockoutTime = Time.now.strftime("%Y-%m-%d %H:%M:%S") :
   lockoutTime = String.new

 #### RIGHT HERE

 I want to save currentUser with updated values for
 recentAttempts and lockoutTime

 ####

end
end

I have not included code that handles errors or that builds up some
of the data and tests that get me to that “if recipheredPswd” point,
but the salient point is that I have done some logic gymnastics and I
want to update the record right there in that Authenticate method.

So, at that point marked above, I have used the model’s find method,
but I haven’t yet passed it back out of my method. I’m thinking I
can’t really use AR’s save command at that point because the model
from the perspective of the controller hasn’t even been fully
loaded?? IOW self.attribute is not the same as currentUser.attribute
yet.

Obviously I can use straight SQL, but that would become DB-specific
at that point which I’d like to avoid.

Have I stated this clearly enough?

– gw

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 W. wrote:


assume we’ve reciphered the password by now

of the data and tests that get me to that “if recipheredPswd” point,
Obviously I can use straight SQL, but that would become DB-specific
at that point which I’d like to avoid.

Have I stated this clearly enough?

– gw


Sincerely,

William P.

Opps, missed the closing }

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

-Bill

William P. wrote:

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

assume we have one match and we’re past the tests for nil, > 1

yet.


Sincerely,

William P.

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

On Nov 2, 2007, at 6:37 PM, William P. wrote:

-Bill

   lockoutTime = String.new

loaded?? IOW self.attribute is not the same as currentUser.attribute


Sincerely,

William P.

– 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 W. wrote:

thx.

match, etc
#### RIGHT HERE
I have not included code that handles errors or that builds up some


Sincerely,

William P.

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

On Nov 2, 2007, at 7:20 PM, William P. wrote:

objects. You typically will be doing more with them in the

result is exactly what would have been returned to @user, so OK

tracking 3- strikes lockout. controller makes a call like this:
currentUser with updated values for recentAttempts and
currentUser.attribute yet. Obviously I can use straight SQL, but
that would become DB-specific at that point which I’d like to
avoid. Have I stated this clearly enough? – gw
– Sincerely, William P.
– gw

– Sincerely, William P.

– gw

On Nov 2, 2007, at 7:25 PM, Greg W. 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.

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

On Nov 2, 2007, at 8:00 PM, Greg W. wrote:

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

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.

bummer.

– gw

a .to_i

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

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

On Nov 2, 2007, at 8:48 PM, William P. wrote:

-Bill

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

foundUsers[0].save
$requestTrace.info(" attempts: " +

userAttempts = currentUser.userAttempts.to_i + 1


Sincerely,

William P.

– 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 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 W. wrote:

So, I changed it to
It semi-works.
userAttempts = currentUser.userAttempts + 1 # with or without a .to_i

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

bummer.

– gw


Sincerely,

William P.

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

On Nov 3, 2007, at 9:08 AM, William P. wrote:

user conversion, etc.

Something I did not show in my code is that I did this:
foundUsers[0].userAttempts = userAttempts
currentUser.userAttempts.to_s) #
foundUsers[0].userLockTime = userLockTime

– gw

On Nov 3, 2007, at 10:53 AM, Greg W. wrote:

On Nov 3, 2007, at 9:12 AM, William P. wrote:

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.

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

On Nov 3, 2007, at 9:12 AM, William P. wrote:

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