fuel / orm

Fuel PHP Framework - Fuel v1.x ORM
http://fuelphp.com/docs/packages/orm/intro.html
152 stars 96 forks source link

Belongs_to relation not saved/altered when calling save() after deleting a related model [1.7/develop] #283

Open philipptempel opened 11 years ago

philipptempel commented 11 years ago

Assume two models, \Model\Record and \Model\Group, with a \Model\Record belongs_to \Model\Group, \Model\Group has_many \Model\Record.

When doing something like the following:

<?php
// Get the record
$record = \Model\Record::find(<some arbitrary ID>);

// Change the assigned group
$record->group = \Model\Group::find(<some arbitrary ID>);

/*
 * Assign some other properties and related data here, too
 */

// Save at the end to save on DB-queries
$record->save()

then $record->group will be changed to the new ORM-object, but the property of \Model\Record keeping the reference to its assigned group is not updated.

An exemplary result of \Debug::dump($record->to_array()); before and after assigning the group as well as after calling save() is listed below:

Before changing the group

     surname (String): "Tempel" (6 characters)
     prename (String): "Philipp" (7 characters)
     archived (Boolean): false
     created_at (Integer): 1374585392
     updated_at (Integer): 1374587495
     archived_at : null
     record_id (String): "1" (1 characters)
     rel_group_id (String): "1" (1 characters)
     meta (Array, 82 elements) ↵
     group (Array, 7 elements) ↵
         group_id (String): "1" (1 characters)
         name (String): "Student" (7 characters)
         slug (String): "student" (7 characters)
         protected (Boolean): true
         created_at (Integer): 1374478794
         updated_at (Integer): 1374478794

After changing the group

     surname (String): "Tempel" (6 characters)
     prename (String): "Philipp" (7 characters)
     archived (Boolean): false
     created_at (Integer): 1374585392
     updated_at (Integer): 1374587495
     archived_at : null
     record_id (String): "1" (1 characters)
     rel_group_id (String): "1" (1 characters)
     meta (Array, 82 elements) ↵
     group (Array, 6 elements) ↵
         group_id (String): "2" (1 characters)
         name (String): "Mitarbeiter" (11 characters)
         slug (String): "mitarbeiter" (11 characters)
         protected (Boolean): true
         created_at (Integer): 1374478794
         updated_at (Integer): 1374478794

After save()

     surname (String): "Tempel" (6 characters)
     prename (String): "Philipp" (7 characters)
     archived (Boolean): false
     created_at (Integer): 1374585392
     updated_at (Integer): 1374587599
     archived_at : null
     record_id (String): "1" (1 characters)
     rel_group_id (String): "1" (1 characters)
     meta (Array, 86 elements) ↵
     group (Array, 6 elements) ↵
         group_id (String): "2" (1 characters)
         name (String): "Mitarbeiter" (11 characters)
         slug (String): "mitarbeiter" (11 characters)
         protected (Boolean): true
         created_at (Integer): 1374478794
         updated_at (Integer): 1374478794

If save() is called right after changing the group, it works perfectly, however.

Using 1.6/master changes the output of the last \Debug::dump to rel_group_id (String): "2" (1 characters) which is the expected behavior.

philipptempel commented 11 years ago

I was just talking with uru on #fuelphp and we narrowed the issue a littler further down:

The problem of non-saved changed relations arises in my case when another relation is deleted before or after changing other relations. Sample code is as follows:

<?php

// Get the record to edit from the DB
$record = \Model\Record::query()
  ->where('id', '=', '1')
  ->related('group')
  ->related('meta')
  ->get_one();

// Change the record's group according to a form input
$record->group = \Model\Group::find(\Input::post('group_id'));

// Delete the old meta-fields (this might not be best practice, but should cause the problem anyhow)
foreach ( $record->meta as $k => $meta )
{
  // Unsetting the meta was added on advice from uru, but did not change the result
  unset($record->meta[$k]);
  $meta->delete();
}

// Then, add new meta values to the record
foreach ( \Input::post('meta', array()) as $field_id => $value )
{
  // This just checks whether there is a value and if it's a valid Profilefield
  if ( $value && $field = \Model\Profilefield::find($field_id) )
  {
    $meta = new \Model\Recordmeta(array('value' => $value));
    $meta->field = $field;
    $record->meta[] = $meta;
  }
}

// This will change the meta fields, but will not change the group assigned to the record
$record->save();

Used models are related like so:

record belongs_to group
record has_many meta
group many_many profilefield
group has_many record
meta belongs_to record, profilefield
profilefield many_many group

Even if I first change the meta-fields and then change the group, it still does not save/alter the assigned group.

awgneo commented 10 years ago

Same basic issue here :/ I have re-order all of my relationship assignments to after metadata setting to avoid this issue for now. I am using the latest 1.7/master. 1.8/develop also has the same issue.

awgneo commented 10 years ago

Grrr..... re-ordering the assignment of metadata still leads to unpredictable results. Of course, you can also avoid this by setting the foreign ids directory instead of assigning objects, but this kind of defeats the point of having an ORM.

WanWizard commented 10 years ago

There were some fixes today related to this, are you using an up to date 1.8/develop? An intermediate action on the object would cause the original relation data to be reset, causing your new records not to be seen as new anymore (and therefore not saved).

awgneo commented 10 years ago

Hrm... I don't see any recent commits other than the one to classes/manymany.php. I will look out for it though! Thanks for the update.

WanWizard commented 10 years ago

Ah, sorry, my bad.

It was discussed today in IRC, I tested it but didn't commit it because someone else was already working on it and would send it in as a PR. Which I haven't seen yet.

webfacer commented 10 years ago

same issue here this helped me out http://fuelphp.com/forums/discussion/comment/17145

EDIT: i just modified it a little bit for using it even if no relation is generated. It means when no model is selected it would throw an error that the child model is empty. here how i use it:

public function action_edit( $id )
{
    // gets newsletter tabel with the articles relation
    $newsletter = \Model_Newsletter::query()->where( 'id', $id )->related( 'articles' )->get_one();

    // gets only articles for selecting relation
    $articles = \Model_Article::find( 'all' );

    if( $post = \Fuel\Core\Input::post() )
    {
        $model = \Model_Newsletter::find( $id );
        $model->title = $post['title'];

        if( isset( $post['articles'] ) && !empty( $post['articles'] ) )
        {
            $new_relation_models = array();
            foreach( $post['articles']['id'] as $key => $value )
            {
                if( $post['articles']['id'][$key] )
                {
                    $new_relation_models[ $value ] = \Model_Article::find( $value );
                }
                else
                {
                    unset( $post['articles']['id'][$key] );
                }
            }

            foreach( $model->articles as $new_relation_model )
            {
                if( !in_array( $value, $post['articles']['id'] ) )
                {
                    unset( $model->articles[ $value ] );
                }
            }

            $model->articles = $new_relation_models;

            if( $model->save() )
            {
                return \Fuel\Core\Response::redirect( 'admin/newsletters/edit/' . $newsletter->id );
            }
        }
    }