Translate

2011年5月3日火曜日

Apexトリガで共有を動的に操作するサンプル

Apexトリガが利用されるよくある使い方の一つに

ロールや共有ルールでは記述できない

動的な個別の共有設定を行いたい場合

というものがある。



以下のコードは、

Force.com開発者コース研修のDEV541内演習4-1のもの。



研修中ほとんど全員が理解せずにコピペですませてしまう演習だが、

よくよむとApexトリガを使ったApex共有の代表的な実装方法を

教えてくれているので

ここにコメント付きで紹介しておく。



Apexで共有をいじる場合は、

あらかじめ宣言的開発でApex共有の理由で

これから作成する共有ロジックの発生理由を

設定しておく。





// 演習4-1 PositionSharingTrigger
// 募集職種(Position__c)にレコードが新規追加、更新直後に起動する
// Apex共有としてHiring_Manager(採用マネージャ)、Approved_Position(組織全体へ
// の共有:承認された募集職種)が用意されている
trigger PositionSharingTrigger on Position__c (after insert, after update) {

// 募集職種IDと新採用マネージャIDのマップ
Map<ID,ID> posIdToNewMgrIdMap = new Map<ID,ID>(); // 募集職種IDと新部署名のマップ
Map<ID,String> posIdToDeptMap = new Map<ID,String>(); // 承認された募集職種レコードのリスト
List<Position__c> approvedPositions = new List<Position__c>(); // 未承認募集職種レコードのリスト
List<Position__c> nonApprovedPositions = new List<Position__c>();
// クローズもしくは未承認の募集職種IDのセット
Set<ID> removeOrgWideSharingSet = new Set<ID>();
// 未承認な募集職種レコードのリスト
List<Position__c> nonClosedPositions = new List<Position__c>();
// クローズされた募集職種IDのリスト
List<Position__c> closedPositions = new List<Position__c>();

// トリガ発生元となった募集職種レコードループ
for (Position__c position:Trigger.new){
// 更新によってトリガ起動した場合
if (Trigger.isUpdate){
// 採用マネージャが更新された場合
if(position.Hiring_Manager__c !=
Trigger.oldMap.get(position.Id).Hiring_Manager__c){
// 新採用マネージャIDマップへ格納
posIdToNewMgrIdMap.put(
position.Id,
position.Hiring_Manager__c);
}

// 部署が更新された場合
if(position.Department__c !=
Trigger.oldMap.get(position.Id).Department__c){
// 新部署名マップへ格納
posIdToDeptMap.put(
position.Id,
position.Department__c);
}
}

// 募集職種のステータスがクローズになっていない場合
if (position.Status__c != 'Closed'){
// 未クローズ募集職種リストへ格納
nonClosedPositions.add(position);
} else {
// クローズ済み募集職種リストへ格納
closedPositions.add(position);
}

// 承認済み募集職種の場合
if ((position.Status__c == 'Open') &&
(position.Sub_Status__c=='Approved')){
// 承認済み募集職種リストへ格納
approvedPositions.add(position);
} else {
// 未承認/クローズ募集職種IDセットへ格納
removeOrgWideSharingSet.add(position.id);
// 未承認募集職種リストへ格納
nonApprovedPositions.add(position);
}
}

// 更新によってトリガ起動した場合
if (Trigger.isUpdate){
// 新採用マネージャ情報から不要な共有設定を削除する
PositionSharing.deleteHiringMgrSharing(
posIdToNewMgrIdMap);

// SalarySharingクラスは給与(Salary__c)の共有設定を
// 操作するあらかじめ研修組織に配置されたクラス
// 新採用マネージャ情報から不要な共有設定を削除する
// SalarySharing.deleteHiringMgrSharing(
// posIdToNewMgrIdMap);
// JobAppSharingクラスは申込(Job_Application__c)の
// 共有設定を操作するあらかじめ研修組織に配置された
// クラス
// 新採用マネージャ情報から不要な共有設定を削除する
// JobAppSharing.deleteHiringMgrSharing(
// posIdToNewMgrIdMap);
// 共有理由がDepartment_VP関連である給与(Salary__c)の
// 共有設定を削除する
// SalarySharing.deleteVPSharing(posIdToDeptMap);
}

// 未承認/クローズである
// 募集職種レコードの
// 組織全体、採用マネージャへの参照のみ共有設定を
// 新規追加する
PositionSharing.addSharing(nonApprovedPositions,'Read');

// 承認済みである
// 募集職種レコードの
// 組織全体、採用マネージャへの編集可能共有設定を
// 新規追加する
PositionSharing.addSharing(approvedPositions,'Edit');

// トリガ発生元となった募集職種レコードに紐づく
// 申込レコードの
// 採用マネージャへの編集可能共有設定を
// 新規追加する
// JobAppSharing.createSharing(
// Trigger.new,'Hiring_Manager__c','Edit');
// 未承認/クローズ募集職種に紐づく
// 給与レコードの
// 組織全体、採用マネージャへの参照のみ共有設定を
// 新規追加する
// SalarySharing.addSharing(nonClosedPositions,'Read');
// 承認済み募集職種に紐づく
// 給与レコードの
// 組織全体、採用マネージャへの編集可能共有設定を
// 新規追加する
// SalarySharing.addSharing(approvedPositions,'Edit');
// クローズされた募集職種に紐づく
// 給与レコードの
// 組織全体、採用マネージャへの共有設定すべてを
// 参照のみに変更する
// SalarySharing.closeSharing(closedPositions);

// クローズされた
// 募集職種レコードの
// 組織全体、採用マネージャへの参照のみ共有設定を
// 新規追加する
PositionSharing.addSharing(ClosedPositions,'Read');

// クローズされた
// 募集職種レコードの
// 組織全体、採用マネージャへの共有設定すべてを
// 参照のみに変更する
PositionSharing.closeHiringMgrSharing(closedPositions);

// JobAppSharing.createSharing(
// closedPositions,'Hiring_Manager__c','Read');
// JobAppsharing.closeHiringMgrSharing(closedPositions);

// 承認済みの募集職種レコードに対して
// 組織全体、採用マネージャへの編集可能共有設定の新規追加を行う
// ただし、共有理由はHiring_Magager__cがセットされる
PositionSharing.addSharing(
approvedPositions,'Approved_Position__c','Edit');

// 未承認/クローズ募集職種レコードに張られている
// 組織全体に対する共有設定を削除する
PositionSharing.removeOrgWideSharing(removeOrgWideSharingSet);
}



Apexトリガでは

・操作対象のレコードを抽出してコレクションクラスにまとめる

・共有処理は、古い共有をはずす、新しい共有を張る、の順

・実際の共有操作は別Apexクラスに書く

 - ここではオブジェクト単位で共有操作を行うクラスを分けている

といったことルールづけしておいたほうがよいということか。






// PositionSharing
// 募集職種オブジェクトの共有設定を操作するクラス
// 呼び出し元の共有ルールに従って実行される
public class PositionSharing {

// 暗黙公開グループ「組織全体」を表す Force.com ID
public static final ID ENTIRE_ORG_GROUP_ID_CONST;

// staticブロックを使って組織全体のIDを取得
static {
ENTIRE_ORG_GROUP_ID_CONST = getEntireOrgGroupId();
}

// 組織のIDを取得するメソッド
private static ID getEntireOrgGroupId(){
// Groupは、ロール、公開グループなどのメタデータが格納された
// 標準オブジェクト
// 組織全体をあらわすGroupレコードのタイプにはOrganizationが
// 格納されているので、それを利用して組織全体をあらわすIDを
// 入手している
return [select Id from Group where Type='Organization'].Id;
}

// 不要になった共有設定を削除する
// 引数には募集職種IDと削除対象でない共有先IDのマップを指定する
public static void deleteHiringMgrSharing(
Map<ID,ID> posIdToNewMgrIdMap){
// 1件以上存在する場合
if (posIdToNewMgrIdMap.size() > 0){

// 共有元(募集職種)レコードの採用マネージャが更新され
// 共有理由が「採用マネージャ」である共有設定レコード
// のループ
// リストで受けることでフェッチ回数を効率化させている
for(List<Position__Share> batchOfShares:
[select
// 共有させている個人/グループのID
UserOrGroupId,
// 共有理由
RowCause,
// 共有元レコード
ParentId,
// 共有設定自身のID
Id,
// アクセスレベル(参照のみ、フル..)
AccessLevel
// Position__Shareは、Position__c(募集職種)の
// 共有設定が格納されているオブジェクト
From Position__Share
// 共有元レコードIDが新採用マネージャリストに
// 含まれる
where ParentId IN
:posIdToNewMgrIdMap.keySet()
// 共有の理由が「採用マネージャ」
and  RowCause='Hiring_Manager__c']
){

// 削除対象となる共有設定を格納するリスト
List<Position__Share> deleteShares =
new List<Position__Share>();

// フェッチ結果から共有設定を1件づつ取得する
// ループ
for(Position__Share posShare:batchOfShares){

// 新採用マネージャIDが共有対象IDと
// 異なる場合
if (posIdToNewMgrIdMap.get(
posShare.ParentId) !=
posShare.UserOrGroupId){

// 不要な共有設定なので
// 削除対象リストへ共有設定を
//格納する
deleteShares.add(posShare);
}
}


try {
// DBから削除対象の共有設定を削除
// 成功すると自動的にコミットされる
delete deleteShares;

// 削除処理に失敗した場合 System.DmlExceptionが
// 発生する
} catch (System.DmlException e){
// デバッグログへメッセージ書き出し
System.debug(
'error bulk deleting position shares');

// 失敗したメッセージ群をデバッグログへ
for (Integer k = 0;
k < e.getNumDml(); k++) {
// デバッグログへ書き出し
System.debug(
e.getDmlMessage(k));
}
}
}
}
}

//
public static void closeHiringMgrSharing(
List<Position__c> closedPositions){

// クローズ済み募集職種レコードが1件以上存在する場合
if (closedPositions.size() > 0){
// クローズ済み募集職種IDセットを抽出
Set<ID> closedPositionSet =
new Map<ID,Position__c>(closedPositions).keySet();

// 共有元(募集職種)レコードがクローズ済みでかつ
// 共有理由が「採用マネージャ」である共有設定レコード
// のループ
// リストで受けることでフェッチ回数を効率化させている
for(List<Position__Share> batchOfShares:
[select
// 共有させている個人/グループのID
UserOrGroupId,
// 共有理由
RowCause,
// 共有元レコード
ParentId,
// 共有設定自身のID
Id,
// アクセスレベル(参照のみ、フル..)
AccessLevel
// Position__Shareは、Position__c(募集職種)の
// 共有設定が格納されているオブジェクト
From Position__Share
// 共有元レコードIDがクローズ済み募集職種セット
// に含まれる
where ParentId IN
:closedPositionSet
// 共有の理由が「採用マネージャ」
and RowCause='Hiring_Manager__c']){

// フェッチ結果すべてのアクセスレベルを
// 参照のみに設定
for(Position__Share posShare:batchOfShares){
posShare.AccessLevel = 'Read';
}

try {
// 共有設定すべてを一括更新
// 1件でも失敗した場合は全件
// ロールバックされる
// Database.update(batchOfShares, true)
// 相当の処理
update batchOfShares;

} catch (System.DmlException e){
// デバッグログへメッセージ書き出し
System.debug(
'error bulk updating position shares');
for (Integer k = 0; k < e.getNumDml(); k++) {
System.debug(e.getDmlMessage(k));
}
// 処理は継続する
}
}
}
}

// 組織全体、採用マネージャへの共有設定の新規追加を行う
// 引数:募集職種レコードの配列、
// 共有理由(Hiring_Manager__cなど)、
// アクセスレベル(参照のみ、フルなど)
public static void addSharing(
Position__c[] positions, String apexType, String accessLevel){
// デバッグログへメッセージ出力
System.debug('creating bulk Position__Share records');
// 新規追加する共有設定を格納するリスト
List<Position__Share> posShares = new List<Position__Share>();

// 引数として受領した募集職種リストから1件づつ取り出すループ
for (Position__c p:positions){
// 新規共有設定を生成
Position__Share ps = new Position__Share();

// 共有元IDとして募集職種レコードのIDを代入
ps.ParentId = p.Id;

// 引数で指定された共有理由がHiring_Magager__cの場合
if (apexType == 'Hiring_Manager__c'){
// 共有対象IDとして募集職種レコード上の
// 項目「採用マネージャ(Hiring_Manager)」の値
// を代入
ps.UserOrGroupId = p.Hiring_Manager__c;
// Position__Share(募集職種の共有設定オブジェ
// クト)の共有理由「Hiring_Manager__c」を共有
// 理由として代入
ps.RowCause =
Schema.Position__Share.RowCause.Hiring_Manager__c;
// 引数で指定された共有理由がApproved_Position__cの場合
} else if(apexType =='Approved_Position__c'){
// 共有対象IDとして組織全体IDを代入
ps.UserOrGroupId = ENTIRE_ORG_GROUP_ID_CONST;
// Position__Share(募集職種の共有設定オブジェ
// クト)の共有理由「Approved_Position__c」を
// 共有理由として代入
ps.RowCause =
Schema.Position__Share.RowCause.Approved_Position__c;
}

// 引数で渡されたアクセスレベルを格納
ps.AccessLevel = accessLevel;
// デバッグログに新規追加するレコードを出力
System.debug('this is ps: ' + ps);
// 新規追加する共有設定リストへ追加
posShares.add(ps);
}

// 新規追加対象の共有設定が1件以上存在する場合
if (posShares.size() > 0){
try {
// 格納
System.debug(
'performing bulk Position__Share insert');
// バルク操作メソッドを使って新規格納
// 新規追加できるレコードは格納してしまい、
// 処理後エラーになったレコードがあっても
// 処理継続する
// insert posShares(スタンドアロンステート
// メントという)の場合、1件でもエラーに
// なると成功しているレコードも含め全件
// ロールバックがかかる
// 第2引数がtrueの場合、insert posSharesと同じ
// 処理になる
Database.insert(posShares,false);

// 一部の更新レコードが失敗した場合
} catch (System.DmlException e) {
// デバッグログ出力
System.debug(
'error bulk inserting position shares');
// DmlExceptionのエラーメッセージを
// デバッグログに出力し、処理継続
for (Integer k = 0; k < e.getNumDml(); k++) {
// Process exception here
System.debug(e.getDmlMessage(k));
}
}
}
}

// Apexコードでもオーバロードメソッドは作成可能
// 組織全体、採用マネージャへの共有設定の新規追加を行う
// ただし、共有理由はHiring_Magager__cがセットされ、
// アクセスレベルは第2引数で渡された値で設定される
public static void addSharing(Position__c[] positions,String accessLevel){
// 採用マネージャが設定されている募集職種リストの新規作成
List<Position__c> hiringMgrPositions = new List<Position__c>();

// 引数で渡された募集職種リストから1件づつレコードを抽出する
// ループ
for(Position__c position:positions){
// 項目「採用マネージャ」が空ではない場合
// 採用マネージャが設定されている募集職種リストへ追加
if(position.hiring_manager__c != null)
hiringMgrPositions.add(position);
}

// 採用マネージャが設定されている募集職種が1件以上存在する場合
if (hiringMgrPositions.size() > 0)
// 組織全体、採用マネージャへの共有設定の新規追加を行う
// 共有理由:Hiring_Magager__c、
// アクセスレベル:第2引数で渡されたアクセスレベル
PositionSharing.addSharing(
hiringMgrPositions,
'Hiring_Manager__c',
accessLevel);
}

//
public static void removeOrgWideSharing(
Set<ID> removeOrgWideSharingSet){

// 削除対象募集職種IDリストが1件以上の場合
if (removeOrgWideSharingSet.size() > 0){
// go through the list of related sharing records in batch and delete any Approved_Position__c
// sharing records

//
for(List<Position__Share> batchOfShares:
[select
// 共有させている個人/グループのID
UserOrGroupId,
// 共有理由
RowCause,
// 共有元レコード
ParentId,
// 共有設定自身のID
Id,
// アクセスレベル(参照のみ、フル..)
AccessLevel
// Position__Shareは、Position__c(募集職種)の
// 共有設定が格納されているオブジェクト
From Position__Share
// 共有先のIDが引数で渡された削除対象IDリスト
// に含まれている
where ParentId IN
:removeOrgWideSharingSet
// 共有理由がApproved_Position__c
and RowCause='Approved_Position__c'
// 共有対象のIDが「組織全体」
and UserOrGroupId =
:ENTIRE_ORG_GROUP_ID_CONST]){
try {
// データベースから削除
// 削除できるものは実行し、
// できなかったレコードがあれば処理終了
// 後も継続する
Database.delete(batchOfShares,false);

} catch (System.DmlException e){
// 例外に格納されたエラーメッセージを
// すべてデバッグログへ出力
System.debug('error bulk deleting position shares');
for (Integer k = 0; k < e.getNumDml(); k++) {
// Process exception here
System.debug(e.getDmlMessage(k));
}
// 処理は継続
}
}
}

}
}





共有操作は<オブジェクト名>__Shareという暗黙オブジェクト

を操作する。



ポイントとしては、

共有操作はSystem権限で行うので

クラスにwith sharingをつけていないこと。

0 件のコメント:

ClaudeをOpenAI O1のように思考させるDifyサンプルを試す

 Difyの「探索」タブにはさまざまなサンプルが載っており、その1つに「Thinking Claude (OpenAI O1 Alternative)」というものがある。   このサンプルがどういうものか、未だに自分でも解けない以下の問題をためしに聞いてみることにした。 『人類の...