FuelPHP 1.6 の Model_Soft を利用して、delete(); した時関連するレコードの deleted_at が更新されない問題に暫定対処する

FuelPHP 1.6 の Model_Soft を利用して delete(); を行なっても、関連するテーブルの deleted_at が更新されない。
また、同様に、 FuelPHP 1.6 の Model_Soft を利用している状態で、 delete 対象から belongs_to な関連テーブルが有るとき、belongs_to になるべき外部キーが0になってしまう問題に対して、勝手に暫定的な対処をする。
※belongs_to.php への修正は Insert 時に問題が起きるかもしれない。(未検証なので、一旦書き終えて時間が出来た時に調査の上必要があれば内容を修正します。)

もしかすると、1.6以外も同様の問題があるかも知れんけど、それは未確認。
後で時間が出来たら確認してみる。

あと、manymany のケースでの挙動は未確認。

もっとスマートな対象方法があるのかも知れんけど、とりあえず、自分用のメモとして。
おかしな所とかあったら突っ込んで下さい。

## 準備

### テーブルとモデルの作成

仮に、以下の3テーブルが存在しているとする。
1. parent
2. child
3. child_profile

parent -> (has_many) -> child -> (has_many) -> child_profile な感じにリレーションする。
※反対向きには belongs_to とする。

```
oil g model test_rel_parent name:string -f --soft-delete
oil g model test_rel_child name:string parent_id:int -f --soft-delete
oil g model test_rel_child_profile key:string value:string child_id:int -f --soft-delete
```

※オプションにある「--soft-delete」は勝手に追加したやつ："FuelPHP (1.6) で oil を改造して oil generate admin... --soft-delete で論理削除モデル使えるように勝手対応" http://qiita.com/items/4642905f4d78e0139590

--soft-delete なんてオプション勝手につけるとかあり得んやろ…な方(僕自身も現状ではイマイチかもとか思ってます…)は、

```
    protected static $_properties = array(
// (略)
        'deleted_at',
// (略)
    );
```

```
protected static $_soft_delete = array(
    'mysql_timestamp' => false,
);
```

辺りを良しなに追加して下さい。

### リレーションの設定

それぞれ、_has_many, _belongs_to を設定しておく。

```APPPATH/classes/model/test/rel/parent.php
<?php

class Model_Test_Rel_Parent extends \Orm\Model_Soft
{
    protected static $_properties = array(
        'id',
        'name',
        'created_at',
        'updated_at',
        'deleted_at',
    );

    protected static $_observers = array(
        'Orm\Observer_CreatedAt' => array(
            'events' => array('before_insert'),
            'mysql_timestamp' => false,
        ),
        'Orm\Observer_UpdatedAt' => array(
            'events' => array('before_update'),
            'mysql_timestamp' => false,
        ),
    );

    protected static $_soft_delete = array(
        'mysql_timestamp' => false,
    );

    protected static $_has_many = array(
        'children' => array(
            'key_from' => 'id',
            'key_to' => 'parent_id',
            'model_to' => 'Model_Test_Rel_Child',
            'cascade_save' => true,
            'cascade_delete' => true,
        )
    );

    protected static $_table_name = 'test_rel_parents';

}
```

```APPPATH/classes/model/test/rel/child.php
<?php

class Model_Test_Rel_Child extends \Orm\Model_Soft
{
    protected static $_properties = array(
        'id',
        'name',
        'parent_id',
        'created_at',
        'updated_at',
        'deleted_at',
    );

    protected static $_observers = array(
        'Orm\Observer_CreatedAt' => array(
            'events' => array('before_insert'),
            'mysql_timestamp' => false,
        ),
        'Orm\Observer_UpdatedAt' => array(
            'events' => array('before_update'),
            'mysql_timestamp' => false,
        ),
    );

    protected static $_soft_delete = array(
        'mysql_timestamp' => false,
    );

    protected static $_belongs_to = array(
        'parent' => array(
            'model_to' => 'Model_Test_Rel_Parent',
            'key_from' => 'parent_id',
            'key_to'   => 'id',
            'cascade_save' => true,
            'cascade_delete' => false,
        ),
    );

    protected static $_has_many = array(
        'profiles' => array(
            'key_from' => 'id',
            'key_to' => 'child_id',
            'model_to' => 'Model_Test_Rel_Child_Profile',
            'cascade_save' => true,
            'cascade_delete' => true,
        ),
    );

    protected static $_table_name = 'test_rel_children';

}
```

```APPPATH/classes/model/test/rel/child/profile.php
<?php

class Model_Test_Rel_Child_Profile extends \Orm\Model_Soft
{
    protected static $_properties = array(
        'id',
        'key',
        'value',
        'child_id',
        'created_at',
        'updated_at',
        'deleted_at',
    );

    protected static $_observers = array(
        'Orm\Observer_CreatedAt' => array(
            'events' => array('before_insert'),
            'mysql_timestamp' => false,
        ),
        'Orm\Observer_UpdatedAt' => array(
            'events' => array('before_update'),
            'mysql_timestamp' => false,
        ),
    );

    protected static $_soft_delete = array(
        'mysql_timestamp' => false,
    );

    protected static $_belongs_to = array(
        'child' => array(
            'model_to' => 'Model_Test_Rel_Child',
            'key_from' => 'child_id',
            'key_to'   => 'id',
            'cascade_save' => true,
            'cascade_delete' => false,
        ),
    );

    protected static $_table_name = 'test_rel_child_profiles';

}
```

### 検証用のデータを入れる

とりあえず検証用のデータだけ欲しいなら、以下を何処かから実行したらいいと思う。
たぶん、 Controller に検証用データの初期化 action を書いたほうが確認としては色々捗ると思う。

```php
        // parent
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Parent::table() . '(name,created_at,updated_at) VALUES ("parent1", ' . \Date::forge()->get_timestamp() . ', null)')->execute();
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Parent::table() . '(name,created_at,updated_at) VALUES ("parent2", ' . \Date::forge()->get_timestamp() . ', null)')->execute();
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Parent::table() . '(name,created_at,updated_at) VALUES ("parent3", ' . \Date::forge()->get_timestamp() . ', null)')->execute();

        // child
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Child::table() . '(name, parent_id,created_at,updated_at) VALUES ("parent1_child1", 1, ' . \Date::forge()->get_timestamp() . ', null)')->execute();
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Child::table() . '(name, parent_id,created_at,updated_at) VALUES ("parent1_child2", 1, ' . \Date::forge()->get_timestamp() . ', null)')->execute();
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Child::table() . '(name, parent_id,created_at,updated_at) VALUES ("parent2_child1", 2, ' . \Date::forge()->get_timestamp() . ', null)')->execute();
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Child::table() . '(name, parent_id,created_at,updated_at) VALUES ("parent1_child3", 1, ' . \Date::forge()->get_timestamp() . ', null)')->execute();

        // child_profile
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Child_Profile::table() . '(child_id,`key`,value,created_at,updated_at) VALUES (1, "child1_prof1", "content_child1_prof1", ' . \Date::forge()->get_timestamp() . ', null)')->execute();
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Child_Profile::table() . '(child_id,`key`,value,created_at,updated_at) VALUES (1, "child1_prof2", "content_child1_prof1", ' . \Date::forge()->get_timestamp() . ', null)')->execute();
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Child_Profile::table() . '(child_id,`key`,value,created_at,updated_at) VALUES (2, "child2_prof1", "content_child2_prof1", ' . \Date::forge()->get_timestamp() . ', null)')->execute();
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Child_Profile::table() . '(child_id,`key`,value,created_at,updated_at) VALUES (3, "child3_prof1", "content_child2_prof1", ' . \Date::forge()->get_timestamp() . ', null)')->execute();
```

これで、一応は $model->delete(); が動作するようになったはず。

## 実際にdelete(); してみる

### まずはController の作成

Controller では、検証用データの初期化と、削除時の各テーブルの結果を確認できるように DB::query を用いて、取得した結果を各テーブルごとに出力してみる。
※ PHPUnit 使え！ちゃんと views 使え！という意見はごもっともなのですが、いろいろ面倒でして…とりあえず許して。。。必要な人は PHPUnit や views に対応する様に書き換えて下さい。

```APPPATH/classes/controller/test/rel.php
<?php
class Controller_Test_Rel extends Controller
{

    public function action_index()
    {
        echo '<!DOCTYPE html><html><head><meta charset="UTF-8"><title>cascade_delete review</title><style>table{font-size: 0.8em;}</style></head><body style="font-size:0.9em;">';
        echo '<h3>Model_Orm_Soft での cascade_delete の動作を確認する。</h3><hr />';
        echo Html::ul(array(
            Html::anchor('test/rel', 'index'),
            Html::anchor('test/rel/truncate', 'truncate'),
            Html::anchor('test/rel/generate_from_sql', 'truncate, generate from_sql'),
        ));

        $message = Session::get_flash('msg') ? Session::get_flash('msg') : '&nbsp;';
        echo '<hr /><p>message : <span style="color:#ff0000;">' . $message . '</span></p>';
        echo '<hr />';

        $query = DB::query('SELECT * FROM ' . Model_Test_Rel_Parent::table())->execute();
        echo '<div style="float: left;margin: 10px;font-size: 0.9em;">';
        echo '<p style="font-weight: strong;font-weight: bold;">Model_Test_Rel_Parent : table = ' . Model_Test_Rel_Parent::table() . '</p>';
        echo '<p>';
        echo 'Model_Test_Rel_Parent の cascadeの状態<br />';
        foreach (Model_Test_Rel_Parent::relations() as $rel_name => $rel)
        {
            echo '　　<span style="color: ' . (($rel->cascade_save) ? 'blue' : 'red') . '">' . $rel_name . ' -> cascade_save = ' . $rel->cascade_save . '</span><br />';
            echo '　　<span style="color: ' . (($rel->cascade_delete) ? 'blue' : 'red') . '">' . $rel_name . ' -> cascade_delete = ' . $rel->cascade_delete . '</span><br />';
        }
        echo '</p>';
        echo '<p>※変更する場合は、 Model の Property を変更して下さい。</p>';
        echo '<table border="1">';
        echo '<tr style="background-color: #999999;">';
        echo '<th scope="col">id</th>';
        echo '<th scope="col">name</th>';
        echo '<th scope="col">created_at</th>';
        echo '<th scope="col">updated_at</th>';
        echo '<th scope="col">deleted_at</th>';
        echo '<th scope="col">delete</th>';
        echo '</tr>';
        if (!count($query))
        {
            echo '<tr><td colspan="6">no records</td></tr>';
        }
        else
        {
            foreach($query as $row)
            {
                echo '<tr>';
                echo '<td>'.$row['id'].'</td>';
                echo '<td>'.$row['name'].'</td>';
                echo '<td>'.(is_null($row['created_at']) ? 'null' : $row['created_at']).'</td>';
                echo '<td>'.(is_null($row['updated_at']) ? 'null' : $row['updated_at']).'</td>';
                echo '<td>'.(is_null($row['deleted_at']) ? 'null' : $row['deleted_at']).'</td>';
                echo '<td>'. Html::anchor('test/rel/delete_parent/' . $row['id'], 'delete') .'</td>';
                echo '</tr>';
            }
        }
        echo '</table>';
        echo '</div>';


        $query = DB::query('SELECT * FROM ' . Model_Test_Rel_Child::table())->execute();
        echo '<div style="float: left;margin: 10px;font-size: 0.9em;">';
        echo '<p style="font-weight: strong;font-weight: bold;">Model_Test_Rel_Child : table = ' . Model_Test_Rel_Child::table() . '</p>';
        echo '<p>';
        echo 'Model_Test_Rel_Child の cascadeの状態<br />';
        foreach (Model_Test_Rel_Child::relations() as $rel_name => $rel)
        {
            echo '　　<span style="color: ' . (($rel->cascade_save) ? 'blue' : 'red') . '">' . $rel_name . ' -> cascade_save = ' . $rel->cascade_save . '</span><br />';
            echo '　　<span style="color: ' . (($rel->cascade_delete) ? 'blue' : 'red') . '">' . $rel_name . ' -> cascade_delete = ' . $rel->cascade_delete . '</span><br />';
        }
        echo '</p>';
        echo '<p>※変更する場合は、 Model の Property を変更して下さい。</p>';
        echo '<table border="1">';
        echo '<tr style="background-color: #999999;">';
        echo '<th scope="col">id</th>';
        echo '<th scope="col">parent_id</th>';
        echo '<th scope="col">name</th>';
        echo '<th scope="col">created_at</th>';
        echo '<th scope="col">updated_at</th>';
        echo '<th scope="col">deleted_at</th>';
        echo '<th scope="col">delete</th>';
        echo '</tr>';
        if (!count($query))
        {
            echo '<tr><td colspan="8">no records</td></tr>';
        }
        else
        {
            foreach($query as $row)
            {
                echo '<tr>';
                echo '<td>'.$row['id'].'</td>';
                echo '<td>'.$row['parent_id'].'</td>';
                echo '<td>'.$row['name'].'</td>';
                echo '<td>'.(is_null($row['created_at']) ? 'null' : $row['created_at']).'</td>';
                echo '<td>'.(is_null($row['updated_at']) ? 'null' : $row['updated_at']).'</td>';
                echo '<td>'.(is_null($row['deleted_at']) ? 'null' : $row['deleted_at']).'</td>';
                echo '<td>'. Html::anchor('test/rel/delete_child/' . $row['id'], 'delete') .'</td>';
                echo '</tr>';
            }
        }
        echo '</table>';
        echo '</div>';


        $query = DB::query('SELECT * FROM ' . Model_Test_Rel_Child_Profile::table())->execute();
        echo '<div style="float: left;margin: 10px;font-size: 0.9em;">';
        echo '<p style="font-weight: strong;font-weight: bold;">Model_Test_Rel_Child_Profile : table = ' . Model_Test_Rel_Child_Profile::table() . '</p>';
        echo '<p>';
        echo 'Model_Test_Rel_Child_Profile の cascadeの状態<br />';
        foreach (Model_Test_Rel_Child_Profile::relations() as $rel_name => $rel)
        {
            echo '　　<span style="color: ' . (($rel->cascade_save) ? 'blue' : 'red') . '">' . $rel_name . ' -> cascade_save = ' . $rel->cascade_save . '</span><br />';
            echo '　　<span style="color: ' . (($rel->cascade_delete) ? 'blue' : 'red') . '">' . $rel_name . ' -> cascade_delete = ' . $rel->cascade_delete . '</span><br />';
        }
        echo '</p>';
        echo '<p>※変更する場合は、 Model の Property を変更して下さい。</p>';
        echo '<table border="1">';
        echo '<tr style="background-color: #999999;">';
        echo '<th scope="col">id</th>';
        echo '<th scope="col">child_id</th>';
        echo '<th scope="col">key</th>';
        echo '<th scope="col">value</th>';
        echo '<th scope="col">created_at</th>';
        echo '<th scope="col">updated_at</th>';
        echo '<th scope="col">deleted_at</th>';
        echo '<th scope="col">delete</th>';
        echo '</tr>';
        if (!count($query))
        {
            echo '<tr><td colspan="9">no records</td></tr>';
        }
        else
        {
            foreach($query as $row)
            {
                echo '<tr>';
                echo '<td>'.$row['id'].'</td>';
                echo '<td>'.$row['child_id'].'</td>';
                echo '<td>'.$row['key'].'</td>';
                echo '<td>'.$row['value'].'</td>';
                echo '<td>'.(is_null($row['created_at']) ? 'null' : $row['created_at']).'</td>';
                echo '<td>'.(is_null($row['updated_at']) ? 'null' : $row['updated_at']).'</td>';
                echo '<td>'.(is_null($row['deleted_at']) ? 'null' : $row['deleted_at']).'</td>';
                echo '<td>'. Html::anchor('test/rel/delete_profile/' . $row['id'], 'delete') .'</td>';
                echo '</tr>';
            }
        }
        echo '</table>';
        echo '</div>';


        echo '<p style="clear: left;"/>';
        echo '<hr />';


        echo '<p>footer</p>';


        echo '</body></html>';
    }

    public function action_delete_parent($id)
    {
        if ($obj = \Model_Test_Rel_Parent::find($id))
        {
            $db = \Database_Connection::instance();
            $db->start_transaction();

            $obj->delete();

            Session::set_flash('msg', e('id ' . $id . ' parent delete success.'));

            $this->action_index();

            $db->rollback_transaction();
            // $db->commit_transaction();

            exit();
        }
        else
        {
            Session::set_flash('msg', e($id . ' parent does not exists!'));
        }

        $this->_redirect_to_index();
    }

    public function action_delete_child($id)
    {

        if ($obj = \Model_Test_Rel_Child::find($id))
        {
            $db = \Database_Connection::instance();
            $db->start_transaction();

            $obj->delete();

            Session::set_flash('msg', e('id ' . $id . ' child delete success.'));

            $this->action_index();

            $db->rollback_transaction();
            // $db->commit_transaction();

            exit();
        }
        else
        {
            Session::set_flash('msg', e('id ' . $id . ' child does not exists!'));
        }

        $this->_redirect_to_index();
    }

    public function action_delete_profile($id)
    {

        if ($obj = \Model_Test_Rel_Child_Profile::find($id))
        {
            $db = \Database_Connection::instance();
            $db->start_transaction();

            $obj->delete();

            Session::set_flash('msg', e('id ' . $id . ' child-profile delete success.'));

            $this->action_index();

            $db->rollback_transaction();
            // $db->commit_transaction();

            exit();
        }
        else
        {
            Session::set_flash('msg', e('id ' . $id . ' child-profile does not exists!'));
        }

        $this->_redirect_to_index();
    }

    public function action_generate_from_sql()
    {
        // truncate
        $this->_truncate_tables();

        // parent
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Parent::table() . '(name,created_at,updated_at) VALUES ("parent1", ' . \Date::forge()->get_timestamp() . ', null)')->execute();
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Parent::table() . '(name,created_at,updated_at) VALUES ("parent2", ' . \Date::forge()->get_timestamp() . ', null)')->execute();
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Parent::table() . '(name,created_at,updated_at) VALUES ("parent3", ' . \Date::forge()->get_timestamp() . ', null)')->execute();

        // child
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Child::table() . '(name, parent_id,created_at,updated_at) VALUES ("parent1_child1", 1, ' . \Date::forge()->get_timestamp() . ', null)')->execute();
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Child::table() . '(name, parent_id,created_at,updated_at) VALUES ("parent1_child2", 1, ' . \Date::forge()->get_timestamp() . ', null)')->execute();
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Child::table() . '(name, parent_id,created_at,updated_at) VALUES ("parent2_child1", 2, ' . \Date::forge()->get_timestamp() . ', null)')->execute();
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Child::table() . '(name, parent_id,created_at,updated_at) VALUES ("parent1_child3", 1, ' . \Date::forge()->get_timestamp() . ', null)')->execute();

        // child_profile
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Child_Profile::table() . '(child_id,`key`,value,created_at,updated_at) VALUES (1, "child1_prof1", "content_child1_prof1", ' . \Date::forge()->get_timestamp() . ', null)')->execute();
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Child_Profile::table() . '(child_id,`key`,value,created_at,updated_at) VALUES (1, "child1_prof2", "content_child1_prof1", ' . \Date::forge()->get_timestamp() . ', null)')->execute();
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Child_Profile::table() . '(child_id,`key`,value,created_at,updated_at) VALUES (2, "child2_prof1", "content_child2_prof1", ' . \Date::forge()->get_timestamp() . ', null)')->execute();
        $query = DB::query('INSERT INTO ' . Model_Test_Rel_Child_Profile::table() . '(child_id,`key`,value,created_at,updated_at) VALUES (3, "child3_prof1", "content_child2_prof1", ' . \Date::forge()->get_timestamp() . ', null)')->execute();

        Session::set_flash('msg', e('truncate and created(SQL)'));

        $this->_redirect_to_index();
    }

    public function action_truncate()
    {
        $this->_truncate_tables();
        $this->_redirect_to_index();
    }

    public function _redirect_to_index()
    {
        \Fuel\Core\Response::redirect('test/rel?' . time() . microtime(true));
    }

    public function _truncate_tables()
    {
        $tables = array(
            Model_Test_Rel_Child::table(),
            Model_Test_Rel_Parent::table(),
            Model_Test_Rel_Child_Profile::table(),
        );
        foreach ($tables as $table) {
            \DBUtil::truncate_table($table);
        }
    }
}
```

これで、http://(your domain)/test/rel/ にアクセスすれば、それっぽい一覧が表示されるはず。

### 検証用データを作成してみる

http://(your domain)/test/rel/generate_from_sql にアクセスして、検証用のデータが Insert された状態にしておく。

再度、http://(your domain)/test/rel/ にアクセスしてみると一覧にそれっぽいデータが入っているはず。

### Orm を 1.7 のものにする

github の https://github.com/fuel/orm/ を見ると色々更新されてるっぽいので、とりあえず Orm は 1.7/develop を使うようにする。
(1.6ではダメで、1.7にした理由は忘れた。。なんかあったはずなので、思い出したら…気が向いたら書く。)

zip をダウンロードして更新するなり、Gitでゴニョゴニョするなりして Orm だけ 1.7 にしておく。

### 削除してみる

まず、 Model_Test_Rel_Parent の列の任意の値の"delete"をクリックしてみる。

期待する動作としては、関連する child, child_profile の deleted_at が更新されること。

実際に delete(); してみると、どうも、期待と違う…

### parent の deleted_at だけが更新された、関連するテーブルの deleted_at が更新されない！？

お好きなツール(phpmyadmin や Contoroller の出力(transaction 使ってるので、$db->rollback_transaction(); してはいるけど、画面には $db->commit_transaction(); した状態が表示されているはず) その他ツール)で確認してみる。

parent の deleted_at だけが更新された、関連するテーブルの deleted_at が更新されていない。
どうも、デフォルトのままではダメらしい。

※原理的には問題ないかも知れんけど、消えるべきデータは一括で消えておいて欲しい。(deleted_at が NULL なのに、リレーションが無いデータとか、メンテナンス性の面でも居てほしくない。)

## 連関しているレコードも deleted_at が update されるように修正する

どうやら、delete 対象のオブジェクトに対するリレーションデータが正しく登録されてない状態で delete すると、リレーション対象は更新されないらしい。

### Controller 側で頑張る方法

たとえば、 parent を delete することで、同時に children に配列として入っている child も更新したければ、以下のように delete() を呼ぶ前に初期化(\Orm\Model::__get($property); が呼ばれる必要がある)しておく必要がある。

※存在しないプロパティーへのアクセスがあったので、マジックメソッドである __get() が呼び出される。__get() の細かい挙動に関しては PKGPATH/orm/classes/model.php をみてみると良いと思う。

Controller 側で実現するなら以下の様な感じか。

```APPPATH/classes/controller/test/rel.php
<?php
    public function action_delete_parent($id)
    {
        if ($obj = \Model_Test_Rel_Parentsoft::find($id))
        {
            $db = \Database_Connection::instance();
            $db->start_transaction();

            $obj->children; // とりあえず宣言することで __get  が呼ばれる
            $obj->delete();


            // Myutil_Common::make_relations($obj);
            // $ary = $obj->delete_wrapper();

            Session::set_flash('msg', e('id ' . $id . ' parent delete success.'));

            $this->action_index();

            $db->rollback_transaction();
            // $db->commit_transaction();

            exit();
        }
        else
        {
            Session::set_flash('msg', e($id . ' parent does not exists!'));
        }

        $this->_redirect_to_index();
    }

```

この方法だと、リレーションが増えてきた時に、いちいち全て Controller に書いてやる必要があり、とても嫌な感じ。
実際、例をとって考えてみると、child_profile は消えてない。
child_profile も併せて消すなら、ちゃんとアクセスしておく必要が有る。
例えば、以下みたいに。

```php
            foreach ($obj->children as $child)
            {
                        $child->profiles;
            }
```

これは、 Controller 側で頑張って制御すれば解決するといえばするけど、リレーションが多くなればなるほど泣けてくる事態が容易に想像できる。

なので、なんとかする。

### \Orm\Model_Soft で対処する

リレーションが妥当に設定されていれば、関連テーブルも cascade_delete に従い動作するはず。
とりあえず、暫定的に、リレーションを自前で書いてみる。

APPPATH/packages/corm/classes/model/soft.php を修正する。
※本当は packages 内を直接触るの良くない…

### APPPATH/packages/corm/classes/model/soft.php @ 228 辺りを以下に修正する

```APPPATH/packages/corm/classes/model/soft.php
                // リレーション用のデータを作る必要がある
                if (!isset($this->_original_relations[$rel_name]))
                {
                    // _original_relations に値が無いので、リレーションデータを作成しておく(事前に作成しないとリレーション先がUpdateされない)
                    $this->unfreeze();
                    $this->get($rel_name);
                    $this->freeze();

                }
```

### 再度、削除してみる

Model に設定してある cascade_delete に従い、関連テーブルのレコードも削除されているはず。
※お好きなツールを使って確認してみて下さい。

ここで、子要素になり得る要素(例で言うと、child, child_profileの2つ)から、belongs_to に設定してある親要素に対する(child -> belongs_to -> parentな状態)プロパティー casca_delete を true に設定するとエラーになるはず。
ここも、頑張れば挙動を変えることができるけど、子から親を削除するのは一般的に行わないケースが多いはずなので、今回は考慮外とする。

(子から親を削除する場合、どこまで辿るのが妥当なのかとかも考える必要がある。例えば、親A -> 子A1, 親A -> 子A2, という状態で、子A2 の belongs_to である 親A を cascade_delete に従い削除した場合、 親A だけが消えるべきか、 子A2 -> 親A -> 子A1 の連関全てに適用するべきかや、 親A の cascade_delete に従うべきかなど。この辺りは一般的にどういった考え方をしたらいいのか、よくわからんです。)

## belongs_to な 要素の リレーション key が 0 になる！！に対応する。

### 次は子要素である child を削除してみる。

こっちも、 Model に設定してある cascade_delete に従い、関連テーブルのレコードも削除されているはず。
※お好きなツールを使って確認してみて下さい。

ただし、parent_id が 0 となってしまったはず。
おかしい。これは良くない。

例えば、以下の様なデータがあったとする。

#### test_rel_parents

| id| name | deleted_at |
|:-----------|------------:|:------------:|
| 1 | parent1 | null |
| 2 | parent2 | null |

#### test_rel_children

| id| parent_id | name | deleted_at |
|:-----------|------------:|------------:|:------------:|
| 1 | 1 | parent1_child1 | null |
| 2 | 2 | parent2_child1 | null |
| 4 | 1 | parent1_child2 | null |

この時、test_rel_children の ID の値が 2 となるレコードを削除するとする。

```php
$obj = \Model_Test_Rel_Childsoft::find($2)
$obj->delete();
```

この結果、test_rel_children テーブルは以下のようになったはず。

#### test_rel_children

| id| parent_id | name | deleted_at |
|:-----------|------------:|------------:|:------------:|
| 1 | 1 | parent1_child1 | null |
| 2 | 0 | parent2_child1 | 111111 |
| 4 | 1 | parent1_child2 | null |

#### parent_id が 0 ！！

削除済みのデータなのだから、リレーションを切ってしまっても良いのではないかという考えには至れない。
困った。
何とかする必要がある。

### PKGPATH/orm/classes/belongsto.php の save メソッドを改修

で、実際に削除した時の動作を追ってみると、どうやら、 BelongsTo の save メソッドでリレーション用の key の値が上書きされていることがわかる。

新規作成された時の save を考慮してなのか、以下の様なコードがあった。

```php
$model_from->{current($this->key_from)} = $model_to ? $model_to->{$pk} : null;
```

この結果、 null が代入され、 0 として保存されていた事が判明。

なので、ここを勝手に書き換える。

おおよそ 135 行目あたり。save メソッド内。

```PKGPATH/orm/classes/belongsto.php
<?php
// (略)
		if ($current_model_id != $original_model_id)
		{
			// change the foreign keys in the model_from to point to the new relation
			reset($this->key_from);
			$model_from->unfreeze();
			foreach ($this->key_to as $pk)
			{
				// $model_from->{current($this->key_from)} = $model_to ? $model_to->{$pk} : null;
                // もし、元も Model が Model_Soft なら、save の時に Foreign Key を変更する必要はないので、上の行をコメントアウトして、以下の if 文を追加した。
                if (get_parent_class($model_from) == 'Orm\\Model_Soft')
                {
                    $model_from->{current($this->key_from)} =$model_from->{current($this->key_from)};
                }
                else
                {
                    $model_from->{current($this->key_from)} = $model_to ? $model_to->{$pk} : null;
                }
				next($this->key_from);
			}
			$model_from->freeze();
		}
```

親クラスを確認し、\Orm\Model_Soft 利用時には Foreign Key が変更されないようにした。

### 改めて、子要素を削除してみる。

修正内容を確認するため、改めて、子要素を削除してみる。

```php
$obj = \Model_Test_Rel_Childsoft::find($2)
$obj->delete();
```

この結果、test_rel_children テーブルは以下のようになったはず。

#### test_rel_children

| id| parent_id | name | deleted_at |
|:-----------|------------:|------------:|:------------:|
| 1 | 1 | parent1_child1 | null |
| 2 | 2 | parent2_child1 | 111111 |
| 4 | 1 | parent1_child2 | null |

#### ちゃんと parent_id が 2 のまま！！

これで \Orm\Model_Soft を利用した状態でもちゃんと deleted_at が反映されるようになったはず。

間違いや考え方が怪しい点、もっとスマートな方法あるよ！って方は是非コメントで教えて下さい。。。