2018年9月30日TweetPocket引き続き、Createに関するコールバックを一つずつ定義されている順番に読んでいく。今回はsaveBeforeAssociationsCallback。O/Rマッピングの実装が登場するので、多少読んでいくのが大変だが、一つずつ読んでいく。{...略...}}素直に一行目から読んで行こう。このメソッドは、引数で渡されたScopeのFields()で返される値に対して、一つずつ処理を行う構造になっている。*scope.fields}Fields()メソッドは、Fieldのポインタのスライスを返す。計算量の節約のためか、scope.fieldsに値をキャッシュしているようだ。reflect.Value}Fieldは上記のように定義されている。StructFieldが埋め込まれているようだ。*Relationship}StructFieldの定義はこのようになっている。これはGolangにおけるStructのフィールドとDBのレコードのカラム名などのマッピングを表現するための構造体のようだ。StructFieldの各々のフィールドの詳細についてはまだわからないが、scope.Fields()はCreateでこれから保存するオブジェクトのフィールドの情報のスライスが返されると想像される。詳しく中を見て行こう。&fieldsscope.fieldsが空だった場合、すなわちキャッシュがなかった場合の処理を詳しく見て行ってみよう。scope.IndirectValue()は前のポストで見たように、Createの引数で渡されたオブジェクト(DBに保存しようとしているオブジェクト)のDereferenceを行う。indirectScopeValueにはこれからDBに保存するオブジェクトの実体が入る。isStructにはこのオブジェクトが構造体かどうかを表す値が入る。スライスなど構造体以外のものが入っていた場合はfalseになる。このisStructはその下のループのイテレーションで毎回参照され処理の分岐に使われている。イテレーション処理中にこの値が変更されることはないので、ループの外側で分岐を書いた方がわかりやすいような気がするが、何か理由があるのだろうか。そもそも変更されないのでconstにした方が良いような?ちょっと大変だがscope.GetModelStruct().StructFieldsについて掘り下げていく。scope.GetModelStruct()は長い関数なので、内容を多少省略しながら記していく。*ModelStructGetModelStruct()はModelStructのポインタを返す。string}ModelStructは上記のように定義されている。StructFieldがモデルの各フィールド単位でのDBのテーブルのカラムとのマッピングを表すのに対し、ModelStructはモデル単位でのマッピングを表す構造体となる。&modelStruct}GetModelStruct()の中に戻ろう。リフレクションの仕組みを使って、scope.Value(DBに保存しようとしているオブジェクト)の型を取得しようとしている。scope.Valueがスライスあるいはポインタの場合はElem()で要素/実体の型を参照できるようにしている。scope.Valueが構造体でもポインタでもスライスでもない場合、例えばintなどの場合は、そこで処理を終了して中身が空のModelStructを返している。value.(*ModelStruct)}GetModelStruct()にもキャッシュの仕組みがある。modelStructsMapはsync.Mapで定義されており、スレッドセーフな実装になっている。reflectTypeキャッシュに値がない場合はモデルの解析が続行される。ModelTypeにモデルの型情報がセットされる。{...略...}}メソッドが長くて読むのが大変だが、リフレクションを使って、モデルのフィールドを一つずつチェックしていく。ast.IsExportedで、フィールドがExportされている(=Privateではない)かどうかチェックし、Exportされているフィールドのみをチェックしていく。parseTagSetting(fieldStruct.Tag),}Exportされているフィールドの場合は、対応するStructFieldオブジェクトが作られる。setting}parseTagSettingsでは、構造体に定義されたタグの解析が行われる。gormのタグの中身をそれぞれ見ていく。タグの中に;が含まれる場合は、;で区切った結果を一つずつ見ていく。タグの中身はkey:valueの形で表現されていることが想定されていて、一つ目のコロンよりも前をkeyに、それ以降をvalueとしてsettingというmapにセットしていき、全部パースし終わったところでそのmapを返すようにしている。keyは強制的に大文字化され、スペースが排除された状態で扱われる。これは表記ブレの対応だろう。いうわけで、StructFieldのTagSettingsには、そのフィールドにつけられたタグがmapの形にパースされてセットされる。ちなみに、この実装ではkeyあるいはvalue内に;が含まれていた場合にパースに失敗してしまいそうだ。ToDBName(fieldStruct.Name)}その後の処理のブロックは非常に長いので、まず大まかな構造から見て行こう。上でパースしたタグの中に、keyが”-“のタグがあった場合は、IsIgnoredなフィールドとして扱われ、それ以上のパースは行われない。IsIgnoredがなんなのか、まだはっきりと処理やコメントには現れていないが、おそらくO/Rマッピングで無視される、ということなんだろう。ここもコメントや説明がないのでまだ予想だが、field.DBNameはデータベースにおけるカラム名を表すフィールドと考えられる。タグでCOLUMNが指定されている場合は、その値を使う。指定されていない場合はToDBNameメソッドを使って、fieldStruct.Name(=reflectType.Field(i).Name)つまりGoにおけるStructのフィールド名から生成した値を利用する。ToDBNameの処理の中身についてはそこまで重要ではないので、今回は省略する。true}...略...少し戻って、IsIgnoredではない場合の処理を見て行こう。最初の3つの処理は、プライマリーキー、デフォルト値(があるかどうか)、自動インクリメントなどのDBの設定をタグの内容からセットしているだけである。ModelStructを作成していく処理が続いていく。長くなるので続きは次のポストに書いていく。コメントをキャンセルメールアドレスが公開されることはありません。*ウェブサイトご意見、ご感想をお願いします。