5

On an edit page for the model Test I want to be able to update the "Questions.order" field on all of it's associated (by hasMany) questions from the same form.

I've ready the Cake book chapter on saveMany()/saveAll() in the book, and I'm using the Model.0.field syntax but I can't figure out how to tell CakePHP which record to corresponds to which input. Should the # in Model.#.field correspond to the question's id field? Here's what I'm currently doing:

 echo $this->Form->create( 'Question', array('action'=>'order'));

$n = 0;
foreach ($questions_array as $question) : ?>
        <?php echo $this->Form->input('Question.'.$n.'.order' ); ?>
        <?php echo $this->Form->input('Question.'.$n.'.id', array('type'=>'hidden', 'value'=>$question['Question']['id']) ); ?>
        <input type="submit" value="Save" />
...
$n++;
endforeach;
$this->Question->Form->end();

The form submits and appears to save, but the updated order values do not correspond to the right question records. What am I doing wrong?

Update:

Here is the order action in my controller:

public function admin_order() {
    $data = $this->request->data;
    $this->Question->saveAll($data['Question']);
    $this->Session->setFlash( "Order saved.");
    $this->redirect( $this->referer() );
}
emersonthis
  • 32,822
  • 59
  • 210
  • 375
  • 1
    Are you incrementing $n? – Dave Apr 21 '13 at 17:37
  • 1
    What is "Qset" and how does it fit in with your described question? – Dave Apr 21 '13 at 17:39
  • Dave has some good questions, also I see that your form contains a 'submit' button on *every row* (this shouldn't be a problem, just noticed it). Could you [edit] your question and add your controller-code as well, to see 'how' you're saving your data – thaJeztah Apr 21 '13 at 18:00
  • @Dave I am incrementing $n, and sorry about the confusion with "Qset". That is the actual name of my class, but I meant to replace it with Question to make it more clear. It was a copy/paste error, which I just corrected. – emersonthis Apr 21 '13 at 20:27
  • @thaJeztah: Yes I intentionally include a submit button next to each input (because the form may be very long). I just updated the question with the controller code. Thanks – emersonthis Apr 21 '13 at 20:30
  • In your updated question, you're using `$question['Qset']['id']`, shouldn't that be `$question['Question']['id']`? Same in your `Form->create('Qset')`? Finally, should the last line just be `echo $this->Form->end()`? – thaJeztah Apr 21 '13 at 20:37
  • @thaJeztah: Sorry again. ALL references to Qset should be Question. I was trying to make things clearer by renaming them for the question but it backfired. I think I got all of them fixed now. – emersonthis Apr 21 '13 at 20:53
  • I've tried to explain CakePHP's workings in my answer, just wondering if this explains your question, or is your code not working? – thaJeztah Apr 21 '13 at 21:01
  • @thaJeztah My code still isn't working. I think I understand your answer, but I think I'm already doing what you described. The `id` in the hidden value isn't the same because it's a key of whichever `$question` the loop is on. – emersonthis Apr 21 '13 at 21:05
  • Is it updating/doing anything at all? What exactly isn't working? – thaJeztah Apr 21 '13 at 21:14
  • @thaJeztah I just checked the db and it appears that all three values *are* getting saved to the right records, but for some reason only the first input is rendered in the view with the correct value. The others are empty. – emersonthis Apr 21 '13 at 21:17
  • Inputs created by the FormHelper are filled with values inside `$this->request->data`. You can manually fill that data with data from the database via your model. Otherwise, just set the 'value' of each input inside the loop; the same as you already did with the 'id' input – thaJeztah Apr 21 '13 at 21:22

2 Answers2

4

CakePHP associates all fields with the same 'index' to be a single 'record' in your database. The 'index' (i.e. the 0 in Foo.0.id) does not have any relation to the 'id' of the record, it's just a number.

For example;

Foo.0.id   = 123
Foo.0.name = 'hello';
Foo.1.id   = 124
Foo.1.name = 'world';

As mentioned in the start of my answer, the index itself does not matter, this code will do exactly the same:

Foo.12345.id   = 123
Foo.12345.name = 'hello';
Foo.54321.id   = 124
Foo.54321.name = 'world';

As long as fields of the same record have the same 'index', CakePHP will understand that they belong 'together'.

When submitting this data and saving it using;

$this->Foo->saveMany($this->data['Foo']); // Just $this->data will probably work as well

CakePHP update two rows via the Foo model;

table 'foos';

id     name
------------------------
123    hello
124    world

Your code seems to use the same 'id' ($qset['Qset']['id']) for each row, which is probably not the right ID to update those records

thaJeztah
  • 27,738
  • 9
  • 73
  • 92
  • After reviewing your answer and my code, I still don't understand how/why CakePHP knows which value to assign to `$this->Form->input('Question.'.$n.'.order' );` My understanding is that `$n` is used to "batch" inputs into single records, but how does CakePHP know which row in the database corresponds to `$n`? – emersonthis Apr 21 '13 at 21:11
  • @Emerson it knows which record to update based on the 'id' field; `Foo.0.id = 123` and `Foo.0.name = 'hello'` will therefore do something like `UPDATE foos SET name='hello' WHERE id = 123;` – thaJeztah Apr 21 '13 at 21:18
  • I did an experiment. I added `'value' => $question['Question']['order']` to the options array for the input and now all the values appear correctly. Not sure why that made a difference. – emersonthis Apr 21 '13 at 21:30
  • Just saw your comment above. That seemed to do it. Thanks! Is it surprising that it was necessary to do that? I'm used to Cake knowing the input's value automatically. – emersonthis Apr 21 '13 at 21:32
  • It will know the right values based on `$request->data`, which is filled with the posted data if you submit the form. If no form was posted, you should fill it with data from the database; e.g `$this->request->data = $this->Foo->find('all');` or something similar. however, **only** do this if the form has *not* been posted – thaJeztah Apr 21 '13 at 21:35
  • I was talking about in the view. It seems like the controller was saving correctly all along. I was wondering why the form helper seemed to require me to overtly set the value. – emersonthis Apr 21 '13 at 22:06
  • 1
    Yup, I understood, however, `$this->request` inside the controller is a reference to the **same** request object that is used inside your view. To demonstrate, try this inside your controller: `$this->request->data['hello'] = 'world';` and this in your view: `echo $this->request['hello'];`. That data is used by the FormHelper to automatically fill the values of your inputs. If the request-object contains the data in the right format, you don't have to set each inputs value manually. – thaJeztah Apr 21 '13 at 22:13
0
As we can see at CakePHP Book, the instruction is:

// Create: id isn't set or is null
$this->Recipe->create();
$this->Recipe->save($this->request->data);

// **Update**: id is set to a numerical value
$this->Recipe->id = 2;
$this->Recipe->save($this->request->data);

Considering there are still people looking for this answer in 2014 You must use in the
View: /* (considering the $n counter)*/
$this->input('Question.'.$n.'.order');
$this->input('Question.'.$n.'.id');
Controller: $this->Question->updateAll->($this->request->data['Question']);

Matteus Barbosa
  • 2,409
  • 20
  • 21