【Salesforce】RemoteObjectsの取得条件オブジェクト自動生成について
2015.12.24
今回はSalesforceのVisualForce Pageを開発している最中に詰まった事について書きたいと思います。
一言で言うと、「RemoteObjectsのwhere句でand演算子の下に3つ以上の条件項目を並列に設定できない」という問題です。
これだけでは解りづらいと思いますので、関連する単語を含めて順を追って説明していきます。
1.RemoteObjects
カスタムユーザインタフェースであるVisualForce PageからSalesforce上のオブジェクトデータを操作する仕組みの一つに、RemoteObjectsというものがあります。
RemoteObjectsは、JavaScript経由でオブジェクトデータの取得・作成・更新・削除といった操作を行うためのものです。
「APIリクエストの使用制限の影響を受けずに直接アクセスすることが出来る」「ApexClassを実装せずに簡単にデータのやり取りが出来る」というメリットがあり、非常に使い易い仕組みと言えます。
2.データの操作方法
RemoteObjectsを用いてオブジェクトデータを操作するには、
1. RemoteObjectsコンポーネントを用いてアクセス定義を行い、アクセスするオブジェクトのモデルを生成する。
2. Javascript上で、生成されたモデルを用いてデータ操作を行う。
というステップを踏みます。
<apex:remoteObjects > <apex:remoteObjectModel name="User" jsShorthand="Users" fields="Id,Name"> <apex:remoteObjectField name="Age" jsShorthand="Ages"/><!-- 年齢 --> <apex:remoteObjectField name="Sex" jsShorthand="Sexs"/><!-- 性別 --> </apex:remoteObjectModel> </apex:remoteObjects>
var user = new SObjectModel.User(), options = {limit:100}; user.retrieve(options,function(error,records){ //データ取得後の処理を記述 });
上の例では、まずRemoteObjectsコンポーネントでUserオブジェクトのId,Name,Age,Sexフィールドへのアクセスを定義しています。
そしてJavaScript上でインスタンス生成を行い、retrive(データ取得) functionを呼び出しています。
その際、データの取得条件を第一引数としてオブジェクトで渡すことが可能です。
3.本題
今回詰まった点は、このretriveの取得条件オブジェクトのWhere句についてです。
Where句では一般的にSQLで使える演算子をそのまま利用することができますが、一部仕様が異なっています*1。
その1つに、「and演算子の下に置く項目は必ず2つでないといけない」と言うものがあります。
サンプルを挙げて説明します。
まず、コード例3.1です。
この取得条件では、and演算子の下に項目が2つあるため、問題なくデータを取得できます。
options = { limit : 10, where : { and : { Name : {like : "山田%"}, Age : {eq:20}, } } }
次にコード例3.2の取得条件ですが、ここではand演算子の下に項目が3つあるため、エラーを吐きます。
options = { limit : 10, where : { and : { Name : {like : "山田%"}, Age : {eq:20}, Sex : {eq:"male"} } } }
また、「and演算子の下に置く項目は必ず2つでないといけない」という制約であるため、
コード例3.3のようなand演算子の下の項目が1つの場合も同様にエラーが吐かれます。
options = { limit : 10, where : { and : { Name : {like : "山田 %"}, } } }
例3.4は、コード例3.2, 3.3の取得条件をRemoteObjectsに渡した際に返ってくるエラーメッセージです。
Error: 無効な取得条件が指定されています。ValidationError at Error (native) at d.h.Base.Sfdc.Class._handleRetrieveResult (https://c.ap2.visual.force.com/jslibrary/1449190104000/sfdc/VFSObjectCrud.js:63:300) at d._handleRetrieveResult (https://c.ap2.visual.force.com/jslibrary/1442862172000/ui-sfdc-javascript/SfdcCore.js:259:430) at https://c.ap2.visual.force.com/jslibrary/1449190104000/sfdc/VFSObjectCrud.js:57:89 at e.cb (https://c.ap2.visual.force.com/jslibrary/1449190104000/sfdc/VFRemote.js:133:252) at VFExt3.direct.RemotingProvider.VFExt3.extend.doCallback (https://c.ap2.visual.force.com/jslibrary/1449190104000/sfdc/VFRemote.js:99:417) at VFExt3.direct.RemotingProvider.VFExt3.extend.onData (https://c.ap2.visual.force.com/jslibrary/1449190104000/sfdc/VFRemote.js:94:40) at VFExt3.extend.handleResponse (https://c.ap2.visual.force.com/jslibrary/1449190104000/sfdc/VFRemote.js:75:147) at a (https://c.ap2.visual.force.com/jslibrary/1449190104000/sfdc/VFRemote.js:39:80) at https://c.ap2.visual.force.com/jslibrary/1449190104000/sfdc/VFRemote.js:40:307
条件項目が1つだけの場合は、そもそもand演算子を置かなければ解決します。
条件項目が3つ以上ある場合は、and演算子を入れ子構造にすることで解決できます。
コード例3.5では、それぞれのand演算子の下に項目が2つだけある構造が保たれているため、
問題なくデータを取得することができます。
options = { limit : 10, where : { and : { Name : {like : "山田 %"}, and : { Age : {eq:20}, Sex : {eq:"male"}, } } } }
これで解決!
となれば良かったんですが、そうは問屋が卸しません。
常に固定された項目数の取得条件であれば、ベタ書きでコード例3.4のような入れ子構造を記述すれば問題ありませんが、
条件項目数が動的に変化する場合、その変化パターン数だけ取得条件を記述する必要が出てきます。
変化パターン数が2つ、3つであれば良いですが、それ以上ある場合はコードが汚くなり可読性も落ちてしまいます。
4.じゃあどうするの
A. 条件項目数が動的に変化するなら、取得条件も動的に生成すれば良いじゃない。
そういうことになりました。
さて、動的に生成する方法です。
条件項目数が増えれば増えるほど、and演算子の入れ子構造が深くなります。
ということは、「取得された条件項目の数だけ再帰をぐるぐる回し、最後の項目になったら条件オブジェクトを戻してand演算子でくっつける」という処理で生成できる筈です。
そう考えて実装したコードがこちらになります。
/** * 再帰的にRemoteObjectの取得条件オブジェクトを生成 * @param list 取得条件リスト * @param keys 取得条件リストのキー * @param i 制御変数 */ var convert = function(list,keys,i){ var options = {}, result = {}, result_key = ""; if (keys.length > (i+1)) { result = this.convert(list, keys, i+1); options['and'] = {}; options['and'][list[keys[i]].key] = list[keys[i]].condition; // options['and']['Age'] = {eq:20} result_key = Object.keys(result); // result_key = 'Sex' options['and'][result_key] = result[result_key]; // options['and']['Sex'] = {eq:"male"} } else { options[list[keys[i]].key] = list[keys[i]].condition; // options['Sex'] = {eq:"male"} } return options; }; /* * @param key フィールド名 * @param condition 取得条件項目 */ var list = [ {key:"Name",condition:{like:"山田%"}}, {key:"Age",condition:{eq:20}}, {key:"Sex",condition:{eq:"male"}}, ], keys = Object.keys(list), i = 0; var where = convert(list, keys, i), options = { limit:10, where : where, }
変更点としては15, 16行目と19行目。
再帰としてフィールド名付きのオブジェクトを返し、and演算子でくっつける際に返って来たオブジェクトの中身を突っ込んでます。
options = { limist:10, where : { and : { and: { Age {eq:20}, Sex : {eq:"male"}, }, Name : {like:"山田%"}, }, } }
「and演算子の下に置く項目は必ず2つでないといけない」という制約をクリアした入れ子構造の取得条件オブジェクトを無事生成できました。
5.検索条件オブジェクトの制約についての考察
今回はRemoteObjects取得条件オブジェクトの「and演算子の下に置く項目は必ず2つでないといけない」という制約にぶつかり、それを回避するために試行錯誤を行いました。
そこで気になってくるのは、そもそも何故そのような制約が定められているのか、という点です。
制約が定められている理由は、一般的なSQLのWhere句を考えることで見えて来ます。
Where句で用いられるAND演算子は『両端のブール式の値が真であれば、真を返す』という演算子です。
ここでポイントとなるのは、AND演算子は『2つの項目の』論理積を求めるものであると言う事です。
つまり、例5.1のように3つの条件の論理積を求める場合でも、実際には例5.2のように1つ目と2つ目の条件の論理積を求め、その結果と3つ目の条件の論理積を求めるという順に演算が行われるのです。
WHERE Name like "山田%" AND Age = 20 AND Sex like "male";
WHERE (Name like "山田%" AND Age = 20) AND Sex like "male";
RemoteObjectsの取得条件オブジェクトに「and演算子の下に置く項目は必ず2つでないといけない」という制約があるのも、この論理演算子の特性が理由だと推察できます。「実際には2項ずつ演算するのだから、そのように取得条件オブジェクトも構成しろ」という事ではないでしょうか。
また、2項ずつしか置けないという制約を定めることで、Where句で括弧を用いるのと同じように、条件の優先順位を明示することになるというのも理由の1つかと思われます。
6.終わりに
という訳で、RemoteObjectsの取得条件オブジェクトを自動生成する手法について書きました。
今回の記事では、and演算子のみを用いた検索条件についてのみ言及しています。
これは、実際のプロジェクトでand演算子・or演算子を混合させた検索条件を用いる場面がなかったためです。
and演算子・or演算子を混合した複雑な検索条件オブジェクトの自動生成には、今回の手法とはまた別の手法が必要になります。
そこまで複雑な検索を行うのであれば、検索条件オブジェクトの自動生成するのではなく、Apexコントローラー内に記述した処理を@RemoteActionとして呼び出してデータを取得する方が簡単で良いかもしれません。
以上です。
CONTACT
お問い合わせ
あなたの「想い」に挑戦します。
どうぞお気軽にお問い合わせください。
受付時間:平日9:00〜18:00 日・祝日・弊社指定休業日は除く