2.16. コールバック関数

挿入・更新・削除といったレコード操作の前後に、コールバック関数を設定することが可能です。

Note

レコード操作でフォームのみを使用する場合は、コールバック関数を使用するより、 FORMやSQLFORMの onvalidationonsuccess といったパラメータを設定した方が良いです。 これにより適切なエラー処理を行うことが可能になります。

設定は、 Table クラスの属性にリストとして設定します。次のような属性があります。

属性 説明
_before_insert レコード 挿入前 に設定された関数を実行します
_before_update レコード 更新前 に設定された関数を実行します
_before_delete レコード 削除前 に設定された関数を実行します
_after_insert レコード 挿入後 に設定された関数を実行します
_after_update レコード 更新後 に設定された関数を実行します
_after_delete レコード 削除後 に設定された関数を実行します

説明では、次の2つのテーブルを使用します。

db.define_table('person',
    Field('name'),
    Field('number_of_dog', 'integer', default=0, writable=False),
    format='%(name)s')

db.define_table('dog',
    Field('owner_id', db.person),
    Field('name'))

属性の設定に、次の関数を使用します。

def check_name(name):
    for s in name.split():
        if len(s) >= 3:
            return False
    return True

名前のチェックを行います。空白文字を除いて、3文字以上連続する文字列があるかどうかをチェックします。

def check_dog(id):
    return False if db(db.dog.owner_id==id).isempty() else True

dogテーブルに指定するowner_idの、レコードが存在するかどうかをチェックします。

def count_dog(owner_id):
    db(db.person.id==owner_id).update(number_of_dog=db(db.dog.owner_id==owner_id).count())
    return None

指定するowner_idのdogレコード数を集計し、personレコードのnumber_of_dogフィールドに格納します。

def store_owner_id(owner_id):
    if not session.check_owner_id:
        session.check_owner_id =[owner_id]
    else:
        session.check_owner_id.append(owner_id)
    return None

(dogレコード更新・削除前に)レコードのowner_idの値を、セッションに格納します。

def check_count_dog():
    if session.check_owner_id:
        for owner_id in session.check_owner_id[:]:
            count_dog(owner_id)
            session.check_owner_id.remove(owner_id)
    return None

(dogレコード更新・削除後に)セッションに格納されたowner_idを使用し、dogレコード数を集計し、personレコードのnumber_of_dogフィールドを更新します。

属性を次のように設定してみます。

db.person._before_insert.append(lambda dict: check_name(dict['name'])
                                if 'name' in dict else True)
db.person._before_update.append(lambda set,dict: check_name(dict['name'])
                                if 'name' in dict else False)
db.person._before_delete.append(lambda set: check_dog(set.select().first().id))

db.dog._after_insert.append(lambda row,id: count_dog(row.owner_id))
db.dog._before_update.append(lambda set,dict: store_owner_id(set.select().first().owner_id)
                             if 'owner_id' in dict else False)
db.dog._after_update.append(lambda set,dict: store_owner_id(dict['owner_id'])
                            if 'owner_id' in dict else None)
db.dog._after_update.append(lambda set,dict: check_count_dog())
db.dog._before_delete.append(lambda set: store_owner_id(set.select().first().owner_id))
db.dog._after_delete.append(lambda set: check_count_dog())

この設定により、レコードの挿入・更新・削除に伴い、次の表のように動作します。

テーブル レコード操作 タイミング 動作説明
person insert 名前の長さチェックを行います。 チェックを通過しない場合は挿入しません。
person update 名前の長さチェックを行います。 チェックを通過しない場合は更新しません。
person delete 関連するdogレコードがないかチェックします。 dogレコードがある場合は削除しません。
dog insert 同じ飼い主のdogレコード数を集計し、personテーブルのnumber_of_dogフィールドを更新します。
dog update 更新前レコードのowner_idをセッション変数に保存します。
dog update 更新したowner_idをセッション変数に保存します。
dog update セッション変数に保存されたowner_idのdogレコード数を集計し、personテーブルのnumber_of_dogフィールドを更新します。
dog delete 削除レコードのowner_idをセッション変数に保存します。
dog delete セッション変数に保存されたowner_idのdogレコード数を集計し、personテーブルのnumber_of_dogフィールドを更新します。
  • dogテーブルのレコード更新では、前1回後2回の3回動作します。更新前後の飼い主でそれぞれ集計するためです。
  • dogテーブルのレコード削除の場合は、前と後で2回動作します。これは削除後だけではレコードが消えてしまうので、飼い主情報がわからなくなるためです。
  • dogテーブルのレコード操作により、コールバック関数でpersonテーブルのレコードも更新します。このため、personテーブルに設定したコールバック関数も同時に動作します。

またコールバック属性に登録した関数は、戻り値を返すことができますが、値は NoneTrueFalse のみ可です。レコード操作の前に(before)動作する属性では、True の戻り値を返ってきた場合、実際のレコード操作は行われません。

設定したコールバック関数を起動させないでレコードを更新する場合は、 update_naive() メソッドを使用してください。

>>> db(db.dog.id==12).update_naive(owner_id=5)
1

参考: before and after callbacksコールバックの前後