Validate_on_update with a save()


#1

I’ve got a form with inputs like user[first_name], user[last_name], etc.
When editing the user instead of creating a new one I include a
user[id]. I then try and save the object using:

user = User.new(params[:user])
user.save()

I would expect that since I included the user[id] it should do an
update and should call validate_on_update. But this doesn’t appear to
be the case. When I call the save it runs the code in
validate_on_create instead (which fails validation).

Why is this?


#2

Because you need to find the record. You’re creating a new one in
memory and then trying to save it to the database. To do both, try:

user = User.find_or_create_by_id(params[:user][:id])
user.save

Note, user.save can be checked for a return value to see if it was
successful. Alternatively you can call user.save! which will raise an
exception if it fails. In that case you need a begin / rescue block.

Michael


#3

Michael T. wrote:

Because you need to find the record. You’re creating a new one in
memory and then trying to save it to the database. To do both, try:

user = User.find_or_create_by_id(params[:user][:id])
user.save

Note, user.save can be checked for a return value to see if it was
successful. Alternatively you can call user.save! which will raise an
exception if it fails. In that case you need a begin / rescue block.

Yeah I think that’s getting me on the right track. Is there a way to
mass-set the attributes using a hash? I tried:

User.find_or_create_by_id(params[:user][:id].to_i, params[:user])

But it didn’t work as expected. It just loaded the data from the DB
as-is so the changes weren’t saved.


#4

The B. wrote:

Yeah I think that’s getting me on the right track. Is there a way to
mass-set the attributes using a hash? I tried:

User.find_or_create_by_id(params[:user][:id].to_i, params[:user])

But it didn’t work as expected. It just loaded the data from the DB
as-is so the changes weren’t saved.

Well I’ll answer my own question. I modified my code to do:
user = User.find_or_create_by_id(params[“user”][“id”])
user.attributes = params[“user”]

Then I do a test using user.new_record? to set some different things
depending on if its new or updated. After that I call user.save() and
it works like a charm!

Thanks Michael


#5

Michael T. wrote:

Glad you found it. Yeah, that’s probably the best approach.

Michael

Hmm I’m noticing some oddities since I changed my code. Here are some
of the columns in my user table when I first create the database.

±—±------±---------±-----------±----------+
| id | login | verified | first_name | last_name |
±—±------±---------±-----------±----------+
| 1 | admin | 1 | Site | Admin |
| 2 | staff | 1 | Staff | User |
±—±------±---------±-----------±----------+

If I login as staff, create a user, logout then login as that user.
Then lets say I log back out and back in as staff and create a second
user, this is what the table looks like:

±—±------±---------±-----------±----------+
| id | login | verified | first_name | last_name |
±—±------±---------±-----------±----------+
| 1 | elmer | 1 | Elmer | Fudd |
| 2 | staff | 1 | Staff | User |
| 3 | john | 1 | John | Doe |
±—±------±---------±-----------±----------+

Seems to only happen if I log out of the staff account and back in as
someone else. It’s really weird, but it overwrote admin!

If I keep adding users it keeps incrementing, so after creating the
third user (first john, then elmer, now jane) I get:

±—±------±---------±-----------±----------+
| id | login | verified | first_name | last_name |
±—±------±---------±-----------±----------+
| 1 | elmer | 1 | Elmer | Fudd |
| 2 | jane | 1 | Jane | Smith |
| 3 | john | 1 | John | Doe |
±—±------±---------±-----------±----------+

Any ideas what would be causing this?


#6

Glad you found it. Yeah, that’s probably the best approach.

Michael


#7

The B. wrote:

±—±------±---------±-----------±----------+
±—±------±---------±-----------±----------+

±—±------±---------±-----------±----------+
| id | login | verified | first_name | last_name |
±—±------±---------±-----------±----------+
| 1 | elmer | 1 | Elmer | Fudd |
| 2 | jane | 1 | Jane | Smith |
| 3 | john | 1 | John | Doe |
±—±------±---------±-----------±----------+

Any ideas what would be causing this?

That is because you are passing it user[:id] on your
find_or_create_by_by call.

Don’t do that.

Ray


#8

The B. wrote:

Ray B. wrote:

That is because you are passing it user[:id] on your
find_or_create_by_by call.

Don’t do that.

Well what am I supposed to pass it?

Depends on your use. From the discussion so far, it isn’t clear to me
why you want to use user[:id] in your create. If you have a user[:id]
then by definition you don’t need to create a user so find is
sufficient. If you don’t have a user[:id] yet then you need to create
one.

The problem is that you are getting the current user’s id in your
find_or_create_by_id. Either use logic to detect which is the case, or
maybe do a User.find_or_create_by_login(user[:login]) in your
controller.

If you just create a new user before calling your form then you can just
reference that user and not reference the user id at all.

Ray


#9

yeah, Ray’s right. I got myself in a tizzy and led you down a bad
path. I was working off of your original info. Sorry about that.

Michael


#10

Ray B. wrote:

That is because you are passing it user[:id] on your
find_or_create_by_by call.

Don’t do that.

Well what am I supposed to pass it?


#11

Ray B. wrote:

The B. wrote:

Ray B. wrote:

That is because you are passing it user[:id] on your
find_or_create_by_by call.

Don’t do that.

Well what am I supposed to pass it?

Depends on your use. From the discussion so far, it isn’t clear to me
why you want to use user[:id] in your create. If you have a user[:id]
then by definition you don’t need to create a user so find is
sufficient. If you don’t have a user[:id] yet then you need to create
one.

The problem is that you are getting the current user’s id in your
find_or_create_by_id. Either use logic to detect which is the case, or
maybe do a User.find_or_create_by_login(user[:login]) in your
controller.

If you just create a new user before calling your form then you can just
reference that user and not reference the user id at all.

I’ll look into this more indepth tomorrow. Thanks for the info. What
spawned my question was when I read the rails API about the save() call.

  • No record exists: Creates a new record with values matching those of
    the object attributes.
  • A record does exist: Updates the record with values matching those of
    the object attributes.

So my real question maybe should have been simpler. How does the save
call know that a record does exist?

So it sounds like from reading what you’ve said that I should be
calling:
User.find_or_by_user(params[:user])

I could be getting it completely wrong though :slight_smile: Basically what I’m
looking for is a call similar to the Hibernate saveOrUpdate call. I
want to pass the hash that comes from the edit form (which also acts as
a new user form) and let rails decide whether it should create a new row
or update an existing one.


#12

Michael T. wrote:

yes, you’re correct. Maybe this bit of code will help. I’m using
email, but it could just as well be username. Anything that would be
unique.

Excellent. Thanks for the help! I’ll get started on the changes
tomorrow.


#13

yes, you’re correct. Maybe this bit of code will help. I’m using
email, but it could just as well be username. Anything that would be
unique.

def signup
# send the email address to us
if params[:email].empty?
render :update do |page|
page.replace_html(‘feedback’,
‘Please enter a valid email address.’)
page.visual_effect :highlight, ‘flash’
end
else
begin
user = MailingList.find_or_create_by_email(params[:email])
user.save!
EmailNotify.send_mailing_list(user)
rescue
render :update do |page|
page.replace_html(‘feedback’,
‘There was a problem signing you up for
the mailing list. Please try again.’)
page.visual_effect :highlight, ‘feedback’
end
end
end
end


#14

Michael T. wrote:

yes, you’re correct. Maybe this bit of code will help. I’m using
email, but it could just as well be username. Anything that would be
unique.

Ok, using find_or_create_by_login (which should be fine since it’s
unique) will return me the record, and new_record? will work correctly
to tell me whether it was loaded from the DB or it’s new.

But what happens if there IS no unique column in the DB other than the
ID? I have another table that’s joined to the user table and it has no
unique columns.

I think I’ll just write my own method if Rails doesn’t have anything to
do this. I’m just looking for a Hibernate-ish saveOrUpdate.


#15

The B. wrote:

ID? I have another table that’s joined to the user table and it has no
unique columns.

  1. You don’t even have unique ids because the new user’s id is nil. You
    can have a large number of users in process with nil ids.

  2. The problem that you were having before was because you were using
    the name of an object that was not unique. You had a user creating or
    editing a user.

If instead you had a user creating or editing active_user, or some other
identifiers, then the user[:id] of the editing user would not be
confused with the active_user[:id].

Ray