2017年4月30日日曜日

Salesforce : プロセスビルダーを使ってみる

プロセスビルダーを使って、取引先の項目を更新してみようと思う。

プロセスビルダーの作成は設定画面のビルド > 作成 > ワークフローと承認申請 > プロセスビルダーで行える。


プロセスビルダーを開くと、右上に新規ボタンがあるのでそれをクリック。


プロセス名前、API名、プロセスの説明、プロセスを開始するタイミングを入力するダイアログが表示されるので、それぞれ入力してSaveする。


オブジェクトをクリックして、プロセスを開始するタイミングを選択する。今回はオブジェクトは取引先で、タイミングはレコードを作成したとき。選択が終わったら保存する。


次に「条件を追加」をクリックして、プロセスが実行される条件を指定する。今回は常に実行してほしいので、「アクションを実行する条件がない」を選択する。これを選択すると常に実行されるわけだけど、分かりにくい表現だと思う。


「アクションを追加」をクリックして、プロセスで何を実行するかを指定する。色々できるけれど、ともかく動きを見たいので特に意味はないけど従業員数に1000を設定するようにした。ここで「スケジュールを設定」の方をクリックすると、スケジュールに従って定期的に動作するようなアクションを指定することもできる。


最後に画面右上の「有効化」をクリックしてプロセスの作成完了。取引先を新規追加すると自動的に1000がセットされているのが分かる。

2017年4月28日金曜日

Salesforce : プロセスビルダーでやれること

プロセスビルダーは処理を自動化してくれる強力なツールなんだけれど、あまり使ったことがない。今日はプロセスビルダーでそもそもどんなことが出来るのかを調べたのでまとめておく。

■複数のif/thenステートメントをサポート
■レコードが変更されたときだけでなく、別プロセスからの呼び出しも出来る
■時間ベースのアクション
■Apexの呼び出し
■レコード作成
■プロセスの呼び出し
■フローの起動
■Chatterへの投稿
■メール送信
■承認申請
■項目の更新

調べてみて思ったのだけれど、やれることが多い。これまで私がApexを書いて実現していたような処理もプロセスビルダーでかなりのところをまかなえそうな気がする。どんな動きになるのかは今後ひとつひとつ試していこうと思う。

2017年4月27日木曜日

Salesforce : プロセスビルダーとワークフロー

Salesforceは機能が豊富なのでApexを書かなくても、自動化の処理を実現することができる。こうした自動化のツールはプロセスビルダー、ワークフロー、VisualWorkflow、承認の4つがある。

プロセスビルダーとワークフローの使い分けについては実はよく分かっておらず、指定があれば指定されたほうを使う、なければあまり深く考えずにワークフローを使うというようなことをやってしまっている。さすがに不味いかなと思ったので調べてみた。

結論としては、基本的にプロセスビルダーの使用を検討するほうがよさそうだなと思った。ワークフローで実現できるほとんどの機能をプロセスビルダーで実現可能だからだ。アウトバウンドメッセージの送付はプロセスビルダーでは出来ないけれど、他のことは全部出来るようだ。

2017年4月26日水曜日

Salesforce : Apex : staticフィールドのスコープ

昨日トリガの2重起動を防ぐためにstaticフィールドを実装するような方法を取った。これまであまり気にしなかったのだけれど、staticフィールド使っていて大丈夫なのかということが気になり始めている。

調べてみると、どうやらstaticフィールドはリクエストスコープの中だけで共有されるようで、組織全体で共有されるものではないようだ。なので、昨日のトリガはちゃんと想定通りに動作し続けてくれると思う。

それにしても、うっかりだったなと思うのはstaticフィールドが共有されるスコープの範囲をまったく知らずに使っていたことだ。ネットで調べたのか、人から聞いたのか忘れてしまったが、実現方法が見つかったあとろくな検証もせずに使っていたと思うとちょっと怖い。

2017年4月25日火曜日

Salesforce : Apex : トリガの2重起動を回避する

トリガとワークフローの項目自動更新なんかを組み合わせていると、トリガが2回動くことがある。例えばデータのupdateのときにトリガで何か処理を行って、その後ワークフローでこのレコードの値を更新すると、このタイミングでまたトリガが呼び出されてしまう。

trigger AccountTrigger on Account (before update) {

    System.debug('トリガの処理');
    
}

こういう取引先のトリガを作って、更新時に項目自動更新が走るように設定したうえで取引先の更新を行うと下記のようなログがとれる。
USER_DEBUG [3]|DEBUG|トリガの処理
USER_DEBUG [3]|DEBUG|トリガの処理
画面からの更新と項目自動更新の2回データの更新が行われるので、トリガも2回呼び出されることになる。こうしたトリガの2重起動を防ぐにはstaticの変数を使う。

public class AccountTriggerHelper {
    public static boolean isFirstRun = true;
}

トリガの処理を書き換えて確かめてみる。

trigger AccountTrigger on Account (before update) {

    if (AccountTriggerHelper.isFirstRun) {
        System.debug('1回目のトリガの処理');
        AccountTriggerHelper.isFirstRun = false;
    } else {
        System.debug('2回目のトリガの処理');
    }
    
}

トリガの2重起動を防げそうだ。
USER_DEBUG [4]|DEBUG|1回目のトリガの処理
USER_DEBUG [7]|DEBUG|2回目のトリガの処理

2017年4月24日月曜日

Salesforce : Apex : 新規登録した取引先へのリンクをChatterへ通知

昨日の取引先の新規追加をChatterへ通知するトリガで、新規追加した取引先の情報へアクセスするリンクが貼るなんてことができると少し便利かなと思った。通知を受け取った人がわざわざ検索しなくても、何が登録されているのかリンクをクリックするだけで見れるようになる。

URL.getSalesforceBaseUrl()

これで自分の組織のURLが取得できるのだけれど、このまま実行してしまうと
Url:[delegate=https://ap.salesforce.com]
こんな形での取得となってしまうので、あまり望み通りの形ではない。URLの部分だけが欲しい場合は、下記のような形での取得となる。

URL.getSalesforceBaseUrl().toExternalForm()

これを使って昨日のトリガを下記のように書き換えると、新規登録した取引先へのリンクをChatterに貼り付けてあげられる。

trigger AccountTrigger on Account (after insert) {

    String groupName = 'Salesforce Developer';
    
    CollaborationGroup cg = [SELECT Id ,Name FROM CollaborationGroup WHERE Name =: groupName Limit 1];
    
    List<FeedItem> feedItems = new List<FeedItem>();
    for (Account a : Trigger.new) {
        FeedItem fi = new FeedItem();
        fi.ParentId = cg.Id;
        fi.Body = a.Name + 'が新規追加されました。' + '\n' +
            URL.getSalesforceBaseUrl().toExternalForm() + '/' + a.Id;
        feedItems.add(fi);
    }
    
    insert feedItems;
    
}

2017年4月23日日曜日

Salesforce : Apex : 取引先への新規登録をChatterのグループへ通知する

FeedItemオブジェクトを操作することでChatterへの投稿を行えるようなので、取引先の新規登録をChatterの特定のグループに対して通知するようなトリガを作ってみようと思う。

trigger AccountTrigger on Account (after insert) {

    String groupName = 'Salesforce Developer';
    
    CollaborationGroup cg = [SELECT Id ,Name FROM CollaborationGroup WHERE Name =: groupName Limit 1];
    
    List<FeedItem> feedItems = new List<FeedItem>();
    for (Account a : Trigger.new) {
        FeedItem fi = new FeedItem();
        fi.ParentId = cg.Id;
        fi.Body = a.Name + 'が新規追加されました。';
        feedItems.add(fi);
    }
    
    insert feedItems;
    
}

Chatterへ投稿するにはグループの指定を行わないといけない。グループはSOQLで取得することができる。"CollaborationGroup"というオブジェクトからChatterのグループを取得できるようだ。以下の部分がグループを取得しているところ。

String groupName = 'Salesforce Developer';
CollaborationGroup cg = [SELECT Id ,Name FROM CollaborationGroup WHERE Name =: groupName Limit 1];

あとは、"FeedItem"のインスタンスを作ってinsertしてあげればChatterへの投稿完了。

2017年4月21日金曜日

Salesforce : Apex : FeedItemの要素

昨日、Chatterに対するトリガとしてFeedItemを取り扱った。このFeedItemの操作を行うことでVisualForceなどからChatterへの投稿を行えるようだ。

FeedItemオブジェクトがどんな要素を持っているのかをまとめておく。

■ID
フィードのID

■Body
投稿の内容

■CommentCount
コメントの件数

■LinkURL
リンクポストのときに入力したリンク

■Title
リンクポストのときのリンクのタイトル

■ContentType
ファイルアップロードのときのアップロードファイルのMIMEタイプ

■ContentSize
アップロードするファイルのサイズ

■ContentFileName
アップロードするファイルのファイル名

■ContentDescription
アップロードするファイルの説明

■ContentData
アップロードファイル(Base64)

■CommentCount
投稿に対するコメントの数

■LikeCount
投稿に対する「いいね」の数

2017年4月20日木曜日

Salesforce : Apex : Chatterのトリガ

Chatterにもトリガを使えるらしきことを知ったので試してみようと思う。


開発者コンソールを開いて、sObjectをのぞいてみたもののそこにはChatterの文字はない。そもそも、何で試そうと思ったのかと言えば、Chatter無かった気がするけど・・・という疑問があったから。

調べてみるとsObjectの名称がChatterではなくて、"FeedItem"のようだ。


"FeedItem"はあった。トリガを作成して、

trigger FeedItemTrigger on FeedItem (before insert) {

    System.debug('Chatterのトリガです');
    
}

Chatterを投稿してみると、


ログが出力されている。ちゃんと動作しているようだ。

USER_DEBUG [3]|DEBUG|Chatterのトリガです

2017年4月19日水曜日

Salesforce : Apex : トリガコンテキスト変数のまとめ

トリガのコンテキスト変数をまとめておこうと思う。

■isExecuting
Apexがトリガから呼ばれているときにtrueを返す。VisualForceなどトリガ以外から呼ばれるとfalseを返す。

■isInsert
レコードを新規作成を行ったときにtrueを返す。

■isUpdate
レコードの更新を行ったときにtrueを返す。

■isDelete
レコードの削除を行ったときにtrueを返す。

■isUnDelete
レコードの復元が行われたときにtrueを返す。

■isBefore
レコードに対する処理完了前(新規作成・更新・削除)であるときtrueを返す。

■isAfter
レコードに対する処理完了後であるときtrueを返す。

■new
レコードに対する処理完了後のsObjectをリストとして返す。

■old
レコードに対する処理前のsObjectをリストとして返す。

■newMap
レコードに対する処理完了後のsObjectをマップで返す。キーはIDで、値がsObject。

■oldMap
レコードに対する処理前のsObjectをマップで返す。キーはIDで、値がsObject。

■size
処理完了後のレコード件数+処理前のレコード件数を返す。

2017年4月18日火曜日

Salesforce : Apex : トリガの処理かどうか判断する

"isExecuting"というコンテキスト変数があって、これを使うとトリガから呼ばれたのか、Visual Forceなどの別のところから呼ばれたのかを判断することができる。

if (Trigger.isExecuting) {
    // トリガから呼ばれたときの処理
} else {
    // トリガ以外から呼ばれたときの処理
}

これを実際に試すために以下のようなプログラムを書いた。

■トリガ
trigger NewTrigger on Account (before insert) {
    
    NewTriggerHandler.newTriggerBeforeInsert(Trigger.new);
    
}

■クラス
public class NewTriggerHandler {
    
    public static void newTriggerBeforeInsert(List accounts) {
  
        if (Trigger.isExecuting) {
            System.debug('トリガ');
        } else {
            System.debug('トリガではない');
        }
        
    }
    
}

こんなトリガを作って、取引先を新規追加するとこんなログが得られる。
USER_DEBUG [6]|DEBUG|トリガ
Visual Forceを作るのは少し面倒なので、Execute Anonymous WindowからNewTriggerHandlerクラスを呼び出してみるとこんなログになる。
USER_DEBUG [8]|DEBUG|トリガではない
確かに、トリガから呼び出されたかどうかで処理が変わっている。だけれど、実際にどういう場合に使いたくなるのかが思いつかないので考えてみようと思う。

2017年4月16日日曜日

Salesforce : Apex : Handlerを作るとトリガがすっきりする

単純な条件分岐くらいなら問題ないのだけれど、いくつか条件が重なってくるととたんにトリガが複雑になってくる。こうしたときはHandlerクラスを作って処理はHandlerクラスで行うようにすると、処理がすっきりする。

■トリガ
trigger NewTrigger on Account (before insert, before update, before delete, after insert, after update, after delete, after undelete) {
    
    if (Trigger.isBefore) {
        // beforeトリガのときの処理
        if (Trigger.isInsert) {
         // レコード登録のときの処理
         NewTriggerHandler.newTriggerBeforeInsert(Trigger.new);
     }
    }
    
    if (Trigger.isAfter) {
        // afterトリガのときの処理
        if (Trigger.isUpdate) {
            // レコード更新のときの処理
            NewTriggerHandler.newTriggerAfterUpdate(Trigger.new);
        }
    }
    
}

■Handler
public class NewTriggerHandler {
    
    public static void newTriggerBeforeInsert(List accounts) {
        // before insert のときの処理        
    }
    
    public static void newTriggerAfterUpdate(List accounts) {
        // after update のときの処理
    }
    
}

処理が複雑になる場合は、もっとちゃんとクラスの分割というか設計を考えたほうがいいかもしれないけれど、これで十分なことも多いと思う。

2017年4月15日土曜日

Salesforce : Apex : 登録、更新、削除のトリガで処理を分岐する

before/afterトリガだけでなく、登録、更新、削除でそれぞれ別の処理をやりたいというのも、よく実装する処理。もちろん、これらも簡単に処理を分けることができるようになっている。

■Trigger.isInsert
レコードの登録処理のときにtrueを返す。

■Trigger.isUpdate
レコードの更新処理のときにtrueを返す。

■Trigger.isDelete
レコードの削除処理のときにtrueを返す。

■Trigger.isUnDelete
レコードの復元処理のときにtrueを返す。

trigger NewTrigger on Account (before insert, before update, before delete, after insert, after update, after delete, after undelete) {
    if (Trigger.isInsert) {
        // レコード登録のときの処理
    }
    if (Trigger.isUpdate) {
        // レコード更新のときの処理
    }
    if (Trigger.isDelete) {
        // レコード削除のときの処理
    }
    if (Trigger.isUnDelete) {
        // レコード復元のときの処理
    }
}

このように書くのだけれど、isBefore/isAfterと組み合わせることもある。

trigger NewTrigger on Account (before insert, before update, before delete, after insert, after update, after delete, after undelete) {
    if (Trigger.isBefore) {
        // beforeトリガのときの処理
        if (Trigger.isInsert) {
        // レコード登録のときの処理
     }
    }
    if (Trigger.isAfter) {
        // afterトリガのときの処理
        if (Trigger.isUpdate) {
            // レコード更新のときの処理
        }
        if (Trigger.isDelete) {
            // レコード削除のときの処理
        }
    }
    if (Trigger.isUnDelete) {
        // レコード復元のときの処理
    }
}

こうすることで、どんなタイミングでどんな処理を行われるのかをコントロールすることができる。レコード復元のUnDeleteはAfterトリガしかないので外だししている。

2017年4月14日金曜日

Salesforce : Apex : beforeトリガ、afterトリガで処理を分岐する

トリガを作っているとき、beforeトリガとafterトリガで処理を分岐させる必要が生じることがよくある。動くタイミングが違うので当然といえば当然かな。

こうしたときに使うのがisBeforeとisAfterコンテキスト。

■Trigger.isBefore
beforeトリガのときtrueを返す。

■Trigger.isAfter
afterトリガのときtrueを返す。

trigger NewTrigger on Account (before insert, before update, before delete,
        after insert, after update, after delete, after undelete) {
    if (Trigger.isBefore) {
        // beforeトリガのときの処理
    }
    if (Trigger.isAfter) {
        // afterトリガのときの処理
    }
}


2017年4月13日木曜日

Salesforce : Apex : Trigger.oldMapとTrigger.newMap

Trigger.oldとTrigger.newではListでレコードの情報を取得できるのだけれど、それをMapで受け取るのがTrigger.oldMapとTrigger.newMap。old、newと意味合いはほとんど同じで、Listで取得するかMapで取得するかの違いだけ。

■Trigger.oldMap
登録、更新、削除される前の状態のsObjectをMapで取得。

■Trigger.newMap
登録、更新、削除された後の状態のsObjectをMapで取得。

trigger NewTrigger on Account (before insert, before update, before delete,
        after insert, after update, after delete, after undelete) {
 Map<Id, account> oldAccountMap = Trigger.oldMap;
 Map<Id, account> newAccountMap = Trigger.newMap;
}

キーはID、値がsObject。

2017年4月12日水曜日

Salesforce : Apex : Trigger.oldとTrigger.new

私がトリガで作った処理の多くは、登録や更新するときに一定の条件に従って自動的に別のオブジェクトへの参照を付け加えたり、値を変更したりということ。更新されるレコードや更新後のレコードの値が分からないとこうした処理は行えない。

こうした場合は、この2つのコンテキストを使ってレコードの値を参照することになる。

■Trigger.old
登録、更新、削除される前の状態のsObjectを取得できる。

■Trigger.new
登録、更新、削除された後の状態のsObjectを取得できる。

trigger NewTrigger on Account (before insert, before update, before delete,
        after insert, after update, after delete, after undelete) {
 List<account> oldAccounts = Trigger.old;
 List<account> newAccounts = Trigger.new;
}

こんな感じでレコードを取得。取得したら、実行したい処理を書いてあげればOK。

注意点としてはListで取得するところ。複数レコードを一度に処理することが前提で、最大200件までこのListにはセットされる可能性がある。

2017年4月11日火曜日

Salesforce : Apex : トリガのイベントについて

トリガは呼び出されるタイミングをトリガの実行イベントの指定でコントロールすることができる。
trigger [トリガ名] on [トリガが紐づくsObject名] (トリガの実行イベント)
トリガの実行イベントは"before insert"とか"after update"とか、こんな感じで書いて、カンマ区切りで複数指定することができる。指定できるのは、以下の7パターン。

・before insert
・before update
・before delete
・after insert
・after update
・after delete
・after undelete

trigger NewTrigger on Account (before insert, before update, before delete,
        after insert, after update, after delete, after undelete) {
 // トリガの処理を記述する
}

トリガには2種類あって、それを指定しているのがbefore/after。

■before
レコードがデータベースに登録・更新・削除される前に実行される。値のチェックなんかを行うときに使う。

■after
beforeとは逆でレコードがデータベースに登録・更新・削除された後に実行される。IDなどのシステムが勝手に設定する値は事前に取得できないので、その値を使って何か処理を行いたい場合なんかに利用する。afterトリガを利用した場合、レコードは参照しかできない。

befre/afterの後に書かれているinsert/update/delete/undeleteでどういった処理の前後なのかを指定する。insert : 登録、update : 更新、delete : 削除、undelete : レコードをゴミ箱から復元したときということになる。

2017年4月10日月曜日

Salesforce : Apex : トリガを作る

レコードを登録、更新、削除するときに何か処理を入れたい場合、トリガを使うことで実現できる。開発者コンソールから作る場合は以下のような手順で作れる。

1.File > New > Apex Trigger


















2.トリガの名前とトリガを実行するsObjectを入力してSubmitをクリック















3.トリガが作成される











トリガは
trigger [トリガ名] on [トリガが紐づくsObject名] (トリガの実行イベント)
という構文で定義をするのだけれど、開発者コンソールで作成すると勝手に構文通りに作成してくれる。

上の例だとトリガの実行イベントが"before insert"となっているので、レコードの登録が行われる直前にトリガが処理される。

2017年4月9日日曜日

Salesforce : SOSL : SOSLの制限

セールスフォースにはAPIコール数など色々と制限がある。SOSLにも当然制限があるので、使いかたは注意しないといけない。

・返却される最大行数が2,000件
・検索文字は2文字以上の長さでないと検索できない
・1トランザクションで発行できるSOSLクエリは20件
SOQLは100件までなので、SOQLと同じように使っているとあっという間にガバナ制限に引っかかってしまう。

他にもステートメントや検索文字列の長さに制限が設けれらているようなのだけれど、あまり気にしなくても制限に引っかかることはないんじゃないかと思った(少なくとも私のやってきた開発ではないと思う)。
https://developer.salesforce.com/docs/atlas.ja-jp.salesforce_app_limits_cheatsheet.meta/salesforce_app_limits_cheatsheet/salesforce_app_limits_platform_soslsoql.htm

2017年4月8日土曜日

Salesforce : SOSL : SOSLでオブジェクトと項目を指定

SOSLでは返却されるオブジェクトと項目を指定することができる。

List<List<SObject>> objectsList = 
    [FIND 'TEST' IN ALL FIELDS 
     RETURNING Account(Name), Contact(FirstName, LastName)];

このクエリだと"RETURNING"以降の部分でオブジェクトと項目を指定している。標準オブジェクトとカスタムオブジェクト、両方を指定可能。

リファレンスを読むとこの"RETURNING"以降の部分は省略可能なようなのだけれど、実際に開発者コンソールで書いてみたらエラーが発生してしまった。

Entities should be explictly specified in SOSL call in Apex
どうやらApexにSOSLクエリを埋め込む場合は明示的に返却するオブジェクトを指定しないといけないようだ。

List<List<SObject>> objectsList = [FIND 'TEST' RETURNING Account];

項目の指定はしなくてもエラーにはならないけれど、IDしか返ってこないようなのであまり使い道がないような・・・。

2017年4月7日金曜日

Salesforce : SOSL : SOSLで検索する項目の範囲を指定する

SOSLは指定をしないと全項目をデフォルトで検索するようになっているのだけれど、検索の項目範囲を指定することができるので、不必要な項目を除外して検索を行える。

List<List<SObject>> objectsList = 
    [FIND 'TEST' IN ALL FIELDS 
     RETURNING Account(Name), Contact(FirstName, LastName)];

このSOSLで言うと、"ALL FIELDS"の部分が検索項目の指定をしているところ。ここには"ALL FIELDS"の他にも以下のようなものを指定できる。

・ALL FIELDS
・NAME FIELDS
・EMAIL FIELDS
・PHONE FIELDS
・SIDEBAR FIELDS

何も指定しないと"ALL FIELDS"を指定したときと同じ動作をする。

何が検索項目の範囲になるのかはここに詳しく書かれていた。
https://developer.salesforce.com/docs/atlas.ja-jp.soql_sosl.meta/soql_sosl/sforce_api_calls_sosl_in.htm

2017年4月6日木曜日

Salesforce : SOSL : Salesforce Object Search Language

Salesforce Object Search Language (SOSL)は、セールスフォースの検索言語で、SOQLのような書き方ができ、グローバル検索のような働きをしてくれる。

List<List<SObject>> objectsList = 
    [FIND 'TEST' IN ALL FIELDS 
     RETURNING Account(Name), Contact(FirstName, LastName)];

Apexでこんな風に書くと、"TEST"という単語を検索してヒットしたAccountとContactのオブジェクトを返す。検索は全フィールドに対して行われる。

SOQLとSOSLの違いは、SOQLはワイルドカード検索をしないと完全一致で検索を行うけれど、SOQLの場合は部分一致検索というところ。また、ロングテキストエリアとリッチテキストエリアはSOQLのWHERE句が対応していないので、この2つに対して絞り込みを行いたい場合はSOSL使う必要がある。

2017年4月5日水曜日

Salesforce : Apex : Apexでインスタンスを判断して条件分岐

"instanceof"キーワードを使うとApexでインスタンスを判断することができるようだ。

if (obj instanceof Contact) {
 // オブジェクトがContactのときの処理
 Contact con = (Contact)obj;
} else {
 // オブジェクトがContactじゃないときの処理
}