Forum: Ruby on Rails validate_on_update with a save()

29d82d169c6037d4cbafce0b20fcf04e?d=identicon&s=25 The Barge (thebarge)
on 2006-04-25 18:44
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?
455ac2a64d06dc8461f4d258d7f7e980?d=identicon&s=25 Michael Trier (Guest)
on 2006-04-25 20:03
(Received via mailing list)
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
29d82d169c6037d4cbafce0b20fcf04e?d=identicon&s=25 The Barge (thebarge)
on 2006-04-25 20:14
Michael Trier 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.
29d82d169c6037d4cbafce0b20fcf04e?d=identicon&s=25 The Barge (thebarge)
on 2006-04-25 20:33
The Barge 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
455ac2a64d06dc8461f4d258d7f7e980?d=identicon&s=25 Michael Trier (Guest)
on 2006-04-25 21:17
(Received via mailing list)
Glad you found it.  Yeah, that's probably the best approach.

Michael
29d82d169c6037d4cbafce0b20fcf04e?d=identicon&s=25 The Barge (thebarge)
on 2006-04-25 22:26
Michael Trier 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?
E28c35323f624b8b9ed8712e25105454?d=identicon&s=25 Ray Baxter (Guest)
on 2006-04-25 22:53
(Received via mailing list)
The Barge 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
29d82d169c6037d4cbafce0b20fcf04e?d=identicon&s=25 The Barge (thebarge)
on 2006-04-25 23:01
Ray Baxter 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?
E28c35323f624b8b9ed8712e25105454?d=identicon&s=25 Ray Baxter (Guest)
on 2006-04-25 23:27
(Received via mailing list)
The Barge wrote:
> Ray Baxter 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
455ac2a64d06dc8461f4d258d7f7e980?d=identicon&s=25 Michael Trier (Guest)
on 2006-04-26 00:02
(Received via mailing list)
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
29d82d169c6037d4cbafce0b20fcf04e?d=identicon&s=25 The Barge (thebarge)
on 2006-04-26 00:12
Ray Baxter wrote:
> The Barge wrote:
>> Ray Baxter 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 :)  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.
455ac2a64d06dc8461f4d258d7f7e980?d=identicon&s=25 Michael Trier (Guest)
on 2006-04-26 00:27
(Received via mailing list)
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
29d82d169c6037d4cbafce0b20fcf04e?d=identicon&s=25 The Barge (thebarge)
on 2006-04-26 00:38
Michael Trier 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.
29d82d169c6037d4cbafce0b20fcf04e?d=identicon&s=25 The Barge (thebarge)
on 2006-04-26 14:56
Michael Trier 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.
E28c35323f624b8b9ed8712e25105454?d=identicon&s=25 Ray Baxter (Guest)
on 2006-04-26 15:38
(Received via mailing list)
The Barge 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
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.