Editing multiple records with saveAll()

Another interesting topic was brought up on IRC channel today…

(By the way if you don’t know about the awesome CakePHP IRC channel, you really should visit, it is a great place to get help and learn a thing or two… irc://irc.freenode.net/cakephp)

Anyways, moving on… How do we edit multiple records at once by using the good ol’ saveAll() method?

One immediate problem is that when we do $this->SomeModel->find('all');, the data array is formatted like this:

Array
(
    [0] => Array
        (
            [Profile] => Array
                (
                    [id] => 21
                    [name] => bob 3                    
                    [created] => 2008-10-27 13:01:30                   
                )

        )

    [1] => Array
        (
            [Profile] => Array
                (
                    [id] => 20                    
                    [name] => larry 5                    
                    [created] => 2008-10-27 13:01:30                    
                )

        )

However, we know that in order for saveAll() to work correctly the data should be formatted like this:


[Profile] => Array
        (
            [21] => Array
                (
                    [id] => 21                    
                    [name] => bob 3                    
                    [created] => 2008-10-27 13:01:30
                    
                )

            [20] => Array
                (
                    [id] => 20                    
                    [name] => larry 5                    
                    [created] => 2008-10-27 13:01:30                    
                )

Notice the difference of the key placement and the array structure?

Alright, let’s examine a super-easy way to get what we need by using the lovely Set class, and namely the Set::combine() method.

First, we build our edit action keeping in mind that we need the data in a form to follow a specific format for saveAll():

function edit() {
   if(!empty($this->data)) {
      $this->Profile->saveAll($this->data['Profile']);
   }
   else {
      $this->data['Profile'] = Set::combine($this->Profile->find('all'), '{n}.Profile.id', '{n}.Profile');
   }
}

We use Set::combine() to easily reformat the return of find('all') (to look just like an example array above) and, of course, assign it to $this->data, which is going to be used by the Form Helper.

Now, all we need to do is build our form (let’s make something really simple):

    echo $form->create('Profile', array('action'=>'edit'));
    
    foreach($this->data['Profile'] as $key => $value) {
        echo $form->input('Profile.'.$key.'.name');
        echo $form->input('Profile.'.$key.'.id');
    }
    
    echo $form->end('Save All Profiles');

This form allows us to rename multiple Profiles at once. It is built from our $this->data array and follows exactly the right format, which allows the saveAll() method to work correctly.

About these ads

37 Responses to Editing multiple records with saveAll()

  1. Phally says:

    Great to see you are doing something with question from the IRC channel! It is good that this is finally clearly documentated. Things like these can really bring up frustrations.

  2. Neil Crookes says:

    Do you have any thoughts on how you should go about validating the data submitted in each profile?

  3. teknoid says:

    @Phally

    I steal most of ideas for the posts from the questions on IRC ;)

  4. teknoid says:

    @Neil Crookes

    Sure, just do: saveAll($yourData, array(‘validate’=>’first’));

  5. Phally says:

    @teknoid

    Doesn’t matter… Keep on going! It is good that someone writes down solutions to common problems!

  6. teknoid says:

    @Phally

    Thanks for the encouragement and positive feedback, I really do appreciate it.

  7. Frank says:

    Nice post, reminds me that I should look into using the Set class more as it seems really handy. Cheers!

  8. teknoid says:

    @Frank

    Thanks. Yeah Set is full of goodies :)

  9. Nik Chankov says:

    Nice approach, I didn’t know about saveAll() function, it’s good to know feature. But I have God’s feeling that later it would transform to $this->Model->save(‘all’, $data);

  10. teknoid, although I didn’t need to format my data array/object for this specific case (actually wanted it reformatted to make it easier to work with JSON, specifically) and knew I’d need to look at the set class to determine which method to use. I can put that off a bit longer now thanks to this article…which may be a good thing or a bad thing, depending… ;) Thanks, as always, for your articles!

  11. teknoid says:

    @Brendon Kozlowski

    No problem, glad it inspires some ideas. That’s the most important thing :)

  12. teknoid says:

    @Nik Chankov

    Hmmm, interesting thought :) I don’t know… I can’t imagine that save() would have a lot of different types… but maybe it could become save(‘field’), update(‘all’)… etc. I guess we’ll wait and see.

    All that being said, saveAll() is actually very powerful, I have a few other posts showing how useful it can be to avoid nasty foreach() loops, etc. and of course to save multiple models at once.

  13. luke says:

    teknoid, am sur ethis was my question :) great post!

    boobyW

  14. teknoid says:

    @luke

    I think it came from alkemann, unless of course you are also him ;)
    At any rate, if it helped… I’m happy :)

  15. rafaelbandeira3 says:

    Is it a bird? Is it a plane? No, it’s TEKNOID saving the day again and again!
    thanks just saved me some precious time.

  16. teknoid says:

    @rafaelbandeira3

    No problem ;) Glad to help…

  17. Pingback: Сохранение нескольких записей с помощью saveAll | Bunyan.Ru

  18. I would also like to thank you for this post. And because it wasn’t clear to me at first glance I would like to mention that your solution automagically fills the form input boxes with database values loaded using find(‘all’) and Set::combine().

    A truly cakish solution.
    Thanks again
    Petr

  19. teknoid says:

    @Petr

    I’m glad it helped out ;)

    Cheers

  20. antispin says:

    Didn’t work for me but should have :) it just kept saving a single blank record instead of my data. Had to use a loop and saveField() instead… just an alternative in case this happens to someone else.

  21. antispin says:

    ^^ should have pointed out that using saveField() is a HORRIBLE, INEFFICIENT, UGLY alternative but it does work. Teknoid’s solution is much better.

  22. teknoid says:

    @antispin

    Don’t give up so quick :) http://bin.cakephp.org/
    Post your before/after arrays and save() code.

  23. Pat says:

    Hi All:

    Great article Teknoid. I’m curious to know how this might work with the example you used in one of your HABTM multiple-record save examples used in some of your other posts (example: Accounts->Contacts). Would there be a short-hand method of pulling up all the data for the parent record, along with all the children records and do a mass save after doing edits to those records/fields?

    Thanks, as usual, for all the helpful articles and active participation in the community.

  24. teknoid says:

    @Pat

    The approach would be the same, the important thing to keep in mind is that your data array must adhere to the right format and that field names in the view are properly named.

    … and thanks for the kind words ;)

    • Pat says:

      @Teknoid

      Thanks for the quick reply!

      So, then, would I be able to achieve this simply by doing a recursive find from the parent model? For example, doing the recursive find on Accounts; which would return the Accounts record with the desired id and all the Contacts records with the same foreign key id made in the find query.

      Would I be correct in that assumption? Or am I way off base? :-)

  25. teknoid says:

    @Pat

    No, you are correct.
    The only thing you might need to do is to “massage” your data array as in the example.
    Again, it depends on the returned array, which is formatted according to the associations between the models. (i.e. hasOne is a little different from hasMany or HABTM)

  26. Rokas says:

    find(‘all’) also can format that array with

    # echo $form->create(‘Profile’, array(‘action’=>’edit’));
    #
    # foreach($profiles as $profile) {
    # echo $form->input(‘Profile.’.$profile['Profile']['id'].’.name’);
    # echo $form->input(‘Profile.’.$profile['Profile']['id'].’.id’);
    # }
    #
    # echo $form->end(‘Save All Profiles’);

  27. teknoid says:

    @Rokas

    Sure, but if your data array doesn’t match the field structure the fields will not be populated, unless there’s more magic in the core now since I’ve wrote this.

  28. Aldo says:

    What happens if a the content of a field has been deleted?
    saveAll does not delete the empty record from the database.
    Any idea how to handle that?

  29. teknoid says:

    @Aldo

    How do you wind up with “empty” records in the first place?

  30. Aldo says:

    Let’s say in your example the name “bob 3″ is deleted from the input field.

    [Profile] => Array
    (
    [21] => Array
    (
    [id] => 21
    [name] =>
    [created] => 2008-10-27 13:01:30

    )

    [20] => Array
    (
    [id] => 20
    [name] => larry 5
    [created] => 2008-10-27 13:01:30
    )

    After saving, the row in the DB still remains, but empty.
    I’m looking for a way to check this empty fields, so the record, corresponding to this field is deleted while the reamining entries are updated/saved.

  31. teknoid says:

    @Aldo

    That should be handled by validation, if the name is not present, the form should fail.
    This is accomplished with ‘validate’ => ‘first’ (see some other saveAll posts here).

    Or if someone tampers with the form data use the Security component to ensure form integrity (I have a few posts about that as well).

    That being said, if you are trying to delete the record on purpose, there is no automated way to do it with saveAll(). Nor, do I think it should be handled in same manner as editing/saving anyway.

  32. Joe Cabezas says:

    thank you, it helps me a lot!

    greetings from Chile, LA !!

    bye!

  33. teknoid says:

    @Joe Cabezas

    Good to hear. You’re welcome.

  34. Nancy M says:

    What if you have new rows mixed in with existing ones? Lets say I want my edit form to pop up an empty form to add another row, but also save the existing ones. So far the only solution I’ve found is awful, I have to assign a random ID, and it could conceivably reuse one already out there.

  35. Harsha M V says:

    Is there any way to unset that marked element of the to so i dont get a blank entry in the database.

    Note: The entering of the Phone has an ajax feature where users can add extra field on the go to enter extra number. taking into consideration some one clicked add field by mistake and then submitted the form.

  36. teknoid says:

    @Harsha M V

    In your beforeSave() you should “step” through the array, and look for any empty values. If one is found, unset the relevant key.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 25 other followers

%d bloggers like this: