こんにちは。エンジニアの若葉です。
先日、社内で「意外と知らないSalesforce Tipes 10撰」という題目で
ネタを持ち寄ったので、とりあえず3つ程ご紹介しますね。
1. 標準ボタンによる画面リロード
まず、標準のリストビューにビューの選択状態を保ったままリロードする方法をご紹介します。
例えば、申請→処理中→完了のような状態が遷移するオブジェクトがある場合、
①各ステータスに応じたリストビューを設定してステータスごとにデータを管理する
②一括で状態を更新するApexを書いて、リストビューにカスタムボタンを配置
③カスタムボタンを実行したら、更新ボタンでリフレッシュして、現在のビューから当該のデータを追い出す
なんてケースが良くあるかと思います。
ただ、実行と更新が2段階に分かれていているので、処理件数が多い場合には、
更新ボタンを押し忘れて、処理済みのデータを対象に再度更新処理を実行してしまう恐れが残ります。
そこで、カスタムボタンに記述するJavascriptに以下のコードを追加しておくと、
var views = ListViewport.instances, i; for (i in views) { if (views.hasOwnProperty(i)) {
// 現在のリストビューを保持したままリロード views[i].refreshList(); return; } }
現在のビューを保ったままリロードができます。
事故の防止、操作の簡易化が図れてとても便利ですね。
2. Sitesで強制SSL
次ですが、Sitesで公開WEBフォームを作る際、住所情報などを登録するので
SSLアクセスが必須となるケースが良くあります。
設定によってSSLアクセスを強制できる助かるのですが、残念ながらその設定は無いようです。
そこで、
①フォームを表示するリクエストをチェックして、
②httpだったらhttpsにしてリダイレクトする
という感じの機能を実装するのですが、URL関連のAPIがわかりにくく、
毎回リファレンスを見直しているので、この際整理してみました。
使用するAPI
プロトコルの確認 | URL.getProtcol() |
---|---|
現在のセキュアなドメインの取得 | Site.getBaseSecureUrl() |
現在のPathの取得 | URL.getPath() |
Sitesのドメインはsand/prod間でドメインが異なるので、環境差分を吸収するのに一工夫必要です。このAPIを知るまでは、この環境違いへ対応するのにカスタム表示ラベルに切り出すなど、悲しい対応をしていました。。
また、Pathの取得はちょっと注意が必要で、取得できるPathは実際のURL中に現れない「/apex」が含まれてしまうので、リダイレクトさせるには、これを除外する必要あります。
使用例
// 現在のURL情報 public class CurrentUrl { public Boolean isSecure() { return (URL.getCurrentRequestUrl().getProtocol() == 'https'); } public Boolean isInSecure() { return !isSecure(); } public String getSecureSiteUrl() { String baseUrl = Site.getBaseSecureUrl(); String path = URL.getCurrentRequestUrl().getPath(); Integer pos = path.lastIndexof('/'); return baseUrl + path.substring(pos, path.length()); } }
こんな感じで、現在のURLを取り扱うクラスを定義しておくと、
コントローラからシンプルに呼び出せて、使いまわしが簡単になります。
// 呼び出し側 CurrentUrl cu = new CurrentUrl(); if (cu.isInSecure()) { // httpsでリダイレクト PageReference pr = new PageReference(cu.getSecureSiteUrl()); pr.setRedirect(true); return pr; } return null; }
3. MIXED_DML_EXCEPTIONの避け方
最後です。テストコードが失敗する場合に、
MIXED_DML_OPERATION, 非設定オブジェクトを更新した後の設定オブジェクト上の DML操作 (またはその逆) は、許可されていません
この例外が原因となっているケースがたまにあります。
サンプル
@isTest public static void test() { User u = getUser(); insert u;//・・・(1) 設定オブジェクト MixedDmlSample md = new MixedDmlSample(); Id accountId = md.createAccount(u.Id); System.assertEquals('flect', [SELECT Name FROM Account WHERE Id=:accountId][0].Name); } // プロダクトコード public class MixedDmlSample { public Id createAccount(String userId) { Account a = new Account(Name='flect'); insert a;//・・・(2) 非設定オブジェクト User u = [SELECT LastName FROM User WHERE Id=:userId]; Contact c = new Contact(LastName=u.LastName, Account=a); insert c;//・・・(3) 非設定オブジェクト return a.Id; } }
これは、例えば上記のサンプルでいうと、User(1)とAccount(2)は、同一トランザクションでInsert/Updateできない、というような制限です。
詳細は以下のリンクから確認できますが、制限の理由は、UserもAccountも、アクセスする側とされる側、としてレコードの共有情報を更新する必要があり、それは各々のDMLを直列に確定させないと正しく処理できない可能性があるためのようです。
http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_dml_non_mix_sobjects.htm
上記のサンプルのように、プロダクトコードのみでは該当しないけど、テストデータとしてUserを事前にInsertすることは多いので、この制限はテストコードでよく遭遇します。
制限だから仕方いないかと思いきや、そこはちゃんとテストコード用に回避策が用意されています。
回避例
@isTest public static void test() { User thisUser = [SELECT Id FROM User WHERE Id = :UserInfo.getUserId()]; System.runAs (thisUser) {//・・★ これで囲うだけ User u = getUser(); insert u; MixedDmlSample md = new MixedDmlSample(); Id accountId = md.createAccount(u.Id); System.assertEquals('flect', [SELECT Name FROM Account WHERE Id=:accountId][0].Name); } }
System.runAs ブロックで囲ってあげれば、それだけでOKです。
アクセス制御に関するテストはどうなるんだろ?とか追加の調査の余地はありますが、とりあえずは回避策があるので良しとしておきます。
それでは。