Apex ワークブック Apex ワークブック, Spring ’15 @salesforcedocs 最終更新日: 2015/2/19 © Copyright 2000–2015 salesforce.com, inc. All rights reserved. Salesforce およびその他の名称や商標は、salesforce.com, inc. の登録商標です。本ドキュメントに記載されたその他の商標は、各社に所有権があります。 目次 Apex ワークブック . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 第 1 章: 基礎 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Warehouse カスタムオブジェクトの作成 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 開発者コンソールの使用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 開発者コンソールの有効化 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 開発者コンソールを使用した Apex コードの実行 . . . . . . . . . . . . . . . . . . . . . . . . . . 6 まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 サンプルデータの作成 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 クラスの作成とインスタンス化 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 開発者コンソールを使用した Apex クラスの作成 . . . . . . . . . . . . . . . . . . . . . . . . . . 9 クラスメソッドのコール . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Salesforce ユーザインターフェースを使用した Apex クラスの作成 . . . . . . . . . . . . . 12 まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 第 2 章: Apex 言語の基本 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 プリミティブデータ型と変数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Boolean と条件付きステートメント . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Time、Date、Datetime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 Integer、Long、Double、Decimal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 null 変数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 enum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 コメント、大文字と小文字の区別、コレクション、ループ . . . . . . . . . . . . . . . . . . . . . . 21 コメント . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 大文字と小文字の区別 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 配列とリスト . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 ループ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Set と Map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 クラス、インターフェース、プロパティ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 クラスの定義 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 private 修飾子 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 コンストラクタ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 静的変数、定数、メソッド . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 インターフェース . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 プロパティ構文 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 目次 sObject とデータベース . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 sObject とは? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 SOQL および SOSL クエリ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 sObject リレーションのトラバースとクエリ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 SOQL for ループ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 Apex データ操作言語 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 例外処理 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 例外とは? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 try、catch、finally ステートメント . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 組み込み例外および共通メソッド . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 さまざまな例外種別のキャッチ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 カスタム例外の作成 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 第 3 章: コンテキストでの Apex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 1 つのトランザクションとしてのデータ操作の実行 . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 トリガを使用したカスタムビジネスロジックの追加 . . . . . . . . . . . . . . . . . . . . . . . . . . 54 トリガの作成 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 トリガの呼び出し . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 Apex 単体テスト . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 テストユーティリティクラスの追加 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 テストメソッドの追加 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 テストの実行とコードカバー率 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 ガバナ実行制限内での Apex の実行 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Apex のスケジュール実行 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 スケジュール可能なインターフェースを実装するクラスの追加 . . . . . . . . . . . . . . 66 スケジュール可能なクラスのテストの追加 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 ジョブのスケジュールとスケジュール済みジョブの監視 . . . . . . . . . . . . . . . . . . . 68 まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 Apex 一括処理 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 Apex 一括処理クラスの追加 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 Apex 一括処理クラスのテストの追加 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 一括処理ジョブの実行 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Apex REST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 REST リソースとしてのクラスの追加 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 Apex REST POST メソッドを使用したレコードの作成 . . . . . . . . . . . . . . . . . . . . . . . 77 Apex REST GET メソッドを使用したレコードの取得 . . . . . . . . . . . . . . . . . . . . . . . . 78 まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 Apex コントローラを使用する Visualforce ページ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 Visualforce 開発モードの有効化 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 目次 単純な Visualforce ページの作成 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 Visualforce ページでの商品データの表示 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 Visualforce ページでのカスタム Apex コントローラの使用 . . . . . . . . . . . . . . . . . . . 84 Apex コントローラでの内部クラスの使用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 Apex コントローラへの action メソッドの追加 . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 Apex ワークブック Force.comApexは、強く型付けされたオブジェクト指向プログラミング言語で、Force.comプラットフォーム上で 実行するコードを記述できます。Force.comでは、コード実行のスケジュール、一括処理、トリガなど、多くの 高レベルのサービスを標準で提供します。これらのすべてで Apex を記述する必要があります。 Apex ワークブックについて このワークブックは、Apex プログラミング言語と Apex を使用可能なコンテキスト (トリガなど) の両方につい て概要を説明します。 このワークブックでは、プログラミングについてごく基本的な知識がある方を対象としています。プログラミ ングの知識がない場合でも進めることはできますが、やや難しく感じるかもしれません。プログラミングの学 習を始める場合は、Head First Java をお勧めします。これは Java に関する本ですが、Java は多くの点で Apex と非 常によく似ており、必要な基本知識が得られます。 このワークブックは 3 章で構成されます。 • 第 1 章: 基礎: 単純な Apex クラスの作成方法、開発者コンソールを使用して Apex スニペットを実行する方法 など、基本事項を学習します。 • 第 2 章: Apex 言語の基本: Apex 言語の構文、型システム、およびデータインテグレーションについて学習し ます。 • 第 3 章: コンテキストでのApex: Apex を使用してトリガ、単体テスト、スケジュール済み Apex、Apex 一括処 理、REST Web サービス、および Visualforce コントローラを記述する方法を学習します。 目標は、実際のアプリケーションを作成することではなく、Apexのさまざまな側面を一通り知っていただくこ とです。ワークブックを進めながら、自由に試してみてください。コードを少し変更したり、別のコンポーネ ントに置き換えたりして、楽しく学習しましょう。 対象利用者 このワークブックは、Force.com プラットフォームを初めて使用し、プラットフォームでの Apex 開発について 概要知識を必要としている開発者、またコーディングを使用したアプリケーション開発の知識を深めたい Salesforceシステム管理者を対象としています。Force.comを初めて使用するシステム管理者は、「Force.com Platform Fundamentals (Force.com プラットフォームの基本)」でポイントアンドクリックによるアプリケーション開発の概 要を参照してください。 1 Apex ワークブック サポートされるブラウザ ブラウザ コメント Microsoft® Internet Explorer® バージョン 7、 8、9、10、11 Internet Explorer を使用する場合は、Salesforce でサポートされている 最新バージョンを使用することをお勧めします。すべての Microsoft ソフトウェア更新を適用してください。次の制限があります。 • Salesforceフルサイトは、Windows 向けタッチ対応デバイスの Internet Explorer ではサポートされていません。代わりに Salesforce1 モバ イルブラウザアプリケーションを使用してください。 • [Salesforce1 の設定] ページと Salesforce1 ウィザードを使用するには Internet Explorer 9 以降が必要です。 • Internet Explorer 11 の HTML ソリューションエディタは、Salesforce ナレッジではサポートされていません。 • Internet Explorer の互換表示機能はサポートされていません。 • Internet Explorer 10 の Metro バージョンはサポートされません。 • Internet Explorer 6 および 7 は、複数アカウントのログインヒント ではサポートされていません。 • Internet Explorer 7 および 8 は、データインポートウィザードでは サポートされていません。 • Internet Explorer 7、8、および 11 は、開発者コンソールではサポー トされていません。 • Internet Explorer 7 は、Open CTI ではサポートされていません。 • Internet Explorer 7 および 11 は、CTI Toolkit バージョン 4.0 以降を使用 して作成された Salesforce CRM Call Center ではサポートされていま せん。 • Internet Explorer 7 は、Force.com Canvas ではサポートされていませ ん。 • Internet Explorer 7 は、高度なブラウザパフォーマンスや最近の Web テクノロジを必要とする Salesforce コンソール機能ではサポート されていません。Internet Explorer 7 で使用できないコンソール機 能は次のとおりです。 – [最新のタブ] コンポーネント – サイドバーの複数のカスタムコンソールコンポーネント – サイドバーに積み上げられているコンソールコンポーネント の垂直方向の自動サイズ調整 – コンソールコンポーネントの [ボタン CSS] のフォントと フォントの色 – マルチモニターコンポーネント – サイズ変更可能な強調表示パネル 2 Apex ワークブック ブラウザ コメント – フィードベースのページレイアウトの全体の幅のフィードオ プション • Internet Explorer 7 および 8 は、セルフサービスのコミュニティテ ンプレートではサポートされていません。 • Internet Explorer 7 および 8 では、商談分割の複数行の編集が使用 されると、パフォーマンス上の問題が生じます。 • セルフサービスのコミュニティテンプレートでは、Internet Explorer 9 以降でデスクトップユーザが、Internet Explorer 11 以降でモバイ ルユーザがサポートされています。 • Internet Explorer 7、8、および 9 は、Salesforce Analytics Cloud ではサ ポートされません。 設定の推奨事項については、Salesforce ヘルプの 「Internet Explorer の 設定」を参照してください。 Mozilla® Firefox® の最新の安定バージョン Salesforce は Firefox の最新バージョンのテストおよびサポートに努め ています。 • Mozilla Firefox は、セルフサービスのコミュニティテンプレートの デスクトップユーザに対してのみサポートされています。 設定の推奨事項については、Salesforce ヘルプの 「Firefox の設定」を 参照してください。 Google Chrome™ の最新の安定バージョン Google Chrome は自動的に更新を適用するため、Salesforce は最新バー ジョンのテストおよびサポートに努めています。Chrome の設定に 関する推奨事項はありません。Chrome は、[Google ドキュメントを Salesforce に追加] ブラウザボタンまたは [コンソール] タブではサ ポートされていません (Salesforce コンソールはサポートされていま す)。 Mac OS X での Apple® Safari® バージョン 5.x および 6.x Safari の設定に関する推奨事項はありません。iOS の Apple Safari は、 Salesforce フルサイトではサポートされていません。 • Safari は、Salesforce コンソールではサポートされていません。 • Safari は、バージョン 4.0 より前の CTI Toolkit を使用して作成され た Salesforce CRM Call Center ではサポートされていません。 • Safari は、Salesforce Analytics Cloud ではサポートされていません。 ご利用になる前に Force.com 開発をサポートする Force.com 環境が必要です。このチュートリアルは、Force.com Developer Edition 環境 で実行するよう設計されており、この環境は https://developer.salesforce.com/signup から無償で入手できます。 1. ブラウザで https://developer.salesforce.com/signup にアクセスします。 3 Apex ワークブック 2. 各項目にユーザ情報と会社情報を入力します。 3. [Email Address (メールアドレス)] 項目には、Web ブラウザから簡単に確認できる公開アドレスを使用 してください。 4. 一意の [Username (ユーザ名)] を入力します。ユーザ名もメールアドレスの形式にする必要があります が、メールアドレスと同じにする必要はなく、通常は違うものを入力することをお勧めします。ユーザ名 は developer.salesforce.com でのログイン情報および ID であるため、担当する作業を説明するユーザ 名 (develop@workbook.org) や自分自身を表すユーザ名 (firstname@lastname.com) を選ぶことで、よ り有益に使用できます。 5. [マスターサブスクリプション契約] を読み、チェックボックスをオンにします。 6. 表示された画像認証文字列を入力し、[Submit Registration (登録を実行)] をクリックします。 7. その後まもなく、ログインリンクを記載したメールが届きます。リンクをクリックし、パスワードを変更 します。 さらに、Force.com自体について少し学習すると、コンテキストを理解するうえで役立ちます。『Force.com ワー クブック』の最初のいくつかのチュートリアルを参照してください。 参考用に、このワークブックに含まれている長いコードサンプルのリポジトリを作成しました。 http://bit.ly/ApexWorkbookCode_Spring12 からダウンロードできます。 終了後にできること このワークブックを終了すると、以下の資料を使用して、より高度な Apex および Force.com 開発を行えるよう になります。 • https://developer.salesforce.com/page/Cheat_Sheets から Apex 早見表をダウンロードしてください。 • Force.com と Visualforce についての詳細は、https://developer.salesforce.com/page/Force.com_workbook にアクセスし て、同様の手引き書である『Force.com ワークブック』と『Visualforce ワークブック』を参照してください。 • Force.com の詳細、関連記事、およびドキュメントを確認するには、https://developer.salesforce.com. の Developer Force にアクセスしてください。特に、『Force.com Apex コード開発者ガイド』は必ず確認してください。 4 第 1 章: 基礎 この一連のチュートリアルでは、カスタムオブジェクトとサンプルデータを設定します。また、Apex言語を使 い始める前に必要ないくつかの必須スキルについて学習します。 ヒント: 第 2 章と第 3 章のチュートリアルを行う前に、第 1 章のチュートリアル 1 と 3 を完了しておく必 要があります。開発ツールを使用する場合は、チュートリアル 2 と 4 は省略できます。チュートリアル 2 では、このワークブックですべてのサンプルの実行に使用する、開発者コンソールの使用方法を説明し ます。チュートリアル 4 では、クラスを作成してそのメソッドをコールする方法を説明します。 • Warehouse カスタムオブジェクトの作成: このチュートリアルで使用するカスタムオブジェクトの作成手順 を学習します。 • 開発者コンソールの使用: 必須のデバッグツールである開発者コンソールを使用してApexのスニペットを実 行し、実行ログ出力を処理する方法を学習します。このワークブックでは、Apex 言語の学習とデバッグに 開発者コンソールを使用します。 • サンプルデータの作成: チュートリアルで参照するサンプルデータをプログラムで作成する場合に使用でき るサンプルコードが含まれます。 • クラスの作成とインスタンス化: Apexクラスの概要を学習します。これは、Apexコード開発の基本となりま す。 Warehouse カスタムオブジェクトの作成 このワークブックの例ではカスタムオブジェクトを使用します。これらのカスタムオブジェクトは、Force.com ワークブックなどの他のワークブックとも共通しており、倉庫アプリケーションの管理に使用されるオブジェ クトを表します。次のオブジェクトを使用します。 • Merchandise • Invoice Statement • Line Item これらのオブジェクトは、次の 2 つの異なる方法のいずれかを使用して作成できます。 • Force.comワークブックのチュートリアル 1、2、および 3 を実行して手動でオブジェクトを作成する (60 分)。 • オブジェクトを作成するパッケージを組織にインストールする (5 分)。 このチュートリアルの残りの部分では、2 つ目の方法である、新しい DE 組織にパッケージをインストールして カスタムオブジェクトをリリースする方法について説明します。 DE 組織にログインした状態で、次の手順を実行します。 1. ログインに使用した同じブラウザウィンドウで、新しいブラウザタブを開き、そのタブを使用して http://bit.ly/ApexWorkbookPackage1_4 を読み込みます。 2. [次へ] > [Next (次へ)] > [Next (次へ)] > [Install (インストール)] をクリックします。 3. [コンポーネントを表示] をクリックし、3 つのカスタムオブジェクト (Merchandise、Invoice Statement、Line Item) を含む、組織にリリースしたコンポーネントを簡単に確認します。 5 第 1 章: 基礎 開発者コンソールの使用 確認後、この 2 つ目のブラウザタブを閉じて元のタブに戻ることができます。 開発者コンソールの使用 開発者コンソールでは、Apex コードステートメントを実行できます。また、Apex クラスまたはオブジェクト 内の Apex メソッドを実行することもできます。このチュートリアルでは、開発者コンソールを開き、基本的 な Apex ステートメントをいくつか実行し、ログ設定を切り替えます。 開発者コンソールの有効化 Salesforce 環境にログインすると、画面には現在使用しているアプリケーション (下図では Warehouse) とユーザ の名前が表示されます。 1. あなたの名前 > [開発者コンソール] をクリックします。 開発者コンソールは別のウィンドウで開きます。 メモ: [Developer Console (開発者コンソール)] オプションが表示されない場合、使用している Force.com 環 境の種別が適切ではない可能性があります。詳細は、このワークブックの冒頭にある「ご利用になる 前に」を参照してください。 2. 開発者コンソールを初めて開く場合、開発者コンソールの機能を紹介するツアーを見ることができます。 [ツアーの開始] をクリックすると、開発者コンソールに関する説明が表示されます。 開発者コンソールはいつでも開くことができます。 開発者コンソールを使用した Apex コードの実行 開発者コンソールの外観には圧倒されるかもしれませんが、これはコード操作に役立つツールのコレクション にすぎません。このレッスンでは、Apexコードを実行して、ログインスペクタに結果を表示します。ログイン スペクタは、頻繁に使用する便利なツールです。 1. [Debug (デバッグ)] > [Open Execute Anonymous Window (実行匿名ウィンドウを開く)] をクリックするか、Ctrl キーを押しながら E キーを押します。 2. [Enter Apex Code (Apex コードを入力)] ウィンドウに、テキスト「System.debug( 'Hello World' );」と 入力します。 メモ: System.debug() は、Java で System.out.println() (または printf()) を使用するのに似て います。ただし、クラウドでコーディングする場合、出力方法が異なります。次のとおりです。 3. [Open Log (ログを開く)] を選択解除し、[Execute (実行)] をクリックします。 6 第 1 章: 基礎 開発者コンソールを使用した Apex コードの実行 コードを実行するたびに、ログが作成されて [Logs (ログ)] パネルの一覧に表示されます。 ログをダブルクリックするとログインスペクタで開きます。一度に複数のログを開いて結果を比較できます。 ログインスペクタは、操作のソース、その操作のトリガ、その後の状況を表示する、状況に対応する実行ビュー アです。このツールを使用して、データベースイベント、Apex処理、ワークフロー、および入力規則ロジック を含むデバッグログを検査できます。 ログインスペクタには、特定の用途のために事前定義されたパースペクティブが含まれます。[Debug (デバッ グ)] > [Switch Perspective (パースペクティブを切り替え)] をクリックして別のビューを選択するか、Ctrl キーを 押しながら P キーを押して個別のパネルを選択します。通常、[Execution Log (実行ログ)] パネルを最も頻繁に使 用します。このパネルには、コードを実行したときに発生した一連のイベントが表示されます。1 つのステー トメントでも多くのイベントが生成されます。ログインスペクタでは、メソッドの入口と出口、データベース や Web サービスの操作、リソースの制限など、多くの種別のイベントがキャプチャされます。イベント種別 USER_DEBUG は、System.debug() ステートメントの実行を示します。 7 第 1 章: 基礎 開発者コンソールを使用した Apex コードの実行 1. [Debug (デバッグ)] > [Open Execute Anonymous Window (実行匿名ウィンドウを開く)] をクリックするか、Ctrl キーを押しながら E キーを押し、次のコードを入力します。 System.debug( 'Hello World' ); System.debug( System.now() ); System.debug( System.now() + 10 ); 2. [Open Log (ログを開く)] を選択して、[Execute (実行)] をクリックします。 3. [Execution Log (実行ログ)] パネルで、[Exe (実行)] を選択します。これにより、実行されたステートメントを表 す項目のみが表示されるように制限されます。たとえば、累積制限情報は除外されます。 4. リストを絞り込んで USER_DEBUG イベントのみを表示するには、[Debug Only (デバッグのみ)] を選択する か、[Filter (検索条件)] 項目に 「USER」と入力します。 メモ: 検索条件のテキストでは、大文字と小文字が区別されます。 これで、Force.com プラットフォームでコードを実行して、結果を表示できました。 もうひとこと... 開発者コンソールのヘルプ 開発者コンソールの詳細を参照するには、開発者コンソールで [Help (ヘルプ)] > [Help Docs… (ヘルプドキュ メント...)] をクリックします。さまざまなガイドツアーを参照することもできます。[Help (ヘルプ)] > [Take the tour (ツアーを開始)] を選択して開始し、機能を選択して詳細を学習できます。 匿名ブロック 開発者コンソールでは、コードステートメントを簡単に実行できます。[Logs (ログ)]パネルで結果をすぐに 評価できます。開発者コンソールで実行するコードは、匿名ブロックと呼ばれます。匿名ブロックは現在 のユーザとして実行されるため、コードがユーザのオブジェクトレベルや項目レベルの権限に違反すると コンパイルに失敗する場合があります。これは、Apex クラスとトリガには該当しません。 8 第 1 章: 基礎 まとめ まとめ Apexコードを実行して実行結果を表示するには、開発者コンソールを使用します。詳細な実行結果には、コー ドで生成された出力だけでなく、実行パスに沿って発生したイベントも含まれます。こうしたイベントとし て、別のコードをコールした結果やデータベース操作の結果などがあります。 サンプルデータの作成 前提条件: • Warehouse カスタムオブジェクトの作成 このワークブックのいくつかのチュートリアルでは、ご使用のデータベースにすでにサンプルデータがあるこ とを前提としています。各チュートリアルのコード例を実行できるように、最初にサンプルレコードを作成す る必要があります。 開発者コンソールを使用して、チュートリアル 1 で作成したサンプルオブジェクトにデータを入力します。 1. 開発者コンソールを開き、[デバッグ] > [Open Execute Anonymous Window (実行匿名ウィンドウを開く)] をク リックすると、[Enter Apex Code (Apex コードを入力)] ウィンドウが表示されます。 2. チュートリアル 1 でパッケージをインストールした場合は、次のコードを実行します。 ApexWorkbook.loadData(); 手動でスキーマを作成した場合は、gist URL (https://gist.github.com/1886593) からコードをコピーして貼り付け て、実行します。 3. コードを実行したら、コンソールを閉じます。 クラスの作成とインスタンス化 Apexはオブジェクト指向プログラミング言語であるため、記述したApexの多くはクラスに包含され、オブジェ クトのブループリントまたはテンプレートと呼ばれることがあります。このチュートリアルでは、2 つのメ ソッドを持つ単純なクラスを作成し、開発者コンソールから実行します。 開発者コンソールを使用した Apex クラスの作成 開発者コンソールで Apex クラスを作成する手順は、次のとおりです。 1. 開発者コンソールを開くには、あなたの名前 > [開発者コンソール] をクリックします。 2. [File (ファイル)] > [New (新規)] > [Apex Class (Apex クラス)] をクリックします。 3. 新しいクラスの名前として「HelloWorld」と入力し、[OK] をクリックします。 9 第 1 章: 基礎 開発者コンソールを使用した Apex クラスの作成 4. 新しい空の HelloWorld クラスが作成されます。静的メソッドをクラスに追加するには、中括弧の間に次 のテキストを追加します。 public static void sayYou() { System.debug( 'You' ); } 5. インスタンスメソッドを追加するには、次のテキストを最後の閉じ中括弧の直前に追加します。 public void sayMe() { System.debug( 'Me' ); } 6. [Save (保存)] をクリックします。 もうひとこと... • ここでは、静的メソッド sayYou() とインスタンスメソッド sayMe() を持つ HelloWorld というクラス を作成しました。メソッドの定義を見ると、これらのクラスが別のクラス System をコールして、そのク ラスの debug() メソッドを呼び出し、そのメソッドが文字列を出力することがわかります。 • 作成したクラスの sayYou() メソッドを呼び出すと、そのメソッドは System クラスの debug() メソッ ドを呼び出し、出力が表示されます。 • 開発者コンソールは、バックグラウンドでコードを検証して、コードが構文的に正しく、正常にコンパイ ルされるかどうかを確認します。コードの入力ミスなど、間違いはどうしても起こります。コードに間違 いがあると、[Problems (問題)] ペインにエラーが表示され、感嘆符がペインヘッダーの横に追加されます ([Problems! (問題!)])。 10 第 1 章: 基礎 クラスメソッドのコール • エラーのリストを表示するには、[Problems (問題)] パネルを展開します。エラーをクリックすると、エラー が発生したコード行に移動します。たとえば、System.debug ステートメントの最後の閉じ括弧を付け忘 れると、次のようにエラーが表示されます。 閉じ括弧を再度追加すると、エラーが消えます。 クラスメソッドのコール HelloWorld クラスを作成できたので、次の手順に従ってそのメソッドをコールします。 1. 開発者コンソールの [Execute Anonymous Window (実行匿名ウィンドウ)] で次のコードを実行して HelloWorld クラスの静的メソッドをコールします (この手順を忘れた場合は、開発者コンソールの有効化を参照してく ださい)。入力パネルに前のコードが残っている場合は、削除します。静的メソッドをコールする場合、ク ラスのインスタンスを作成する必要はありません。 HelloWorld.sayYou(); 2. 結果のログを開きます。 3. USER_DEBUG イベントを表示するように検索条件を設定します (開発者コンソールの有効化 でも説明され ています)。ログには「You」が表示されます。 4. 次のコードを実行して HelloWorld クラスのインスタンスメソッドをコールします。インスタンスメソッ ドをコールする場合、最初に HelloWorld クラスのインスタンスを作成する必要があります。 HelloWorld hw = new HelloWorld(); hw.sayMe(); 11 第 1 章: 基礎 Salesforce ユーザインターフェースを使用した Apex ク ラスの作成 5. 結果のログを開いて検索条件を設定します。 [Details (詳細)] 列に「Me」が表示されます。このコードは HelloWorld クラスのインスタンスを作成し、そ のインスタンスを hw という変数に割り当てます。次にそのインスタンス上の sayMe() メソッドをコール します。 6. 両方のログの検索条件をクリアして、2 つの実行ログを比較します。最も明らかな違いは、HelloWorld インスタンスの作成と変数 hw への割り当てに関する部分です。それ以外にも違いがあるか探してみてく ださい。 これで、Force.com プラットフォームで新しいコードを作成して実行できました。 Salesforce ユーザインターフェースを使用した Apex クラスの作成 Apex クラスは、Salesforce ユーザインターフェースで作成することもできます。 1. [設定] で、[開発] > [Apex クラス] をクリックします。 2. [新規] をクリックします。 3. エディタペインに、次のコードを入力します。 public class MessageMaker { } 4. [Quick Save (適用)] をクリックします。代わりに [Save (保存)] をクリックすることもできますが、その場合は クラスエディタが閉じて、[Apex クラス] リストに戻ります。[Quick Save (適用)] をクリックすると、Apex コー ドが保存されて実行可能になりますが、そのまま編集を続行することもでき、コードの追加や変更がしや すくなります。 5. 次のコードをクラスに追加します。 public static string helloMessage() { return('You say "Goodbye," I say "Hello"'); } 6. [Save (保存)] をクリックします。 作成したクラスを開発者コンソールに表示して編集することもできます。 12 第 1 章: 基礎 まとめ 1. 開発者コンソールで、[ファイル] > [Open (開く)] をクリックします。 2. [Setup Entity Type (設定エンティティ種別)] パネルで、[クラス] をクリックし、[Entities (エンティティ)] パネル の [MessageMaker] をダブルクリックします。 ソースコードエディタに MessageMaker クラスが表示されます。エディタ内に直接コードを入力して編集 し、クラスを保存できます。 まとめ このチュートリアルでは、Apexクラスを作成してそのリストを表示する方法を学習しました。作成したクラス とメソッドは、開発者コンソールから、また、記述した他のクラスとコードからコールできます。 もうひとこと... • また、Apex コードの作成と実行に、Force.com IDE を使用することができます。詳細は、サイト (Developer Force サイト: https://developer.salesforce.com/) で「Force.com IDE」を検索してください。 13 第 2 章: Apex 言語の基本 前提条件: • この章のチュートリアルでは、コードスニペットの実行に開発者コンソールを使用します。開発者コンソー ルの使用方法を学習するには、「開発者コンソールの使用」を完了してください。 • 「sObject とデータベース」のサンプルは、Warehouse カスタムオブジェクトとサンプルデータに基づいてい ます。これらを作成するには、「Warehouse カスタムオブジェクトの作成」と「サンプルデータの作成」を 完了してください。 第 1 章では、Apexを作成して実行する方法を学習しました。この章では、基本的なApex構文、データ型、デー タベースインテグレーション、および Apex ベースのアプリケーションロジックを作成できるようにするその 他の機能の多くを学習します。 ヒント: Java に習熟している場合は、Apex と Java は非常に似ているため、第 2 章は目を通すだけか、場合 によっては省略してもよいでしょう。その場合でも、「sObject とデータベース」には Apex 固有の内容が 含まれるため、「第 3 章: コンテキストでの Apex」に進む前に必要に応じて確認してください。 この章に含まれるチュートリアルとその簡単な説明は、次のとおりです。 • プリミティブデータ型と変数: プリミティブデータ型を取り上げ、これらの型の変数の作成および使用方法 を学習します。 • コメント、大文字と小文字の区別、コレクション、ループ: クラス内にコレクションやループを作成した り、コメントを追加したりするための基本的な方法を学習します。このチュートリアルでは、大文字と小 文字の区別についても説明します。 • クラス、インターフェース、プロパティ: 基本的なクラスとインターフェースの作成方法について学習しま す。 • sObject とデータベース: データベースのオブジェクトを表す新しい型の概要と、これらのオブジェクトの操 作方法を学習します。 • 例外処理: 問題が発生した場合に対応するためのコーディング方法について学習します。 このように、この章では Apex ベースのロジックを構築するうえで必要になる基本的な Apex の作成方法を学習 します。第 3 章では、このロジックを、データベーストリガ、単体テスト、スケジューラ、Apex 一括処理、 REST Web サービス、および Visualforce からコールする方法を取り上げます。 プリミティブデータ型と変数 Apexには多くのプリミティブデータ型があります。データは、このいずれかの型と一致する変数に格納されま す。このチュートリアルでは、多くの使用可能な型の概要と値の操作方法について説明します。このチュート リアルの例は、すべて開発者コンソールを使用して実行します。 このチュートリアルでは、次のデータ型と変数を取り上げます。 • String: 単一引用符で囲まれた文字のセット (文字列) です。名前や住所など、テキスト値が格納されます。 • Boolean: true 値または false 値を保持し、特定の条件が真 (true) か偽 (false)かを検証するために使用できます。 14 第 2 章: Apex 言語の基本 String • Time、Date、Datetime: このいずれかのデータ型で宣言された変数には、時刻、日付、または日付と時刻の結 合値が保持されます。 • Integer、Long、Double、Decimal: このいずれかのデータ型で宣言された変数には数値が保持されます。 • null 変数: 値が割り当てられていない変数です。 • Enum: 定数値の列挙です。 String 名前や住所など、テキスト値を格納する必要がある場合は、String データ型を使用します。String (文字列) は、 単一引用符で囲まれた文字のセットです。たとえば、「I am a string」などです。次のコードを実行すると、文 字列を作成し、変数に割り当てることができます。 String myVariable = 'I am a string.'; 上の例では、変数 myVariable で表される String クラスのインスタンスを作成し、単一引用符で囲まれた 文字列値を割り当てます。 String 静的メソッド valueOf() を使用すると、日付など、他の型の値から文字列を作成することもできます。 次のコードを実行します。 Date myDate = Date.today(); String myString = String.valueOf(myDate); System.debug(myString); この例の出力は、今日の日付になります。たとえば、2012-03-15 です。実行日に応じた日付が表示されま す。 + 演算子は、文字列に適用すると連結演算子として機能します。次のコードでは、1 つの文字列が作成されま す。 System.debug( 'I am a string' + ' cheese'); == および != 演算子は、大文字と小文字を区別しない比較演算として機能します。次のコードを実行すると、 両方の比較が true を返すことを確認できます。 String x = 'I am a string'; String y = 'I AM A STRING'; String z = 'Hello!'; System.debug (x == y); System.debug (x != z); String クラスには、文字列の操作や問い合わせに使用できる多くのインスタンスメソッドがあります。次のコー ドを実行します。 String x = 'The !shorn! sheep !sprang!.'; System.debug (x.endsWith('.')); System.debug (x.length()); System.debug (x.substring(5,10)); System.debug (x.replaceAll ('!(.*?)!', '$1')); 出力は次のようになります。 15 第 2 章: Apex 言語の基本 String 各メソッドの処理を見ていきましょう。 • endsWith メソッドは、文字列の末尾が引数に含まれる文字列と同じであるため、true を返します。 • length メソッドは、文字列の長さを返します。 • substring メソッドは、文字列のうち、1 つ目の索引引数 (0 からカウント開始) で指定された文字から、2 つ目の索引引数で指定された文字までを使って新しい文字列を作成します。 • replaceAll メソッドは、正規表現に一致する各サブ文字列を指定された置換値で置き換えます。この場 合、感嘆符で囲んだテキストを照合し、一致した部分のテキストを文字列 ($1) で置き換えます。 ヒント: 開発者コンソールでログ出力を絞り込み、「USER_DEBUG」を含む行のみを表示することができま す。この手順は、「チュートリアル 2: レッスン 2」を参照してください。この方法で、すべてのログ出力 を参照することなく、前述の例の debug ステートメントのみを表示できます。 さらに、開発者コンソールでシステムのログレベルを [Info (情報)] に設定して、システムメソッドの ログを除外することもできます。ログレベルにアクセスするには、[Log Levels (ログレベル)] をクリック し、[System (システム)] を [Info (情報)] に設定します。 16 第 2 章: Apex 言語の基本 Boolean と条件付きステートメント 今後のレッスンでは、値を表示するために検索条件を使用するように指示されたり、さらには System.debug を使用するように指示されたりすることはありません。適宜実行してください。 Boolean と条件付きステートメント 変数が true または false の値を持つ必要がある場合は、変数を Boolean データ型で宣言します。Boolean 値 は、すでに前のレッスンで戻り値として使用されていました。endsWith メソッドは Boolean 値を返し、== お よび != String 演算子は文字列比較の結果に基づいて Boolean 値を返します。単純に変数を作成して値を割り当 てることもできます。 Boolean isLeapYear = true; Boolean には、複数の標準演算子があります。否定演算子 ! は、引数が false の場合に true を返します (そ の逆もあります)。&& 演算子は論理 AND を返し、|| 演算子は論理 OR を返します。たとえば、次のステート メントはすべて false と評価されます。 Boolean iAmFalse = !true; Boolean iAmFalse2 = iAmFalse && true; Boolean iAmFalse3 = iAmFalse || false; Boolean の値に応じて、条件を満たしたらロジックを実行するようにするには、if ステートメントを使用しま す。 Boolean isLeapYear = true; if (isLeapYear) { System.debug ('It\'s a leap year!'); } else { System.debug ('Not a leap year.'); } 17 第 2 章: Apex 言語の基本 Time、Date、Datetime エスケープシーケンス: 上の例では、最初の System.debug ステートメントの引数内にバックスラッシュ (\) 文字が含まれています ('It\'s a leap year!')。これは、文章に単一引用符が含まれているためです。Apex では単一引用符に String 値を囲むという特殊な意味があるため、String 値内に単一引用符を使用するには、各単 一引用符の直前にバックスラッシュ (\) 文字を追加してエスケープする必要があります。このようにすると、 Apexは、単一引用符文字を文字列の終了マーカーとしてではなく、文字列内の文字値として扱うように認識し ます。Apexでは、単一引用符のエスケープシーケンスのような、文字列内の特殊文字を表すエスケープシーケ ンスがその他にも提供されています。\b (バックスペース)、\t (タブ)、\n (改行)、\f (フォームフィード)、\r (行頭 復帰)、\" (二重引用符)、\' (単一引用符)、\\ (バックスラッシュ) です。 上の例では、else 部分は省略可能です。ブロック (中括弧内のステートメント) には、条件を満たしたときに 実行される任意の数のステートメントを含めることができます。たとえば、次のコードは 2 つの debug ステー トメントの出力を生成します。 if ('Hello'.endsWith('o')) { System.debug('me'); System.debug('me too!'); } ブロックに含まれるステートメントが 1 つのみの場合、中括弧を省略することもできます。たとえば、次のよ うになります。 if (4 > 2) System.debug ('Yep, 4 is greater than 2'); 上記 2 つに加え、if-then-else ステートメントの省略形として機能する条件付き 3 項演算もあります。構文は次の ようになります。 x ? y : z これは「if 条件 (Boolean 型の x = true), then 結果 = y; else 結果 = z」と読めます。次のコードを実行します。 Boolean isIt = true; String x = 'You are ' + (isIt ? System.debug(x); 'great' : 'small'); 結果の文字列は「You are great」という値になります。 Time、Date、Datetime 日付と時刻に関連付けられたデータ型は 3 つあります。Time データ型には時刻 (時、分、秒、ミリ秒) が格納さ れます。Date データ型には日付 (年、月、日) が格納されます。Datetime データ型には、日付と時刻の両方が格 納されます。 これらの各クラスには、newInstance メソッドが含まれ、特定の日時値の作成に使用できます。たとえば、 次のコードを実行した場合、 Date myDate = Date.newinstance(1960, 2, 17); Time myTime = Time.newInstance(18, 30, 2, 20); System.debug(myDate); System.debug(myTime); 出力は次のようになります。 1960-02-17 00:00:00 18 第 2 章: Apex 言語の基本 Integer、Long、Double、Decimal 18:30:02.020Z Date データ型では、時刻は保持されず、デフォルトで 0 に設定されます。 現在のクロックから次のように日付と時刻を作成することもできます。 Datetime myDateTime = Datetime.now(); Date today = Date.today(); Date および Time クラスには、形式を変換するインスタンスメソッドもあります。たとえば、次のようになり ます。 Time t = DateTime.now().time(); さらに、さまざまなインスタンスメソッドを使用して値の操作や問い合わせもできます。たとえば、Datetime には、addHours、addMinutes、dayOfYear、timeGMT など多くのメソッドがあります。次のコードを実行 します。 Date myToday = Date.today(); Date myNext30 = myToday.addDays(30); System.debug('myToday = ' + myToday); System.debug('myNext30= ' + myNext30); 次のような出力が得られます。 2012-02-09 00:00:00 2011-03-10 00:00:00 Integer、Long、Double、Decimal 変数に数値を格納するには、変数を Integer、Long、Double、Decimal のいずれかの数値データ型で宣言します。 Integer 小数点を含まない 32 ビットの数値。Integer の最小値は -2,147,483,648、最大値は 2,147,483,647 です。 Long 小数点を含まない 64 ビットの数値。Long の最小値は -263、最大値は 263-1 です。 Double 小数点を含む 64 ビットの数値。Double の最小値は -263、最大値は 263-1 です。 Decimal 小数点を含む数値。Decimal は、任意の精度数です。通貨項目には自動的に Decimal 型が割り当てられます。 次のコードを実行すると、各数値型の変数が作成されます。 Integer i = 1; Long l = 2147483648L; Double d = 3.14159; Decimal dec = 19.23; valueOf 静的メソッドを使用して、文字列を数値型にキャストできます。たとえば、次のコードは文字列 「10」から Integer 値を作成し、20 を加算します。 Integer countMe = Integer.valueof('10') + 20; 19 第 2 章: Apex 言語の基本 null 変数 Decimal クラスには、値の問い合わせや操作を行う数多くのインスタンスメソッドがあります。これには、指 定された丸め動作を使用して適切な精度を維持する一連のメソッドなどが含まれます。scale メソッドは、 小数部の桁数を返し、divide のようなメソッドでは、商の小数部の桁数を指定して除算を実行します。次の コードを実行する場合、divide への最初の引数が序数、2 つ目の引数が小数部の桁数です。 Decimal decBefore = 19.23; Decimal decAfter = decBefore.Divide(100, 3); System.debug(decAfter); decAfter の値は 0.192 になります。 null 変数 変数は、宣言した後に値で初期化しないと null になります。null とは値がないことを意味します。null は、プリミティブ型で宣言された任意の変数に割り当てることもできます。たとえば、次のどちらのステート メントでも、変数は null に設定されます。 Boolean x = null; Decimal d; データ型に対するインスタンスメソッドの多くは、変数が null だと失敗します。次の例では、2 つ目のステー トメントがコンパイルエラーを生成します。 Decimal d; d.addDays(2); この結果、エラー「line 2, column 1: Method does not exist or incorrect signature: [Decimal].addDays(Integer)」になります。 例外と例外の処理方法についての詳細は、「例外処理」を参照してください。 enum 定数のセットを指定するには、列挙 (Enum) を使用します。新しい列挙を定義するには、enum キーワードの後 に中括弧で囲んだ識別子のリストを使用します。列挙内の各値は、開始値 0 で左から右へ 1 ずつ増加する整数 値に対応します。各値が定数に対応するため、識別子は大文字になります。たとえば、次の例では 4 つの季節 を包含する Season という列挙を定義します。 public enum Season {WINTER, SPRING, SUMMER, FALL} 上の例では、WINTER の整数値は 0、SPRING は 1、SUMMER は 2、FALL は 3 です。列挙を定義したら、新しい Enum 型をデータ型として使用して変数を宣言できます。次の例では、Season Enum 型を最初に定義し、それ を使用して Season 型の変数 s を作成します。次に、s 変数の値をチェックして、値に基づいて異なるデバッ グ出力を書き出します。次のコードを実行します。 public enum Season {WINTER, SPRING, SUMMER, FALL} Season s = Season.SUMMER; if (s == Season.SUMMER) { // Will write the string value SUMMER System.debug(s); } else { 20 第 2 章: Apex 言語の基本 まとめ System.debug('Not summer.'); } デバッグ出力では SUMMER と表示されます。 Apex では、列挙を独自の用途向けに作成できるだけでなく、組み込みの列挙もあります。その一例が System.LoggingLevel です。これは、System.debug メソッドのデバッグ出力のログレベル指定に使用し ます。 Java と異なり、Enum 型にはコンストラクタ構文はありません。 まとめ このチュートリアルでは、さまざまなプリミティブデータ型 (String 型、Boolean 型、Date 型) と、条件ステート メントの記述方法について学習しました。また、null 変数についても学習しました。 もうひとこと... Apex では、他にも特定の種別のデータを保持するためのデータ型が提供されています。 ID ID データ型は、18 文字のオブジェクト識別子を表します。Force.com は、オブジェクトがデータベースに挿 入されるとオブジェクトに ID を設定します。たとえば、ID 値は「a02D0000006YLCyIAO」などになります。 Blob Blob データ型は、1 つのオブジェクトとして保存されたバイナリデータを表します。Blob データの例とし て、メールメッセージへの添付ファイルやドキュメントの本文などがあります。Blob は、Web サービス引 数として受け入れられます。toString メソッドを使用して Blob データ型を String 型に変換したり、valueOf メソッドを使用して String 型から Blob データ型に変換したりできます。Blob データ型は、Crypto クラスメ ソッドの引数種別として使用されます。 コメント、大文字と小文字の区別、コレクション、ループ ここまでのチュートリアルでは、変数の宣言、条件付きステートメント、および割り当てステートメントを見 てきました。このチュートリアルでは、Apex構文の機能について説明します。このチュートリアルの例は、す べて開発者コンソールを使用して実行します。 このチュートリアルでは次の内容を取り上げます。 • コメント: コードに追加して処理を説明するテキスト行です。 • 大文字と小文字の区別: Apex では、大文字と小文字は区別されません。 • コレクション: Apex では、さまざまな種類のコレクション (配列、リスト、セット、マップ) がサポートされ ます。 • ループ: Apex では、コードの繰り返し実行に do-while ループ、while ループ、for ループを使用できます。 21 第 2 章: Apex 言語の基本 コメント コメント コメントは、コードに追加して処理を説明するテキスト行です。コメントは実行可能なコードではありませ ん。必要に応じて、コードにコメントで注釈を付けることをお勧めします。これにより、コードが理解しやす く、また保守しやすくなります。Apex のコメントには 2 つの形式があります。1 つは、// トークンを使用し、 その行でトークンより右側にあるすべてをコメントとしてマークする方法です。もう 1 つは、複数行にまたが る場合など、テキストのブロックを /* トークンと */ トークンで囲む方法です。 次のコードを実行します。debug ステートメントのみが実行されます。 System.debug ('I will execute'); // This comment is ignored. /* I am a large comment, completely ignored as well. */ 大文字と小文字の区別 Java とは異なり、Apexは大文字と小文字を区別しません。つまり、すべてのApexコードは、メソッド名、クラ ス名、変数名、キーワードを含め、大文字と小文字を区別せずに記述できます。たとえば、Integer myVar; と integeR MYVAR; は等しいステートメントです。次のステートメントを開発者コンソールで実行すると、 どのステートメントも System.today メソッドを使用して今日の日付を出力します。 System.debug ( System.today() ); System.debug ( System.Today() ); System.debug ( SySteM.Today() ); 実際には、クラス名の先頭を大文字、メソッド名の先頭を小文字にすることをお勧めします。 配列とリスト Apexには、順序付けたオブジェクトのコレクションを保持するリストコレクション型があります。リスト要素 は、索引を使用してアクセスでき、また Integer や String などのプリミティブ型である場合は並び替えができま す。リストは通常、索引を使用してアクセス可能な値のセットを格納する場合には常に使用されます。後の チュートリアルで説明するように、リストはクエリの結果を保持する場合にも使用されます。 リストの要素にアクセスするには、開始値 0 の索引を使用するか、リストを反復処理します。 次のコードは、 新しいリストを作成して、そのサイズを表示します。 List<Integer> myList = new List<Integer>(); System.debug(myList.size()); Apex では、配列とリストは同じです。Apex には、リストにアクセスするための配列のような構文が用意され ています。次のコードでは、別の方法でまったく同じリストを作成します。 Integer[] myList = new List<Integer>(); 次の例のように、List 変数を定義して同時に初期化することもできます。この例は文字列「two」を表示しま す。 List<String> myStrings = new List<String> { 'one', 'two' }; 22 第 2 章: Apex 言語の基本 配列とリスト リストに新しい要素を追加するには、add メソッドを使用します。 myList.add(101); 配列表記を使用して既存の値を取得または変更できます。 // Get the first element Integer i = myList[0]; // Modify the value of the first element myList[0] = 100; 実際に試してみる 次のスニペットはリストを作成し、リストに整数値を追加します。索引 0 に対応する最初の要素を取得し、デ バッグ出力に書き出します。この例では、配列表記 (索引を角括弧で囲んで指定) と、get メソッドの両方を使 用してリストの最初の要素を取得しています。 Integer[] myList = new List<Integer>(); //Adds a new element with value 10 to the end of the list myList.add(10); // Retrieve the first element of the list // using array notation Integer i = myList[0]; // or using the get method Integer j = myList.get(0); System.debug('First element in the array using myList[0] is ' + i); System.debug('First element in the array using myList.get(0) is ' + j); 次に、開発者コンソールでこのスニペットを実行した場合の出力の一部を示します。 次のスニペットは、リストを作成し、リストに整数値を追加します。第 1 要素の値を変更して、その値をデ バッグ出力に書き出します。最後に、リストのサイズをデバッグ出力に書き出します。この例では、配列表記 (索引を角括弧で囲んで指定) と、set メソッドの両方を使用してリストの最初の要素を変更しています。 Integer[] myList = new List<Integer>{10, 20}; // Modify the value of the first element // using the array notation myList[0] = 15; // or using the set method myList.set(0,15); System.debug ('Updated value:' + myList[0]); // Return the size of the list System.debug ('List size: ' + myList.size()); 次に、開発者コンソールでこのスニペットを実行した場合の出力の一部を示します。 23 第 2 章: Apex 言語の基本 ループ ループ ある条件が true の間、コードのブロックを繰り返し実行するには、ループを使用します。Apex では、do-while ループ、while ループ、および for ループがサポートされます。 While ループ do-while ループは、while ステートメントで指定した Boolean 条件が true である限り、コードのブロックを繰り 返し実行します。次のコードを実行します。 Integer count = 1; do { System.debug(count); count++; } while (count < 11); 上の例では、do-while ブロック内に含まれるステートメントを 10 回実行して 1 から 10 までの数値をデバッグ出 力に書き出します。 while ループは、開始部分に指定した Boolean 条件が true である限り、コードのブロックを繰り返し実行します。 次のコードを実行すると、同様に数値 1 から 10 を出力します。 Integer count = 1; while (count < 11) { System.debug(count); count++; } 24 第 2 章: Apex 言語の基本 Set と Map For ループ for ループには 3 つの種類があります。1 つ目の種類の for ループは、変数をある値に設定し、条件をチェックし て、変数に対して何らかのアクションを実行することで反復処理する従来のループです。次のコードを実行す ると、1 から 10 の数値を出力に書き出します。 for (Integer i = 1; i <= 10; i++){ System.debug(i); } 2 つ目の種類の for ループは、リストまたはセットに対する反復処理に使用できます。次のコードを実行しま す。 Integer[] myInts = new Integer[]{10,20,30,40,50,60,70,80,90,100}; for (Integer i: myInts) { System.debug(i); } 上の例では、リスト内の各整数を反復処理し、整数を出力に書き出します。 3 つ目の種類の for ループについては、「チュートリアル 8: レッスン 4」で説明します。 Set と Map Apex では、リスト以外に 2 つのコレクション型、Set と Map をサポートします。 Set Set (セット) は、重複する要素を含まないオブジェクトの順序付けされていないコレクションです。コレクショ ン内での要素の順序を追跡する必要がない場合、および要素が一意で並び替えが不要な場合は、Set を使用し ます。 次の例では、新しい Set を作成して初期化し、要素を追加して、Set に文字列「b」が含まれているかどうかを チェックします。この例は、開発者コンソールで実行できます。 Set<String> s = new Set<String>{'a','b','c'}; // Because c is already a member, nothing will happen. s.add('c'); s.add('d'); 25 第 2 章: Apex 言語の基本 まとめ if (s.contains('b')) { System.debug ('I contain b and have size ' + s.size()); } 例の実行後、次の行が出力に表示されます。 Map Map (マップ) は、キー - 値ペアのコレクションで、キーはプリミティブデータ型です。キーを使用して参照す る値を格納する場合は、Map を使用します。たとえば、Map を使用すると、従業員 ID に対応する住所のリスト を格納できます。次の例では、Map を作成し、項目を Map に追加して、キーである従業員 ID に基づいて対応す る方の項目を取得します。取得された住所は、デバッグ出力に書き出されます。 Map<Integer,String> employeeAddresses = new Map<Integer,String>(); employeeAddresses.put (1, '123 Sunny Drive, San Francisco, CA'); employeeAddresses.put (2, '456 Dark Drive, San Francisco, CA'); System.debug('Address for employeeID 2: ' + employeeAddresses.get(2)); 例の実行後、次の行が出力に表示されます。 Map は、作成時にデータをコレクションに入力するためのショートカット構文もサポートします。次の例で は、2 つのキー - 値ペアを持つ Map を作成します。実行すると、文字列「apple」がデバッグ出力に表示されま す。 Map<String,String> myStrings = new Map<String,String>{'a'=>'apple','b'=>'bee'}; System.debug(myStrings.get('a')); Set と Map には多くの役に立つメソッドが含まれています。たとえば、ある Set のすべての要素を別の Set に追 加するには、Set に対して addAll メソッドを使用します。また、Map に含まれる値のリストを返すには、 values をコールします。 まとめ このチュートリアルでは、コードにコメントを追加する方法を学習しました。さらに、Apex は大文字と小文字 を区別しない言語であることを学習しました。最後に、コレクション (リスト、マップ、セット) とループにつ いて学びました。 クラス、インターフェース、プロパティ Apexは、オブジェクト指向プログラミング言語です。このチュートリアルでは、コールされることがある次の すべての重要なオブジェクトまたはクラスインスタンスに対する Apex のサポートについて説明します。オブ ジェクトは、クラスメソッド、インスタンスメソッド、およびデータ変数を含むデータ構造である、クラスか 26 第 2 章: Apex 言語の基本 クラスの定義 ら作成されます。一方、クラスは、一連のメソッドであるインターフェースを実装できます。このチュートリ アルの例は、すべて開発者コンソールを使用して実行します。 このチュートリアルでは次の内容を取り上げます。 • クラスとオブジェクト: クラスとはオブジェクトを作成する元となるテンプレートです。 • private 修飾子: クラスや、クラスに含まれるクラスメソッドまたはメンバー変数へのアクセスを制限し、他 のクラスから使用できないようにします。 • 静的変数、定数、メソッド: これらは、クラスのインスタンスに依存せず、クラスからオブジェクトを作成 しなくてもアクセスできます。 • インターフェース: 実装を含まないメソッド署名の名前付きセットです。 • プロパティ: プロパティを使用すると、クラスメンバー変数への参照および更新アクセスを制御できます。 クラスの定義 Apexクラスは Java クラスと似ています。クラスは、オブジェクトを作成するためのテンプレート、つまり設計 図です。オブジェクトはクラスのインスタンスです。たとえば、Fridge クラスは、冷蔵庫の状態と、冷蔵庫で できることすべてを規定します。Fridge クラスの 1 つのインスタンスが、購入や販売が可能な特定の冷蔵庫に あたります。 Apexクラスには、変数とメソッドが含まれます。変数は、オブジェクトの名前や種別など、オブジェクトの状 態を指定するために使用されます。これらの変数はクラスに関連付けられており、クラスのメンバーであるた め、メンバー変数と呼ばれます。メソッドは、品目の購入や販売など、動作を制御するために使用されます。 メソッドには、ローカル変数を含めることもできます。ローカル変数はメソッド内で宣言され、そのメソッド のみが使用します。クラスメンバー変数がオブジェクトの属性 (名前や高さなど) を定義するのに対して、メ ソッド内のローカル変数は、メソッドのみが使用し、クラスを規定しません。 このワークブックの第 1 章にある「クラスの作成とインスタンス化」では、新しいクラスの作成方法を説明し ています。同じ手順に従い、次のクラスを作成します。 public class Fridge { public String modelNumber; public Integer numberInStock; public void updateStock(Integer justSold) { numberInStock = numberInStock - justSold; } } これで、Fridge という新しいクラスを定義できました。このクラスには 2 つのメンバー変数 (modelNumber と numberInStock) および 1 つのメソッド (updateStock) が含まれます。void 型は、updateStock メソッ ドが値を返さないことを示します。 これで、この新しいクラス型 Fridge の変数を宣言し、操作できます。開発者コンソールで次のコードを実行 します。 Fridge myFridge = new Fridge(); myFridge.modelNumber = 'MX-O'; myFridge.numberInStock = 100; myFridge.updateStock(20); 27 第 2 章: Apex 言語の基本 private 修飾子 Fridge myOtherFridge = new Fridge(); myOtherFridge.modelNumber = 'MX-Y'; myOtherFridge.numberInStock = 50; System.debug('myFridge.numberInStock=' + myFridge.numberInStock); System.debug('myOtherFridge.numberInStock=' + myOtherFridge.numberInStock); これにより、Fridge クラスの新しいインスタンスが myFridge という名前のオブジェクトとして作成されま す。オブジェクト内の変数の値を設定し、updateStock メソッドをコールして、引数で値 20 を渡します。こ れを実行すると、updateStock インスタンスメソッドが numberInStock 値から引数を減算します。次に、 Fridge クラスのインスタンスをもう 1 つ作成し、在庫数を 50 に設定します。最終的な出力の値として、80 と 50 が表示されます。 private 修飾子 ここまで、クラス、クラスメソッド、およびメンバー変数はすべて、public キーワードを使用して宣言しま した。これは、他の Apex クラスもそのクラス、メソッド、および変数にアクセスできるようにするアクセス 修飾子です。場合によっては、他の Apex クラスからはアクセスできないようにする必要が生じます。このよ うな場合には、private アクセス修飾子でクラス、メソッド、またはメンバー変数を宣言します。 メンバー変数を private として宣言すると、どのメンバー変数を参照または更新可能にするか、また他のクラス でどのように操作されるかを制御できます。これらの private 変数の値を取得して設定するための public メソッ ドを提供できます。これらの getter および setter メソッドはプロパティと呼ばれ、詳細は「プロパティ構文」で 説明します。これらのメソッドを private として宣言するのは、定義されたクラス内でのみコールされ、ヘル パーメソッドである場合です。ヘルパーメソッドは、クラスの動作を表しませんが、ユーティリティ目的で提 供されることがあります。 メモ: デフォルトでは、メソッドや変数は private であり、それらが定義されたクラス内の Apex コードから のみ参照できます。メソッドや変数を他のクラスで使用できるようにするには、明示的に public とし て指定する必要があります。 それでは、Fridge クラスを、メンバー変数に private 修飾子を使用するように変更しましょう。 1. Fridge クラスを変更し、両方の変数の修飾子を private に変更します。 private String modelNumber; private Integer numberInStock; 2. [Quick Save (適用)] をクリックします。 3. 開発者コンソールで次のコードを実行します。 Fridge myFridge = new Fridge(); myFridge.modelNumber = 'MX-EO'; 警告エラー「Variable is not visible: modelNumber」が表示されます。変数 modelNumber は、ク ラス内からのみアクセスできるようになりました。これが推奨される方式です。 28 第 2 章: Apex 言語の基本 コンストラクタ 4. 変数へのアクセス権を提供するには、値の設定用と値の取得用にコールする public メソッドをそれぞれ新 しく定義します。Fridge のクラス本文内に次のコードを追加します。 public void setModelNumber(String theModelNumber) { modelNumber = theModelNumber; } public String getModelNumber() { return modelNumber; } 5. [Quick Save (適用)] をクリックします。 6. 次のコードを実行します。 Fridge myFridge = new Fridge(); myFridge.setModelNumber('MX-EO'); System.debug(myFridge.getModelNumber()); これは適切に実行されます。setModelNumber メソッドをコールすると文字列が渡され、myFridge イン スタンス変数の modelNumber 値に設定されます。getModelNumber メソッドをコールすると、モデル番 号が取得され、それが System.debug システムメソッドに渡されてデバッグ出力に書き出されます。 コンストラクタ Apex は、作成するクラスごとにデフォルトのコンストラクタを提供します。たとえば、ここまでは Fridge コンストラクタを自分で定義していなくても、new Fridge() をコールすれば、Fridge のインスタンスを作 成することができました。ただし、Apex では初期化されていない変数はすべて null であるため、この場合の Fridge インスタンスのメンバー変数はすべて null に設定されます。場合によっては、在庫数量に 0、モデ ル番号に総称番号など、特定の初期値を指定する必要が生じます。この場合、独自のコンストラクタを記述す る必要があります。さらに、パラメータを取るコンストラクタがあると、渡された引数値からメンバー変数を 初期化できるため、便利な場合があります。 では、パラメータなし、ありの 2 つのコンストラクタを追加してみましょう。 1. 次のコードを Fridge クラスに追加します。 public Fridge() { modelNumber = 'XX-XX'; numberInStock = 0; } public Fridge(String theModelNumber, Integer theNumberInStock) { modelNumber = theModelNumber; numberInStock = theNumberInStock; } コンストラクタは、クラス自体と同じ名前であること、戻り値がないことを除き、メソッドと似ています。 2. これで、インスタンスを作成し、追加した 2 つ目のコンストラクタを使用してデフォルト値を一括設定で きます。次のコードを実行します。 Fridge myFridge = new Fridge('MX-EO', 100); System.debug (myFridge.getModelNumber()); 29 第 2 章: Apex 言語の基本 静的変数、定数、メソッド これにより「MX-EO」と出力されます。多くの場合、クラスにはオブジェクト作成に役立つさまざまなコン ストラクタがあります。 静的変数、定数、メソッド これまでに作成した変数とメソッドは、インスタンスメソッドとインスタンス変数です。つまり、modelNumber 変数と numberInStock 変数を使用するには、最初にクラスのインスタンスを作成する必要があります。個々 のインスタンスには、インスタンス変数の独自のコピーがあり、インスタンスメソッドはこれらの変数にアク セスできます。場合によっては、メンバー変数の値をすべてのインスタンスから使用できるようにする必要が あります。たとえば、在庫のしきい値変数の値が Fridge クラスのすべてのインスタンスと共有されていて、 あるインスタンスが行った更新を他のすべてのインスタンスから参照できるようにする場合などです。この場 合、静的変数を作成する必要があります。静的変数は、インスタンスではなくクラスに関連付けられ、クラス をインスタンス化せずにアクセスできます。 インスタンスではなく、クラスに関連付けられた静的メソッドを定義することもできます。通常、インスタン スの状態に依存しないユーティリティメソッドが静的にするのに適しています。 1. 次の静的変数を追加して Fridge クラスを変更します。 public static Integer stockThreshold = 5; 2. 開発者コンソールで次のコードを実行します。 System.debug ( Fridge.stockThreshold ); 5 が出力されます。ここでは new 演算子を使用して Fridge クラスのインスタンスを作成する必要はあり ませんでした。クラスの変数にアクセスしただけです。 3. クラス名を使用してこの静的変数にアクセスし、その値を変更することもできます。 // Modify the static stock threshold value Fridge.stockThreshold = 4; System.debug ( Fridge.stockThreshold ); 出力に 4 が書き出されます。 4. 変更できない定数として変数を宣言する場合があります。Apexでこれを行うには final キーワードを使用 します。これは、変数への値の割り当てが 1 回限りであることを示します。宣言した静的変数を次のよう に変更します。 public static final Integer STOCK_THRESHOLD = 5; 引き続き項目の値を出力できます。たとえば、Fridge.STOCK_THRESHOLD; は機能しますが、その他の値 を項目に割り当てることはできません。たとえば、Fridge.STOCK_THRESHOLD = 3; は機能しません。 5. では、引数で渡されたオブジェクトの値を出力する静的クラスメソッドを定義してみましょう。これは、 デバッグに非常に役立ちます。次のように新しいメソッドを Fridge クラスに追加します。 public static void toDebug(Fridge aFridge) { System.debug ('ModelNumber = ' + aFridge.modelNumber); System.debug ('Number in Stock = ' + aFridge.numberInStock); } 30 第 2 章: Apex 言語の基本 インターフェース 6. この新しいメソッドをテストするには、開発者コンソールでメソッドをコールし、Fridge インスタンス を渡します。 Fridge myFridge = new Fridge('MX-Y', 200); Fridge.toDebug(myFridge); 開発者コンソールには次のように出力されます。 これで、作成したオブジェクトのダンプを簡単に開発者コンソールに表示できるようになりました。 インターフェース インターフェースとは、実装のない、メソッド署名 (戻り値とパラメータの定義) の名前付きセットです。。イ ンターフェースにより、コードで抽象化レイヤを使用できます。インターフェースは、メソッドの特定の実装 をメソッドの宣言から切り離します。これにより、1 つのメソッドを特定のアプリケーションに基づいて別々 に実装できます。たとえば、冷蔵庫とトースターはどちらも台所用品の種類です。すべての台所用品にはモデ ル番号があり、対応するインターフェースは getModelNumber メソッドを持つことができます。ただし、モ デル番号の形式は台所用品の種類によって異なります。Fridge クラスと Toaster クラスは、それぞれ異な る形式のモデル番号を返すようにこのメソッドを実装できます。 インターフェースでは、ある種の決まり事が規定されるため便利です。クラスでインターフェースが実装され ていると、そのインターフェースのメソッドがクラスに表示されることが保証されます。複数の異なるクラス が同じインターフェースを実装できます。 Fridge クラスと Toaster クラスによって実装されるインターフェースを作成して試してみましょう。 1. クラスを作成したときと同じ方法でインターフェースを作成します。 public interface KitchenUtility { String getModelNumber(); } 2. Fridge クラスを変更してこのインターフェースを実装します。太字の語を最初の行にあるクラスの定義 に追加するだけです。 public class Fridge implements KitchenUtility { 3. Toaster という新しいクラスを定義して、同じ KitchenUtility インターフェースを実装します。 public class Toaster implements KitchenUtility { private String modelNumber; public String getModelNumber() { return 'T' + modelNumber; } 31 第 2 章: Apex 言語の基本 プロパティ構文 } Toaster クラスと Fridge クラスは同じインターフェースを実装したため、どちらにも getModelNumber メソッドがあります。Toaster または Fridge のインスタンスを KitchenUtility として扱うことがで きるようになりました。 4. 次の例では Toaster および Fridge のインスタンスを作成します。続いて、これら 2 つのオブジェクトを 使用して KitchenUtility インスタンスとして扱うことで、KitchenUtility オブジェクトの配列を作 成します。 Fridge f = new Fridge('MX', 200); Toaster t = new Toaster(); KitchenUtility [] utilities = new KitchenUtility[] { f, t }; String model = utilities[0].getModelNumber(); System.debug(model); プロパティ構文 「 private 修飾子」では、変数を private に変更し、メソッドを介してのみアクセスできるようにしました。これ は Apex クラスを開発する場合の一般的なパターンです。また、変数と、その変数がアクセスまたは取得され ると実行されるコードを定義できる短縮構文もがあります。 1. 次のコードを追加して、新しいプロパティ ecoRating を Fridge クラスに追加します。 public Integer ecoRating { get { return ecoRating; } set { ecoRating = value; if (ecoRating < 0) ecoRating =0; } } これは、変数 ecoRating、および値が取得されると実行されるコード (get ブロックのコード) と値が設定 されると実行されるコード (set ブロックのコード) を作成するものです。value という名前の自動変数を使 用できるようになるため、設定されている値を確認できます。この場合、プロパティ setter は、マイナスの ecoRating があるかチェックし、マイナスの値を 0 に調整します。 2. 次のコードを実行して、マイナスの評価が 0 に変換されたことを確認します。 Fridge myFridge = new Fridge('E', 10); myFridge.ecoRating = -5; // calls the setter System.debug (myFridge.ecoRating); // calls the getter 0 が出力されます。 まとめ このチュートリアルでは、クラスを定義およびインスタンス化する方法と、public および private メンバー変数、 定数、コンストラクタ、およびメソッドをクラスに追加する方法を学習しました。また、インターフェースと プロパティについても学習しました。 32 第 2 章: Apex 言語の基本 sObject とデータベース もうひとこと... その他のリソースとして次のようなものがあります。 サブクラス Apexではサブクラスがサポートされるため、別のクラスを拡張するクラスを作成できます。サブクラスは、 親クラスのすべての機能を継承します。追加のメソッドやメンバー変数を持つこともでき、既存の親クラ スメソッドの動作を上書きすることもできます。 静的メソッドとインスタンスメソッド 静的メソッドは、static キーワードで宣言されるメソッドです。一般にユーティリティメソッドとして 役立ち、特定のインスタンスメンバー変数の値には依存しません。静的メソッドはクラスにしか関連付け できないため、そのクラスのインスタンスメンバー変数の値にはアクセスできません。静的変数は、要求 の範囲内のみで静的です。サーバ全体、または全体組織においては、静的ではありません。 インスタンスメソッドとメンバー変数は、クラスのインスタンス、すなわちオブジェクトによって使用さ れます。インスタンスメンバー変数は、メソッド内ではなく、クラス内で宣言されます。インスタンスメ ソッドは通常、メソッドの動作に影響を与えるためにインスタンスメンバー変数を使用します。 実行中のコードのセキュリティ 開発者コンソールの実行匿名ウィンドウで実行されるコードスニペットとは異なり、クラス (およびトリガ) 内の Apex コードはシステムコンテキストで実行されます。オブジェクトレベルおよび項目レベルのセキュ リティ設定は適用されません。つまり、Apex クラスは組織のすべてのデータにアクセスできます。不注意 でデータを削除したり、機密データを公開したりしないようにしてください。権限が大きいほど、責任も 大きくなります。クラスを with sharing キーワードで宣言することで、現在ログインしているユーザの 共有権限を適用できます。トリガについての詳細は、「トリガを使用したカスタムビジネスロジックの追 加」を参照してください。 詳細は、Developer Force ドキュメントサイト (https://developer.salesforce.com/docs) の『Force.com Apex コード開発者ガ イド』を参照してください。 sObject とデータベース Apex は、Force.com の永続レイヤであるデータベースと緊密に統合されています。このチュートリアルでは、 Apex を使用して sObject と呼ばれるデータベースオブジェクトを作成、保持、および更新する方法と、データ ベースをクエリして結果を反復処理する方法を学習します。このチュートリアルの例は、すべて開発者コン ソールを使用して実行します。 sObject とは? sObject とは、Force.comプラットフォームデータベースに保存できるオブジェクトを指します。Apexクラスのイ ンスタンスという意味でのオブジェクトではなく、すでに保持されているか、これから保持されるデータを表 します。 これらの永続オブジェクトは、Apex言語内では最優先に処理されるため、データベースの連携処理をきわめて 直観的かつ簡単に行うことができます。 sObject は、永続オブジェクト型に対応する汎用抽象型です。汎用 sObject は、Account オブジェクトや Invoice_Statement__c カスタムオブジェクトなど、特定の sObject 型にキャストできます。 33 第 2 章: Apex 言語の基本 SOQL および SOSL クエリ これにより、項目を設定せずに Invoice_Statement__c カスタムオブジェクトに対応する請求書明細が作成され、 新しい請求書明細が sObject に割り当てられます。 sObject s = new Invoice_Statement__c(); 2 つ目の例では、Description__c 項目と Status__c 項目に初期値が設定された請求書明細が作成され、やはり sObject 型である Invoice_Statement__c 型の変数に割り当てられます。 Invoice_Statement__c inv = new Invoice_Statement__c(Description__c='Test Invoice', Status__c='Pending'); 次の例は、sObject 変数を別の sObject 型にキャストする方法を示します。mySObjectVar 変数を Invoice_Statement__c sObject 型にキャストします。 Invoice_Statement__c inv = (Invoice_Statement__c)mySObjectVar; 新しい sObject レコードを正常に挿入するには、その前にすべての必須項目を設定する必要があります。「Apex データ操作言語」では、特にデータ操作言語 (DML) を使用した新しいレコードの挿入方法を説明します。 sObject の項目は、コンストラクタの引数として渡すか、sObject 型の作成後にドット表記を使用して設定できま す。次の例は、ドット表記を使用して請求書明細の Description__c 項目に文字列値を設定する方法を示します。 inv.Description__c = 'Test Invoice'; ドット表記を使用して項目値を読み取ることもできます。 ID id = inv.Id; String x = inv.Name; sObject を作成し、項目を設定してから読み取ってみましょう。次のコードを実行します。 Invoice_Statement__c inv = new Invoice_Statement__c(); inv.Description__c = 'Large invoice'; System.debug('Invoice Description: ' + inv.Description__c); 上のスニペットでは、「Invoice Description: Large invoice」と出力されます。 SOQL および SOSL クエリ データベースシステムがクエリ言語によるデータ取得をサポートするのと同様に、Force.com 永続レイヤでも 2 つのクエリ言語を提供します。 • Salesforce Object Query Language (SOQL) は、クエリ専用言語です。いくつかの点で SQL と似ていますが、結合で はなくリレーションを使用してデータのより直観的なナビゲーションを可能にするオブジェクトクエリ言 語です。単一の sObject とその関連 sObject のデータ取得に使用される主クエリ言語です。この後に例があり ます。 • Salesforce Object Search Language (SOSL) は、複数の永続オブジェクトすべてを同時に検索する簡易言語です。 SOSL は、Apache Lucene と似ています。 Apex はデータベースと緊密に統合されているため、多くのコードを追加しなくても、Apex で直接クエリを記 述できます。 34 第 2 章: Apex 言語の基本 sObject リレーションのトラバースとクエリ SOQL クエリの例 SOQL クエリは、角括弧で囲みます。次の例では、name 項目の値が「Pencils」と等しい sObject (データベースか らのレコード) を取得します。 sObject s = [SELECT Id, Name FROM Merchandise__c WHERE Name='Pencils']; 次の例では、0 個以上の商品品目があることを前提として、一致する商品品目をすべて取得し、リストに割り 当てます。例のように、SOQL クエリに変数を含めるには、変数の前にコロン (:) を付けます。 String myName = 'Pencils'; Merchandise__c[] ms = [SELECT Id FROM Merchandise__c WHERE Name=:myName]; 次のコードを実行して、最初に一致した商品品目を取得し、その Total_Inventory__c 項目を変数に割り当てます。 Double totalInventory = [SELECT Total_Inventory__c FROM Merchandise__c WHERE Name = 'Pencils'][0].Total_Inventory__c; System.debug('Total inventory: ' + totalInventory); 出力は次のようになります。 Total inventory: 1000.0 SOSL クエリの例 SOSL ステートメントは、sObject リストの一覧に対して評価を行います。各リストには特定の sObject 型の検索 結果が含まれます。次の例では、すべての Merchandise__c sObject と Inventory_Statement__c sObject を対象にすべて の項目を検索します。次のコードを実行します。 List<List<SObject>> searchList = [FIND 'Pencil*' IN ALL FIELDS RETURNING Merchandise__c (Id, Name), Invoice_Statement__c]; Merchandise__c[] merList = ((List<Merchandise__c>)searchList[0]); Invoice_Statement__c[] invList = ((List<Invoice_Statement__c>)searchList[1]); System.debug('Found ' + merList.size() + ' merchandise items.'); System.debug('Found ' + invList.size() + ' invoice statements.'); 次のような出力が得られます。 Found 1 merchandise items. Found 0 invoice statements. sObject リレーションのトラバースとクエリ sObject リレーションとドット表記 2 つの sObject がリレーションを介して互いに関連付けられる場合、一方の sObject の親 sObject を次のドット表 記構文を使用して取得できます。 sObjectTypeName parentObject = objectA.RelationshipName; 35 第 2 章: Apex 言語の基本 sObject リレーションのトラバースとクエリ 親 sObject の項目を次のようにリレーション名に付加してその項目にアクセスすることもできます。 DataType s = objectA.RelationshipName.FieldName; 同様に、同じ構文を使用して sObject の子 sObject を取得できます。唯一の違いは、sObject の子レコードが 1 つ 以上のコレクションであるのに対し、上述の場合は親レコードが 1 つのみであることです。構文は次のように なります。 List<sObjectTypeName> children = objectA.ChildRelationshipName; sObject リレーションのクエリ sObject が別の sObject に主従関係または参照関係で関連付けられている場合、SELECT ステートメントにリレー ション名と項目名を次のように指定して、親 sObject 項目をクエリできます。 SELECT RelationshipName.Field FROM sObjectName WHERE Where_Condition [...] 子 sObject をフェッチするには、要求するすべての子 sObject とその項目を取得するネストしたクエリを次のよ うに指定します。 SELECT field1, field1, ..., (Nested query for child sObjects) FROM sObjectName WHERE Where_Condition [...] 実際に試してみる 次の例は、請求書明細と品目名の間に存在する主従関係をトラバースする方法を示します。最初に、クエリに Invoice_Statement__r.Name を指定し、特定の品目名の親請求書明細の名前をクエリします。続いて、ス テートメント li.Invoice_Statement__r.Name で、返された品目名 sObject から請求書明細 sObject とその名 前を取得します。次のコードを実行します。 Line_Item__c li = [SELECT Invoice_Statement__r.Name FROM Line_Item__c LIMIT 1]; // Traverses a relationship using the dot notation. System.debug('Invoice statement name: ' + li.Invoice_Statement__r.Name); SELECT ステートメントの Invoice_Statement__r 項目の末尾は __r です。このサフィックスは、この項目が リレーション項目であることを示します。これは外部キーのように機能し、クエリした品目名の親請求書明細 を参照します。 次のような出力が返されます。 Invoice statement name: INV-0000 次の 2 つ目の例は、子 sObject の取得方法を示します。ネストしたクエリ (SELECT Value__c FROM Line_Items__r) を使用して請求書明細の子品目名を取得します。続いて、返された請求書明細 sObject を使 用して請求書明細の子品目名を取得します。 Invoice_Statement__c inv = [SELECT Id, Name, (SELECT Units_Sold__c FROM Line_Items__r) FROM Invoice_Statement__c WHERE Name='INV-0000']; // Access child records. List<Line_Item__c> lis = inv.Line_Items__r; System.debug('Number of child line items: ' + lis.size()); 36 第 2 章: Apex 言語の基本 SOQL for ループ ネストしたクエリは Line_Items__r から子レコードを取得します。Line_Items__r の __r サフィックスは、 これがリレーションの名前であることを示します。このネストしたクエリは、Line_Items__r で表される主 従関係を使用して請求書明細の子品目名を取得します。 サンプルの請求書明細に含まれる品目名は 1 つのみであるため、この例では次のように出力されます。 Number of child line items: 1 SOQL for ループ クエリは特殊な for 構文に組み込むことができます。この構文は、ループ内でクエリから返される sObject を一 度に 1 つずつ処理する場合や、リスト変数を使用してクエリ結果を保持し、200 個の sObject のバッチを一括処 理する場合に使用できます。SOQL for ループでリスト変数を使用してクエリ結果を保持する方法は、ガバナ実 行制限の 1 つであるヒープ制限を回避できるため、大量のレコードをクエリする場合に適しています。ガバナ 制限についての詳細は、第 3 章の「ガバナ実行制限内での Apex の実行」で学習します。 次に、SOQL for ループの例を示します。この例では、クエリから返される 1 個の sObjectに対して for ループの 1 回の反復処理が行われます。これは、for ループ内部でデータベース操作を行う場合は不十分です。sObject ご とに操作を 1 回実行するため、特定のガバナ制限に達する可能性が高くなるためです。 for (Merchandise__c tmp : [SELECT Id FROM Merchandise__c]) { // Perform some actions on the single merchandise record. } より効率的な方法は、リスト変数を使用して、for ループの各反復で返されるレコードのバッチを保持する方 法です。これにより、データベース操作の一括処理が可能になります。次の例では、for ループでリスト変数 を使用しています。 for (Merchandise__c[] tmp : [SELECT Id FROM Merchandise__c]) { // Perform some actions on the single merchandise record. } Apex データ操作言語 このチュートリアルのこれまでのレッスンでは、sObject の概要、sObject のクエリ方法、および sObject 間のリ レーションをトラバースする方法を見てきました。ここでは、Apex データ操作言語 (DML) を使用してデータ ベースのレコードを操作する方法を学習します。DML を使用すると、データベース内のデータを挿入、更新、 削除、または復元できます。 次の例では、insert をコールして新しい請求書明細を挿入します。実際に試してみましょう。 Invoice_Statement__c inv = new Invoice_Statement__c(Description__c='My new invoice'); // Insert the invoice using DML. insert inv; 請求書明細を挿入すると、sObject 変数 inv には新しい請求書明細の ID が入ります。 今度は、請求書明細の状況を変更して更新しましょう。次のコードを実行して、挿入した請求書明細の状況を 変更し、データベースのレコードを更新します。 // First get the new invoice statement Invoice_Statement__c inv = [SELECT Status__c 37 第 2 章: Apex 言語の基本 Apex データ操作言語 FROM Invoice_Statement__c WHERE Description__c='My new invoice']; // Update the status field inv.Status__c = 'Negotiating'; update inv; この請求書明細は完了したので、delete ステートメントを使用して削除しましょう。次のサンプルを実行し ます。 // First get the new invoice statement Invoice_Statement__c inv = [SELECT Status__c FROM Invoice_Statement__c WHERE Description__c='My new invoice']; delete inv; レコードを削除すると、そのレコードはごみ箱に入り、そこから復元できます。ごみ箱のレコードは一時的に 15 日間保存された後、完全に削除されます。レコードを復元するには、undelete DML ステートメントを使用 するだけです。削除したレコードを取得できるように SOQL クエリでは ALL ROWS キーワードを使用していま す。 Invoice_Statement__c inv = [SELECT Status__c FROM Invoice_Statement__c WHERE Description__c='My new invoice' ALL ROWS]; undelete inv; メモ: Apex は、merge や upsert などその他の DML 操作もサポートします。詳細は、『Force.com Apex コー ド開発者ガイド』を参照してください。 Database DML メソッド 別の方法として、Database クラスで提供されるメソッドをコールして DML 操作を実行できます。学習した DML ステートメントには対応する Database メソッドもあり、Database クラス Database.DMLOperation でコー ルできます。Database DML メソッドは、1 つの sObject または sObject のリストを第 1 引数として取ります。また、 省略可能な Boolean 型の第 2 引数 opt_allOrNone も取ります。この引数では、操作の部分的な完了を許可す るかどうかを指定します。この引数を false に設定した場合、あるレコードが失敗しても、残りの DML 操作 を続行して完了することができます。Database DML メソッドは、実行した DML 操作の結果を返します。 次の例では、2 つの請求書明細を挿入し、部分的な完了を許可します。続いて、DML の結果を反復処理し、失 敗したレコードの最初のエラーを取得します。実際に試してみましょう。 Invoice_Statement__c inv1 = Invoice_Statement__c inv2 = // Insert the invoice using Database.SaveResult[] lsr = new Invoice_Statement__c(Description__c='My new invoice'); new Invoice_Statement__c(Description__c='Another invoice'); DML. Database.insert( new Invoice_Statement__c[]{inv1, inv2}, false); // Iterate through the results and // get the first error for each failed record. for (Database.SaveResult sr:lsr){ if(!sr.isSuccess()) 38 第 2 章: Apex 言語の基本 Apex データ操作言語 Database.Error err = sr.getErrors()[0]; } メモ: opt_allOrNone 引数を false に設定すると、DML 操作が失敗したときに例外の発生を避けること ができます。例外についての詳細は、「例外処理」で学習します。 請求書明細が挿入されたら、削除しましょう。次の例は、最初にクエリを実行して前の例で作成した請求書を 取得し、削除します。続いて、delete 操作の結果を反復処理し、失敗したレコードの最初のエラーを取得しま す。次のコードを実行します。 Invoice_Statement__c[] invs = [SELECT Id FROM Invoice_Statement__c WHERE Description__c='My new invoice' OR Description__c='Another invoice']; // Delete the invoices returned by the query. Database.DeleteResult[] drl = Database.delete(invs, false); // Iterate through the results and // get the first error for each failed record. for (Database.DeleteResult dr:drl){ if(!dr.isSuccess()) Database.Error err = dr.getErrors()[0]; } 前のセクションで説明したように、削除したレコードは 15 日間ごみ箱に置かれます。次の例では、削除した レコードを Database.undelete をコールして復元します。SOQL クエリでは、削除したレコードを取得でき るように、ALL ROWS キーワードを使用しています。 Invoice_Statement__c[] invs = [SELECT Status__c FROM Invoice_Statement__c WHERE Description__c='My new invoice' OR Description__c='Another invoice' ALL ROWS]; // Restore the deleted invoices. Database.UndeleteResult[] undelRes = Database.undelete(invs, false); // Iterate through the results and // get the first error for each failed record. for (Database.UndeleteResult dr:undelRes){ if (!dr.isSuccess()) Database.Error err = dr.getErrors()[0]; } DML ステートメントと Database DML ステートメントをいつ使用するか 通常、opt_allOrNone 引数を false に設定して一括 DML 操作の部分的な完了を許可する場合は DML ステー トメントではなく Database メソッドを使用します。この方法では、コードで例外が発生するのを回避し、返さ れた結果から拒否されたレコードを調査し、可能ならば操作を再試行できます (例外については次の「例外処 理」で学習します)。opt_allOrNone 引数を false に設定していなければ、Database メソッドは例外もサポー トします。 39 第 2 章: Apex 言語の基本 まとめ DML 一括処理中のエラーを Apex 例外としてスローし、制御の流れを直ちに中断する場合は DML ステートメン トを使用します。これは try/catch ブロックを使用して処理できます。この動作は、ほとんどのデータベース手 続き型言語での例外の処理方法に似ています。 まとめ このチュートリアルでは、sObject と、クエリを記述してデータベースから情報を抽出する方法について学習し ました。また、Apex DML を使用して挿入、更新、削除、および復元操作を実行する方法も学習しました。 もうひとこと... その他のリソースとして次のようなものがあります。 トランザクションのロールバックと savepoint Apex は、トランザクションのロールバックをサポートします。savepoint を生成し、データベースの状態に 対応する、要求内のある時点に境界を設定できます。savepoint より後に実行された DML ステートメントを 破棄して、データベースを実行前と同じ初期状態に復元できます。Apex トランザクションについての詳細 は、このワークブックの第 3 章にある「1 つのトランザクションとしてのデータ操作の実行」を参照してく ださい。 ロックステートメント Apexでは、sObject レコードをロックして他のコードがそれを変更しないようにすることができます。レコー ドをロックするには FOR UPDATE SOQL ステートメントを使用します。 sObject Describe Apex は、sObject の Describe を実行するメソッドを提供します。すべての sObject のリストと、sObject の項目 および項目属性のリストを取得できます。詳細は、『Force.com Apex コード開発者ガイド』を参照してくだ さい。 例外処理 このチュートリアルでは、Apexでの例外と、コードでの例外処理方法について学習します。また、組み込み例 外の概要と、独自の例外を作成して発生させる方法も学習します。 このチュートリアルの例は、すべて開発者コンソールを使用して実行します。 例外とは? 例外は、コード実行の正常な流れを中断させるエラーやその他のイベントが発生したことを通知します。throw ステートメントは例外の生成に使用され、try、catch、および finally ステートメントは例外から適切に 復旧するために使用されます。 コードでエラーを処理するには、System.assert コールのようなアサーションの使用や、エラーコードや Boolean 値を返すなど、さまざまな方法がありますが、なぜ例外を使うのでしょうか。例外を使用する利点は、 エラー処理が簡素化されることです。例外は、コールされたメソッドからコール側に必要な数のレベルで生成 されて放置され、catch ステートメントが見つかるとエラーが処理されます。これにより、各メソッドでエ 40 第 2 章: Apex 言語の基本 try、catch、finally ステートメント ラーを処理するコードを記述する必要がなくなります。また、finally ステートメントを使用することで、 変数のリセットやデータの削除など、例外からの復旧を一元的に行うことができます。 例外が発生すると何が行われるか 例外が発生すると、コードの実行は停止し、例外発生より前に処理された DML 操作はロールバックされてデー タベースにはコミットされません。例外はデバッグログに記録されます。処理されない例外、つまり、コード でキャッチされない例外の場合、Salesforceから開発者に、実行ユーザの組織 ID とユーザ ID および例外メッセー ジを記載したメールが送信されます。 標準のユーザインターフェースを使用中に Apex コードで例外が発生した場合、処理されない例外を示すテキ ストを含むエラーメッセージがページに次のように表示されます。 try、catch、finally ステートメント Apex では、try、catch、および finally ステートメントを使用して例外を処理します。次の例は、これら のステートメントとそれを記述する順序を示します。 try { // Perform some database operations that // might cause an exception. } catch(DmlException e) { // DmlException handling code here. } catch(Exception e) { // Generic exception handling code here. } finally { // Perform some clean up. } try ステートメントは例外が発生する可能性のあるコードのブロックを識別します。例外を生成する可能性の あるコードでは、コードのこのセクションを try ブロックでラップし、その後に catch ブロックを追加しま す。try ブロックでラップしたコードから発生する例外のみが、catch ブロックで処理されます。 41 第 2 章: Apex 言語の基本 try、catch、finally ステートメント catch ステートメントは、特定の種別の例外を処理するコードのブロックを示します。上の例には、2 つの catch ステートメントがあります。catch ステートメントは、キャッチする例外種別ごとに 1 つずつ、必要 な数だけ使用できます。 catch ステートメントは、固有なものから汎用的なものへと具体性の高い順に並べます。すべての例外は Exception 種別とみなされるため、汎用的な例外を最初にキャッチすると、1 つの catch ブロックのみが実行さ れ、その他の catch ステートメントが実行されなくなるからです。 catch ステートメントでは、受け取った例外を処理します。たとえば、ログの記録やメールの送信、その他 の何らかの処理を実行できます。 finally ステートメントは省略可能で、catch ブロックが実行された後に実行されます。finally ブロック内 のコードは、発生して処理された例外の種別に関係なく、常に実行されます。ここに最終的なクリーンアップ コードを追加できます。 実際に試してみる 例外の発生を確認するには、DML 例外を発生させるいくつかのコードを実行します。開発者コンソールで次の コードを実行します。 Merchandise__c m = new Merchandise__c(); insert m; この例の insert DML ステートメントは、必須項目を設定せずに商品品目を挿入しているため、DMLException を発生させます。この例外エラーは、デバッグログに次のように表示されます。 System.DmlException: Insert failed. First exception on row 0; first error: REQUIRED_FIELD_MISSING, Required fields are missing: [Description, Price, Total Inventory]: [Description, Price, Total Inventory] 続いて、開発者コンソールで次のスニペットを実行します。これは前の例に基づいていますが、try-catch ブロッ クが追加されています。 try { Merchandise__c m = new Merchandise__c(); insert m; } catch(DmlException e) { System.debug('The following exception has occurred: ' + e.getMessage()); } 開発者コンソールに表示される要求の状況は、正常に完了したことを報告しています。これは、コードが例外 を処理しているためです。 例外の後に出現する try ブロックのステートメントはすべてスキップされ、実行されません。たとえば、insert m; の後にステートメントを追加しても、ステートメントは実行されません。次のコードを実行します。 try { Merchandise__c m = new Merchandise__c(); insert m; // This doesn't execute since insert causes an exception System.debug('Statement after insert.'); } catch(DmlException e) { System.debug('The following exception has occurred: ' + e.getMessage()); } 42 第 2 章: Apex 言語の基本 try、catch、finally ステートメント 新しいデバッグログエントリには、「Statement after insert」というデバッグメッセージは表示されま せん。これは、この debug ステートメントが挿入で発生した例外の後に出現し、実行されないためです。例外 が発生した後にコードステートメントの実行を続行するには、try-catch ブロックの後にステートメントを配置 します。この変更されたコードスニペットを実行すると、デバッグログに「Statement after insert」と いうデバッグメッセージが表示されるようになります。 try { Merchandise__c m = new Merchandise__c(); insert m; } catch(DmlException e) { System.debug('The following exception has occurred: ' + e.getMessage()); } // This will get executed System.debug('Statement after insert.'); または、try-catch ブロックを追加できます。このコードスニペットでは、2 つ目の try-catch ブロック内に System.debug ステートメントがあります。これを実行すると、前と同じ結果になります。 try { Merchandise__c m = new Merchandise__c(); insert m; } catch(DmlException e) { System.debug('The following exception has occurred: ' + e.getMessage()); } try { System.debug('Statement after insert.'); // Insert other records } catch (Exception e) { // Handle this exception here } finally ブロックは、発生した例外に関係なく、また例外が発生しなくても常に実行されます。実際にどう使用 されるのか見てみましょう。次のコードを実行します。 // Declare the variable outside the try-catch block // so that it will be in scope for all blocks. XmlStreamWriter w = null; try { w = new XmlStreamWriter(); w.writeStartDocument(null, '1.0'); w.writeStartElement(null, 'book', null); w.writeCharacters('This is my book'); w.writeEndElement(); w.writeEndDocument(); // Perform some other operations String s; // This causes an exception because // the string hasn't been assigned a value. Integer i = s.length(); } catch(Exception e) { System.debug('An exception occurred: ' + e.getMessage()); 43 第 2 章: Apex 言語の基本 組み込み例外および共通メソッド } finally { // This gets executed after the exception is handled System.debug('Closing the stream writer in the finally block.'); // Close the stream writer w.close(); } 上記のコードスニペットでは、XML ストリームライタを作成し、いくつかの XML 要素を追加します。次に、null の String 変数 s にアクセスしたために例外が発生します。catch ブロックがこの例外を処理します。続いて、 finally ブロックが実行されます。このブロックでデバッグメッセージが書き出され、ストリームライタが終了 し、それによって関連リソースが解放されます。デバッグログでデバッグ出力を確認します。例外エラーの後 にデバッグメッセージ「Closing the stream writer in the finally block.」が表示されます。これ により、例外がキャッチされた後に finally ブロックが実行されたことがわかります。 メモ: ランタイムがガバナ制限に達した結果として発生させる例外など、処理できない例外もあります。 ガバナ制限についての詳細は、第 3 章の「ガバナ実行制限内での Apex の実行」で学習します。 組み込み例外および共通メソッド Apexには、実行時にエラーが発生した場合にランタイムエンジンが生成する複数の例外種別が用意されていま す。前の例では DMLException が使用されていました。いくつかの組み込み例外の例を次に説明します。 DmlException insert ステートメントでレコードの必要な項目が欠落している場合など、DML ステートメントに関する問 題を示す例外。 DmlException の使用例は、「try、catch、finally ステートメント」を参照してください。 ListException 範囲外のインデックスへのアクセスなど、リストに関する問題を示す例外。 意図的にこの例外を発生させるコードを実行してみしょう。次のコードを実行します。 try { List<Integer> li = new List<Integer>(); li.add(15); // This list contains only one element, // but we're attempting to access the second element // from this zero-based list. Integer i1 = li[0]; Integer i2 = li[1]; // Causes a ListException } catch(ListException le) { System.debug('The following exception has occurred: ' + le.getMessage()); } 上記のコードスニペットでは、リストを作成して 1 つの要素を追加しました。続いて、2 つの要素にアクセ スを試みました。一方の要素はインデックス 0 に存在し、もう一方の要素はインデックス 1 に存在しない ために ListException が発生します。この例外は、catch ブロックでキャッチされます。catch ブロックの System.debug ステートメントは、デバッグログに「The following exception has occurred: List index out of bounds: 1」と出力します。 NullPointerException null 変数の参照解決に関する問題を示す例外。 44 第 2 章: Apex 言語の基本 組み込み例外および共通メソッド 意図的にこの例外を発生させるコードを実行してみしょう。次のコードを実行します。 try { String s; Boolean b = s.contains('abc'); // Causes a NullPointerException } catch(NullPointerException npe) { System.debug('The following exception has occurred: ' + npe.getMessage()); } 上記の例では、s という名前の String 変数を作成しましたが、この変数は値で初期化されていないため null です。null 変数に対して contains メソッドをコールすると、NullPointerException が発生します。この例外 は、catch ブロックでキャッチされ、デバッグログには「The following exception has occurred: Attempt to de-reference a null object」と出力されます。 QueryException sObject の単一変数に対する、レコードを返さない、または複数のレコードを返すクエリの割り当てなど、 SOQL クエリに関する問題を示す例外。 意図的にこの例外を発生させるコードを実行してみしょう。次のコードを実行します。 try { // This statement doesn't cause an exception, even though // we don't have a merchandise with name='XYZ'. // The list will just be empty. List<Merchandise__c> lm = [SELECT Name FROM Merchandise__c WHERE Name='XYZ']; // lm.size() is 0 System.debug(lm.size()); // However, this statement causes a QueryException because // we're assiging the return value to a Merchandise__c object // but no Merchandise is returned. Merchandise__c m = [SELECT Name FROM Merchandise__c WHERE Name='XYZ' LIMIT 1]; } catch(QueryException qe) { System.debug('The following exception has occurred: ' + qe.getMessage()); } 上記のコードスニペットの 2 つ目のクエリでは、QueryException が発生します。ここではクエリの戻り値に Merchandise オブジェクトを割り当てようとしています。クエリでの LIMIT 1 の使用方法を見てください。 ここでは、データベースから返されるオブジェクトは 1 つ以下であるため、割り当てることができるのは 1 つのオブジェクトであり、リストではありません。ただし、この場合、XYZ という名前の Merchandise はな いため、何も返されず、戻り値を 1 つのオブジェクトに割り当てようとすると QueryException が発生します。 例外は catch ブロックでキャッチされ、デバッグログには「The following exception has occurred: List has no rows for assignment to SObject」と表示されます。 SObjectException insert の間のみ変更可能な update ステートメント内の項目の変更など、sObject レコードに関する問題 を示す例外。 意図的にこの例外を発生させるコードを実行してみしょう。次のコードを実行します。 try { Merchandise__c m = [SELECT Name FROM Merchandise__c LIMIT 1]; // Causes an SObjectException because we didn't retrieve // the Total_Inventory__c field. 45 第 2 章: Apex 言語の基本 組み込み例外および共通メソッド Double inventory = m.Total_Inventory__c; } catch(SObjectException se) { System.debug('The following exception has occurred: ' + se.getMessage()); } コードスニペットでは、データベース内にある Merchandise オブジェクトをクエリします。クエリでの LIMIT 1 の使用方法を見てください。サンプルの商品品目があるため、クエリの最初のオブジェクトは返され、 Merchandise 変数 m に割り当てられます。ただし、クエリで取得したのは Name 項目のみで Total_Inventory で はないため、Merchandise オブジェクトから Total_Inventory 値を取得しようとすると、SObjectException が発生 します。この例外は catch ブロックでキャッチされ、デバッグログには「The following exception has occurred: SObject row was retrieved via SOQL without querying the requested field: Merchandise__c.Total_Inventory__c」と表示されます。 共通例外メソッド 共通例外メソッドを使用して、例外エラーメッセージやスタック追跡など、例外に関する詳細情報を取得でき ます。上の例では、例外に関連付けられたエラーメッセージを返す getMessage メソッドをコールしていま す。この他にも使用できる例外メソッドがあります。いくつかの役に立つメソッドを次に説明します。 • getCause: 例外オブジェクトとして例外の原因を返します。 • getLineNumber: 例外が発生した箇所の行番号を返します。 • getMessage: ユーザに表示されるエラーメッセージを返します。 • getStackTraceString: 文字列としてスタック追跡を返します。 • getTypeName: DMLException、ListException、MathException などの例外種別を返します。 実際に試してみる 次の単純な例を実行して、これらのメソッドが何を返すか確認しましょう。 try { Merchandise__c m = [SELECT Name FROM Merchandise__c LIMIT 1]; // Causes an SObjectException because we didn't retrieve // the Total_Inventory__c field. Double inventory = m.Total_Inventory__c; } catch(Exception e) { System.debug('Exception type caught: ' + e.getTypeName()); System.debug('Message: ' + e.getMessage()); System.debug('Cause: ' + e.getCause()); // returns null System.debug('Line number: ' + e.getLineNumber()); System.debug('Stack trace: ' + e.getStackTraceString()); } すべての System.debug ステートメントの出力は、次のようになります。 17:38:04:149 USER_DEBUG [7]|DEBUG|Exception type caught: System.SObjectException 17:38:04:149 USER_DEBUG [8]|DEBUG|Message: SObject row was retrieved via SOQL without querying the requested field: Merchandise__c.Total_Inventory__c 17:38:04:150 USER_DEBUG [9]|DEBUG|Cause: null 17:38:04:150 USER_DEBUG [10]|DEBUG|Line number: 5 17:38:04:150 USER_DEBUG [11]|DEBUG|Stack trace: AnonymousBlock: line 5, column 1 46 第 2 章: Apex 言語の基本 組み込み例外および共通メソッド catch ステートメントの引数種別は、汎用的な Exception 種別です。より具体的な SObjectException をキャッチしま す。これが行われているかどうか確認するには、デバッグ出力で e.getTypeName() の戻り値を調べます。 出力には、エラーメッセージ、例外が発生した行番号、スタック追跡など、SObjectException の他のプロパティ も含まれます。getCause はなぜ null を返したのでしょうか。このサンプルでは、この前にこの例外を発生さ せる例外 (内部例外) がないためです。「カスタム例外の作成」には、getCause の戻り値が実際の例外となる 例があります。 その他の例外メソッド DmlException など、いくつかの例外種別には、その種別にのみ適用される次のような特定の例外メソッドがあ ります。 • getDmlFieldNames(Index of the failed record): 指定されたエラーレコードのエラーの原因となっ た項目の名前を返します。 • getDmlId(Index of the failed record): 指定されたエラーレコードのエラーの原因となったエラー レコードの ID を返します。 • getDmlMessage(Index of the failed record): 指定されたエラーレコードに関するエラーメッセー ジを返します。 • getNumDml: エラーレコードの数を返します。 実際に試してみる このスニペットは、DmlException メソッドを使用して、Merchandise オブジェクトのリストを挿入したときに返 された例外に関する詳細な情報を取得します。挿入する品目リストには 3 つの品目が含まれており、2 つ目以 降の品目には必須項目がなく、例外が発生します。 Merchandise__c m1 = new Merchandise__c( Name='Coffeemaker', Description__c='Kitchenware', Price__c=25, Total_Inventory__c=1000); // Missing the Price and Total_Inventory fields Merchandise__c m2 = new Merchandise__c( Name='Coffeemaker B', Description__c='Kitchenware'); // Missing all required fields Merchandise__c m3 = new Merchandise__c(); Merchandise__c[] mList = new List<Merchandise__c>(); mList.add(m1); mList.add(m2); mList.add(m3); try { insert mList; } catch (DmlException de) { Integer numErrors = de.getNumDml(); System.debug('getNumDml=' + numErrors); for(Integer i=0;i<numErrors;i++) { System.debug('getDmlFieldNames=' + de.getDmlFieldNames(i)); System.debug('getDmlMessage=' + de.getDmlMessage(i)); 47 第 2 章: Apex 言語の基本 さまざまな例外種別のキャッチ } } 上記のサンプルでは、try ブロックに含まれている初期コードがすべて含まれているわけではありません。例 外が発生する可能性があるコード部分のみが try ブロック内にラップされています。この場合、insert ス テートメントは入力データが有効でない場合に DML 例外を返す可能性があります。insert 操作で発生した例 外は、その後の catch ブロックでキャッチされます。このサンプルを実行した後、次のような System.debug ステートメントの出力が表示されます。 14:01:24:939 USER_DEBUG [20]|DEBUG|getNumDml=2 14:01:24:941 USER_DEBUG [23]|DEBUG|getDmlFieldNames=(Price, Total Inventory) 14:01:24:941 USER_DEBUG [24]|DEBUG|getDmlMessage=Required fields are missing: [Price, Total Inventory] 14:01:24:942 USER_DEBUG [23]|DEBUG|getDmlFieldNames=(Description, Price, Total Inventory) 14:01:24:942 USER_DEBUG [24]|DEBUG|getDmlMessage=Required fields are missing: [Description, Price, Total Inventory] DML エラーの数は、リストのうち 2 つの品目で挿入が失敗したため、正しく 2 つと報告されています。また、 エラーが発生した項目名と、各エラーレコードのエラーメッセージも出力に書き出されます。 さまざまな例外種別のキャッチ 前のレッスンの例では、catch ブロックで具体的な例外種別を使用しました。すべての例で汎用的な Exception 種別だけをキャッチすれば、あらゆる例外種別をキャッチすることも可能です。たとえば、SObjectException を 発生させ、catch ステートメントの引数に Exception 種別を指定した次の例を実行してみます。SObjectException は catch ブロックでキャッチされます。 try { Merchandise__c m = [SELECT Name FROM Merchandise__c LIMIT 1]; // Causes an SObjectException because we didn't retrieve // the Total_Inventory__c field. Double inventory = m.Total_Inventory__c; } catch(Exception e) { System.debug('The following exception has occurred: ' + e.getMessage()); } または、複数の catch ブロックを使用して、例外種別ごとに 1 つの catch ブロックを指定し、最後の catch ブロッ クで汎用的な Exception 種別をキャッチすることもできます。次の例を見てください。3 つの catch ブロックがあ ります。 try { Merchandise__c m = [SELECT Name FROM Merchandise__c LIMIT 1]; // Causes an SObjectException because we didn't retrieve // the Total_Inventory__c field. Double inventory = m.Total_Inventory__c; } catch(DmlException e) { System.debug('DmlException caught: ' + e.getMessage()); } catch(SObjectException e) { System.debug('SObjectException caught: ' + e.getMessage()); } catch(Exception e) { 48 第 2 章: Apex 言語の基本 カスタム例外の作成 System.debug('Exception caught: ' + e.getMessage()); } すでに説明したとおり、実行される catch ブロックは 1 つのみで、残りの catch ブロックはスキップされます。 この例は前の例と似ていますが、catch ブロックの数が少し増えています。このスニペットを実行すると、行 Double inventory = m.Total_Inventory__c; で SObjectException が発生します。すべての catch ブロック が指定された順序で、発生した例外と catch ブロックの引数に指定された例外種別が一致するまで調べられま す。 1. 最初の catch ブロック引数は DmlException 種別で、発生した例外 (SObjectException) と一致しません。 2. 2 つ目の catch ブロックの引数は SObjectException 種別で、発生した例外と一致するため、このブロックが実 行され、メッセージ「SObjectException caught: SObject row was retrieved via SOQL without querying the requested field: Merchandise__c.Total_Inventory__c」がデバッグログに書き 出されます。 3. 最後の catch ブロックは、catch ブロックが 1 つすでに実行されているため、無視されます。 最後の catch ブロックは、どの例外種別でも、つまり、前の catch ブロックでキャッチされなかったどの例外で もキャッチするため、便利です。たとえば、上記のコードを NullPointerException が発生するように変更すると、 この例外は最後の catch ブロックでキャッチされます。変更した次の例を実行します。デバッグメッセージ 「Exception caught: Attempt to de-reference a null object」が表示されます。 try { String s; Boolean b = s.contains('abc'); // Causes a NullPointerException } catch(DmlException e) { System.debug('DmlException caught: ' + e.getMessage()); } catch(SObjectException e) { System.debug('SObjectException caught: ' + e.getMessage()); } catch(Exception e) { System.debug('Exception caught: ' + e.getMessage()); } カスタム例外の作成 組み込み Apex 例外は意図的に発生させることはできず、キャッチすることしかできません。そのため、例外 をメソッドで発生させるにはカスタム例外を作成します。カスタム例外を使用すると、詳細なエラーメッセー ジを指定したり、catch ブロックでカスタマイズしたエラー処理を行ったりすることもできます。 カスタム例外クラスを作成するには、組み込み Exception クラスを拡張して、クラス名の最後が Exception で終わるように指定します。クラス宣言の後に、次のように extends Exception を付加します。 public class MyException extends Exception {} 独自の例外オブジェクトは次のような形で作成し、発生させることができます。 次のような例外を作成できます。 • 引数のない例外 new MyException(); 49 第 2 章: Apex 言語の基本 カスタム例外の作成 • エラーメッセージを指定する 1 つの string 型の引数を取る例外 new MyException('This is bad'); • 1つの Exception 型の引数を取るもの。これは原因を特定でき、任意にスタック追跡できます new MyException(e); • string 型のエラーメッセージと、任意のスタック追跡に表示される例外チェーンの両方を取る例外 new MyException('This is bad', e); 例外クラスの作成方法と、例外オブジェクトの構築方法を学習したので、カスタム例外の便利さを示す例を作 成して実行してみましょう。 1. 開発者コンソールで、MerchandiseException という名前のクラスを作成し、次のコードを追加します。 public class MerchandiseException extends Exception {} この例外クラスは、これから作成する 2 つ目のクラス内で使用します。最後の中括弧で例外クラスの本文 を囲みます。例外クラスは空のままにしておき、既存のコードを使用します。このクラスは、組み込み Exception クラスから、getMessage など、すべてのコンストラクタと共通例外メソッドを継承するため です。 2. 続いて、2 つ目のクラスを MerchandiseUtility という名前で作成します。 public class MerchandiseUtility { public static void mainProcessing() { try { insertMerchandise(); } catch(MerchandiseException me) { System.debug('Message: ' + me.getMessage()); System.debug('Cause: ' + me.getCause()); System.debug('Line number: ' + me.getLineNumber()); System.debug('Stack trace: ' + me.getStackTraceString()); } } public static void insertMerchandise() { try { // Insert merchandise without required fields Merchandise__c m = new Merchandise__c(); insert m; } catch(DmlException e) { // Something happened that prevents the insertion // of Employee custom objects, so throw a more // specific exception. throw new MerchandiseException( 'Merchandise item could not be inserted.', e); } } } このクラスには、mainProcessing メソッドが含まれ、そのメソッドから insertMerchandise がコール されます。このコール先で、必須項目を指定せずに Merchandise が挿入されるため、例外が発生します。catch 50 第 2 章: Apex 言語の基本 まとめ ブロックはこの例外をキャッチし、前に作成した新しい例外であるカスタムの MerchandiseException を発生 させます。ここでは、2 つの引数 (エラーメッセージ、元の例外オブジェクト) を取る例外のコンストラクタ をコールしています。なぜ元の例外を渡すのでしょうか。それは、最初のメソッド mainProcessing で MerchandiseException をキャッチした場合、この例外の本当の原因は MerchandiseException よりも前に発生した 元の例外 (内部例外と呼ばれる) であるため、元の例外の情報が役に立つからです。 3. 理解を深めるために、これらが実際にどう機能するのか見てみましょう。次のコードを実行します。 MerchandiseUtility.mainProcessing(); 4. デバッグログ出力を確認します。ログには、次のように表示されます。 18:12:34:928 USER_DEBUG [6]|DEBUG|Message: Merchandise item could not be inserted. 18:12:34:929 USER_DEBUG [7]|DEBUG|Cause: System.DmlException: Insert failed. First exception on row 0; first error: REQUIRED_FIELD_MISSING, Required fields are missing: [Description, Price, Total Inventory]: [Description, Price, Total Inventory] 18:12:34:929 USER_DEBUG [8]|DEBUG|Line number: 22 18:12:34:930 USER_DEBUG [9]|DEBUG|Stack trace: Class.EmployeeUtilityClass.insertMerchandise: line 22, column 1 次の点に留意してください。 • MerchandiseException の原因は DmlException です。必須項目がないことを示す DmlException メッセージも表 示されます。 • スタック追跡は、2 つ目の例外が発生した場所である行 22 です。MerchandiseException の throw ステートメ ントに対応しています。 throw new MerchandiseException('Merchandise item could not be inserted.', e); まとめ このチュートリアルでは、例外、例外の処理方法、Apexの組み込み例外および共通例外メソッド、独自のカス タム例外を作成して発生させる方法について学習しました。 51 第 3 章: コンテキストでの Apex 第 2 章では、基本的な Apex 構文、データ型、データベースインテグレーション、および Apex 言語のその他の 機能について学習しました。この章では、高レベルの Force.com プラットフォーム機能を使用して Apex クラス をコールする方法を学習します。 ヒント: この章のチュートリアルを行うには、「Warehouse カスタムオブジェクトの作成」と「サンプル データの作成」でカスタムオブジェクトとサンプルデータを設定しておく必要があります。 この章に含まれるチュートリアルとその簡単な説明は、次のとおりです。 • 1 つのトランザクションとしてのデータ操作の実行: トランザクションの概要とトランザクションのロール バックについて学習します。 • トリガを使用したカスタムビジネスロジックの追加: トリガの作成手順を取り上げ、トリガの概要とその構 文について学習します。 • Apex単体テスト: テストデータを生成するテストファクトリクラスの作成手順と、テストメソッドの作成手 順について学習します。このチュートリアルでは、テストを実行して、コードカバー率を検証する方法も 取り上げます。 • ガバナ実行制限内でのApexの実行: Force.comマルチテナントクラウド環境の制限について説明し、いくつか の例を確認します。 • Apex のスケジュール実行: Apex を特定の日時に実行できる Apex スケジューラについて学習します。 • Apex 一括処理: Apex 一括ジョブを作成して実行する手順について学習します。 • Apex REST: Apex クラスを作成して REST リソースとして公開する手順、およびワークベンチから REST メソッド をコールする手順について学習します。 • Apex コントローラを使用する Visualforce ページ: 在庫品目を表示および注文するための Visualforce ページとコ ントローラを作成する手順について学習します。 1 つのトランザクションとしてのデータ操作の実行 前提条件: • クラスの作成とインスタンス化 • sObject とデータベース Apex トランザクションとは? Apex トランザクションは、1 つの単位として実行される一連の操作を表します。トランザクションの実行には、 すべての DML 操作が正常に完了することが求められます。いずれかの操作でエラーが発生した場合はトラン ザクション全体がロールバックされます。この場合、データは一切データベースにコミットされません。トラ ンザクションの境界は、トリガ、クラスメソッド、匿名のコードブロック、Visualforce ページ、カスタム Web サービスメソッドのいずれかにすることができます。 52 第 3 章: コンテキストでの Apex 1 つのトランザクションとしてのデータ操作の実行 トランザクション境界内部で発生するすべての操作は、操作の 1 つの単位に相当します。これは、トランザク ション境界内で実行されたコードの結果として起動されたクラスやトリガなど、トランザクション境界から外 部コードへのコールにも適用されます。たとえば、カスタム Apex Web サービスメソッドによってトリガが起 動し、そのトリガがクラスのメソッドをコールするという連続した操作があるとします。この場合、トランザ クション内のすべての操作がエラーなしで実行を完了した後にのみ、すべての変更がデータベースにコミット されます。中間ステップのいずれかでエラーが発生した場合、すべてのデータベース変更はロールバックさ れ、トランザクションはコミットされません。 トランザクションが便利な場合とは? トランザクションは、複数の操作が関連していて、それらの操作のすべてをコミットするか、一切コミットし ないかのいずれかにする必要がある場合に便利です。これにより、データベースは整合性の取れた状態に保た れます。トランザクション処理は、さまざまなビジネスシナリオで活用されています。たとえば、一般的なシ ナリオとして銀行口座間での送金があります。最初の口座から送金額を引き落とし、その金額を 2 つ目の口座 に入金します。これら 2 つの操作は、一緒にデータベースにコミットする必要があります。引き落とし操作が 成功して入金操作が失敗したような場合に、口座残高に矛盾が生じることを防ぐためです。 実際に試してみる この例は、最後の操作で入力規則エラーが発生した場合、すべてのデータベース insert 操作がどのように ロールバックされるかを示しています。この例では、invoice メソッドがトランザクション境界です。この メソッド内で実行されるすべてのコードは、データベースにすべての変更をコミットするか、すべての変更を ロールバックします。この場合、Line Item (品目名) として鉛筆を指定した、新しい請求書明細を追加します。 この Line Item を使用して、5,000 本の鉛筆を購入 (Units_Sold__c 項目に指定) します。これは鉛筆の全在庫数量 1,000 本を上回ります。 第 1 章で作成したサンプルの Line Item オブジェクトには入力規則が含まれます。この入力規則は、商品品目の 全在庫数量が新規購入に足りるかどうかをチェックします。この例では、在庫数量 (1,000) よりも多く (5,000) の 鉛筆を購入しようとしているので、入力規則は失敗して、実行時例外が発生します。コードの実行はこの時点 で停止し、この例外よりも前に処理されたすべての DML 操作はロールバックされます。この場合、請求書明 細と品目名はデータベースには追加されず、その insert DML 操作はロールバックされます。 1. 開発者コンソールで次のクラスを追加します。 2. クラス名として「MerchandiseOperations」と入力し、自動生成されたコードを次の例で置き換えます。 public class MerchandiseOperations { public static Id invoice( String pName, Integer pSold, String pDesc) { // Retrieve the pencils sample merchandise Merchandise__c m = [SELECT Price__c,Total_Inventory__c FROM Merchandise__c WHERE Name = :pName LIMIT 1]; // break if no merchandise is found System.assertNotEquals(null, m); // Add a new invoice Invoice_Statement__c i = new Invoice_Statement__c( Description__c = pDesc); insert i; // Add a new line item to the invoice 53 第 3 章: コンテキストでの Apex トリガを使用したカスタムビジネスロジックの追加 Line_Item__c li = new Line_Item__c( Name = '1', Invoice_Statement__c = i.Id, Merchandise__c = m.Id, Unit_Price__c = m.Price__c, Units_Sold__c = pSold); insert li; // Update the inventory of the merchandise item m.Total_Inventory__c -= pSold; update m; return i.Id; } } 3. 開発者コンソールで、静的 invoice メソッドを実行します。 Id invoice = MerchandiseOperations.invoice('Pencils', 5000, 'test 1'); このスニペットでは、品目名の挿入時に品目名に対する入力規則が失敗し、DmlException が返されます。す べての DML 操作はロールバックされます。請求書明細と品目名はデータベースにはコミットされません。 4. 実行ログで入力規則のエラーメッセージと例外を探しましょう。[Filter (条件)]の横に「VF_PAGE_MESSAGE」 と入力します。 入力規則のエラーメッセージが絞り込まれたビューに表示されます (You have ordered more items than we have in stock.)。 5. 次に、検索項目に「exception」と入力し、例外を調べます。 6. 前のスニペットを削除して、次の 2 つ目のコードのスニペットを実行します。 Id invoice = MerchandiseOperations.invoice('Pencils', 5, 'test 2'); このスニペットは、品目名を含む新しい請求書明細を挿入し、データベースにコミットします。鉛筆の購 入数量が全在庫数量以下であるため、入力規則の検証は成功します。 トリガを使用したカスタムビジネスロジックの追加 トリガは、sObject に対する挿入、更新、削除、または復元イベントが発生する前か後に実行される Apex コー ドです。特定の構文を持つクラスであり、データベースレコードの変更方法に応じていつ実行するかを指定で きます。 トリガを定義する構文は、クラスやインターフェースの場合と大きく異なります。トリガは常に trigger キー ワードで始まり、その後にトリガ名、トリガの関連付け先となるデータベースオブジェクト、トリガ起動の条 件 (そのデータベースオブジェクトの新しいレコードが挿入される前など) が続きます。トリガは次の構文を使 用します。 trigger triggerName on ObjectName (trigger_events) { code_block } 54 第 3 章: コンテキストでの Apex トリガの作成 挿入、更新、削除、および復元操作の前または後にトリガを実行する場合、カンマ区切りのリストで複数のト リガイベントを指定できます。指定できるのは次のイベントです。 • before insert • before update • before delete • after insert • after update • after delete • after undelete トリガの作成 前提条件: • Warehouse カスタムオブジェクトの作成 • 開発者コンソールの使用 • sObject とデータベース このレッスンで作成するトリガは、請求書明細の削除の前に起動します。請求書明細に品目名が含まれる場合 は、削除されないようにします。 1. 開発者コンソールで、[File (ファイル)] > [New (新規)] > [Apex Trigger (Apex トリガ)] をクリックします。 2. 名前に「RestrictInvoiceDeletion」と入力し、[sObject] ドロップダウンリストから、 Invoice_Statement__c を選択します。[Submit (実行)] をクリックします。 3. 自動生成されたコードを削除し、次のコードを追加します。 trigger RestrictInvoiceDeletion on Invoice_Statement__c (before delete) { // With each of the invoice statements targeted by the trigger // and that have line items, add an error to prevent them // from being deleted. for (Invoice_Statement__c invoice : [SELECT Id FROM Invoice_Statement__c WHERE Id IN (SELECT Invoice_Statement__c FROM Line_Item__c) AND Id IN :Trigger.old]){ Trigger.oldMap.get(invoice.Id).addError( 'Cannot delete invoice statement with line items'); } } 4. [Save (保存)] をクリックします。 トリガは、保存するとデフォルトで有効になります。 もうひとこと... • トリガは RestrictInvoiceDeletion という名前で、Invoice_Statement__c sObject に関連付けられま す。 55 第 3 章: コンテキストでの Apex トリガの呼び出し • 1 つ以上の Invoice_Statement__c sObject が削除される前に、トリガが実行されます。これは、before delete パラメータで指定されます。 • トリガには、トリガの対象で品目名を含む請求書明細を反復処理する SOQL for ループが含まれます。 • WHERE 句の最初の条件に含まれるネストされたクエリ (SELECT Invoice_Statement__c FROM Line_Item__c) を確認してください。各品目名には、親請求書明細を参照する Invoice_Statement__c 項目があります。このネストされたクエリは、すべての品目名の親請求書明細を取得します。 • クエリは、請求書明細の ID 値が、ネストされたクエリ WHERE Id IN (SELECT Invoice_Statement__c FROM Line_Item__c) で返された親請求書明細のセットに含まれるかどうかをチェックします。 • 2 つ目の条件は、このトリガで対象とする請求書明細のみがクエリされるように制限します。これを行うた めに、各 ID 値が Trigger.old に含まれるかどうかをチェックします。Trigger.old には、削除対象で まだ削除されていない古いレコードのセットが含まれます。 • 条件を満たす請求書明細ごとに、トリガは addError メソッドを使用してカスタムエラーメッセージを追 加し、レコードが削除されるのを防ぎます。このカスタムエラーメッセージは、品目名を含む請求書明細 を削除しようとすると、ユーザインターフェースに表示されます。これについては次のレッスンで確認し ます。 トリガの呼び出し 前提条件: • サンプルデータの作成 このレッスンでは、前のレッスンで作成したトリガを呼び出します。トリガは、請求書明細の削除の前に起動 するため、ユーザインターフェースまたはプログラムで請求書明細を削除することで、起動させることができ ます。このレッスンでは、ユーザインターフェースで削除を実行するため、請求書明細に品目名が含まれてい ると、トリガから返されるエラーメッセージを確認できます。品目名を含まない請求書明細も作成します。ト リガは品目名を含まない請求書明細の削除は防止しないため、この新しい請求書明細は削除できます。 1. Salesforce ユーザインターフェースで、[+] タブをクリックします。 2. [Invoice Statement] をクリックします。 3. [ビュー] ドロップダウンリストを [すべて] が選択された状態にし、[Go!] をクリックします。 4. [INV-0000] のような名前の付いたサンプル請求書明細をクリックします。 5. 請求書明細の詳細ページで、[削除] をクリックします。 6. 確認を求められたら、[OK] をクリックします。 新しいページに次のエラーメッセージが表示されます。 7. リンクをクリックして請求書明細ページに戻ります。 8. [最後に開いたビュー: 請求書明細] をクリックします。 56 第 3 章: コンテキストでの Apex まとめ 9. 次に、品目名を含まない別の請求書明細を作成します。[新規 Invoice Statement] をクリックします。 10. [保存] をクリックします。 11. この請求書明細を削除してみましょう。削除するには、[削除] をクリックします。 12. 確認を求められたら、[OK] をクリックします。 今度は、請求書明細が削除されました。トリガが呼び出されると、トリガのクエリは、品目名を含む請求 書明細のみを選択し、それらのレコードをエラーとマークして削除されないようにします。この請求書明 細には品目名が含まれていないため、トリガがエラーでマークするレコードに該当せず、削除が可能にな ります。 もうひとこと... • サンプル請求書明細を削除したときに表示される検証エラーメッセージは、トリガで addError メソッド を使用して請求書明細 sObject に対して指定されたエラーメッセージ「Cannot delete invoice statement with line items (品目名を含む請求書明細は削除できません)」です。 • トリガは、操作がユーザ、別のプログラム、一括操作がどこから行われても関係なく、すべてにビジネス ルールを適用します。このレッスンでは、ユーザインターフェースを使用して手動で削除を実行しました。 まとめ このチュートリアルでは、2 つの請求書明細の削除を試みることで、トリガを起動させました。トリガがどの ように品目名を含む請求書明細の削除を防止するかを確認し、ユーザインターフェースにエラーメッセージを 表示できました。次のチュートリアル「Apex単体テスト」では、プログラムでトリガを呼び出します。品目名 を含む請求書明細と含まない請求書明細の削除を試みるテストメソッドを追加します。 Apex 単体テスト コードの単体テストを記述することは、Apex コード開発の基本です。本番組織に Apex コードをリリースする には、75% 以上のテストカバー率が必要です。さらに、テストカバー率にカウントされるテストに合格する必 要があります。テストの実施は、アプリケーションの品質を確保するためには非常に重要です。さらに、将 来、コードに変更を加えた場合に再実行できるテストのセットがあると、変更によって既存のコードに発生す る潜在的な不具合を検出できます。 メモ: このテストカバー率要件は、アプリケーションのパッケージを作成して Force.com AppExchange に公 開する場合にも適用されます。Salesforce では、サービスのアップグレードを行うとき、すべての組織の Apex 単体テストを実行して、品質と、ユーザ向けの既存の動作が変更されていないことを確認します。 テストデータの分離と一時性 デフォルトでは、Apexテストメソッドは、組織の既存のデータにアクセスできません。各テストメソッド用に 独自のテストデータを作成する必要があります。このように、テストは組織データに依存しないため、依存す るデータが存在しなくなっても、データの欠落が原因で失敗することはありません。 テストデータはデータベースにコミットされず、テスト実行が完了するとロールバックされます。つまり、テ ストで作成したデータは削除する必要がありません。テストの実行が完了したら、テスト実行中に作成された データは組織に保持されず、使用できなくなります。 57 第 3 章: コンテキストでの Apex テストユーティリティクラスの追加 テストデータはテストメソッドで作成することも、テストデータ作成用メソッドを含むユーティリティテスト クラスを記述して他のテストからコールすることもできます。 組織にはテストがアクセスできるオブジェクトもあります。User や Profile など、組織の管理に使用する メタデータオブジェクトとオブジェクトです。 テストユーティリティクラスの追加 前提条件: • トリガを使用したカスタムビジネスロジックの追加 このレッスンでは、前のチュートリアルで作成したトリガを検査するためのテストを追加します。テストデー タを作成する必要があるため、他のテストクラスやテストメソッドからコール可能なテストデータ作成のため のメソッドが含まれるテストユーティリティクラスを追加します。 1. 開発者コンソールで、[ファイル] > [New (新規)] > [Apex クラス] をクリックします。 2. クラス名として「TestDataFactory」と入力し、[OK] をクリックします。 3. 自動生成されたコードを削除し、次のコードを追加します。 @isTest public class TestDataFactory { public static Invoice_Statement__c createOneInvoiceStatement( Boolean withLineItem) { // Create one invoice statement Invoice_Statement__c testInvoice = createInvoiceStatement(); if (withLineItem == true) { // Create a merchandise item Merchandise__c m = createMerchandiseItem('Orange juice'); // Create one line item and associate it with the invoice statement. AddLineItem(testInvoice, m); } return testInvoice; } // Helper methods // private static Merchandise__c createMerchandiseItem(String merchName) { Merchandise__c m = new Merchandise__c( Name=merchName, Description__c='Fresh juice', Price__c=2, Total_Inventory__c=1000); insert m; return m; } private static Invoice_Statement__c createInvoiceStatement() { Invoice_Statement__c inv = new Invoice_Statement__c( 58 第 3 章: コンテキストでの Apex テストメソッドの追加 Description__c='Test Invoice'); insert inv; return inv; } private static Line_Item__c AddLineItem(Invoice_Statement__c inv, Merchandise__c m) { Line_Item__c lineItem = new Line_Item__c( Invoice_Statement__c = inv.Id, Merchandise__c = m.Id, Unit_Price__c = m.Price__c, Units_Sold__c = (Double)(10*Math.random()+1)); insert lineItem; return lineItem; } } 4. [Save (保存)] をクリックします。 もうひとこと... • このクラスには、createOneInvoiceStatement という public メソッドが 1 つ含まれます。このメソッド は、次のレッスンのテストメソッドでテストデータとして使用する請求書明細と商品品目を作成します。 このメソッドは、品目名を請求書に追加するかどうかを示す Boolean 引数を取ります。 • このクラスには、createOneInvoiceStatement で使用される 3 つのヘルパーメソッドも含まれます。こ れらのメソッドはすべて private で、このクラス内でのみ使用されます。 • Apexクラスにはテストデータ作成用の public メソッドを含めることができますが、この共通ユーティリティ クラスは @isTest アノテーションで定義されます。このアノテーションを使用することの利点は、クラス が 3 MB の組織コードサイズ制限の対象外となるという点です。このクラスに含まれる public メソッドは、 テストコードからのみコールできます。 テストメソッドの追加 テストに使用するデータを作成するためのユーティリティクラスを追加してテストメソッドからコールできる ようになりました。次はテストメソッドが含まれるクラスを作成しましょう。テストクラスとテストメソッド を追加する手順は、次のとおりです。 1. [Repository (リポジトリ)] タブの [Setup Entity Type (設定エンティティ種別)] セクションで、[Classes (クラス)] をクリックし、[New (新規)] をクリックします。 2. クラス名として「TestInvoiceStatementDeletion」と入力し、[OK] をクリックします。 3. 自動生成されたコードを削除し、次のコードを追加します。 @isTest private class TestInvoiceStatementDeletion { static testmethod void TestDeleteInvoiceWithLineItem() { // Create an invoice statement with a line item then try to delete it Invoice_Statement__c inv = TestDataFactory.createOneInvoiceStatement(true); 59 第 3 章: コンテキストでの Apex テストメソッドの追加 Test.startTest(); Database.DeleteResult result = Database.delete(inv, false); Test.stopTest(); // Verify invoice with a line item didn't get deleted. System.assert(!result.isSuccess()); } static testmethod void TestDeleteInvoiceWithoutLineItems() { // Create an invoice statement without a line item and try to delete it Invoice_Statement__c inv = TestDataFactory.createOneInvoiceStatement(false); Test.startTest(); Database.DeleteResult result = Database.delete(inv, false); Test.stopTest(); // Verify invoice without line items got deleted. System.assert(result.isSuccess()); } static testmethod void TestBulkDeleteInvoices() { // Create two invoice statements, one with and one with out line items // Then try to delete them both in a bulk operation, as might happen // when a trigger fires. List<Invoice_Statement__c> invList = new List<Invoice_Statement__c>(); invList.add(TestDataFactory.createOneInvoiceStatement(true)); invList.add(TestDataFactory.createOneInvoiceStatement(false)); Test.startTest(); Database.DeleteResult[] results = Database.delete(invList, false); Test.stopTest(); // Verify the invoice with the line item didn't get deleted System.assert(!results[0].isSuccess()); // Verity the invoice without line items did get deleted. System.assert(results[1].isSuccess()); } } 4. [Save (保存)] をクリックします。 もうひとこと... • このクラスは、@isTest アノテーションを使用して定義されています。このアノテーションは、前のレッ スンの共通テストユーティリティクラスの定義でも使用されていました。このレッスンでは、このアノテー ションを使用してクラスをテストクラスとしてマークし、Apex が実行できるテストメソッドを含めること ができるようにします。このアノテーションを使用せずに Apex クラス内にテストメソッドと他のコードを 混在させることもできますが、このアノテーションを使用することをお勧めします。 • クラスには 3 つのテストメソッドが含まれます。テストメソッドは、引数を取らない静的な最上位レベル のメソッドです。テストメソッドは、testmethod キーワードまたは @isTest アノテーションを使用して 定義されます。次のどちらの宣言形式も有効です。 60 第 3 章: コンテキストでの Apex テストメソッドの追加 testmethod キーワードを使用したテストメソッドの宣言: static testmethod void myTest() { // Add test logic } @isTest アノテーションを使用したテストメソッドの宣言: static @isTest void myTest() { // Add test logic } • 次に、このクラス内の各テストメソッドについて説明します。 – TestDeleteInvoiceWithLineItem: このテストメソッドは、トリガが本来の機能を実行しているかど うか、つまり、品目名を含む請求書明細が削除されないようにしているかどうかを確認します。テスト ファクトリメソッドを使用して品目名を含む請求書明細を作成し、Database.delete Apexメソッドを 使用して請求書を削除します。このメソッドが返す Database.DeleteResult オブジェクトは、操作 が正常に終了したかどうかを判定し、エラーのリストを取得するために使用できます。テストは isSuccess メソッドをコールして、false が返される (請求書が削除されていない) ことを確認します。 – TestDeleteInvoiceWithoutLineItems: このテストメソッドは、トリガが品目名を含まない請求書明 細の削除を阻止しないことを確認します。このテストメソッドは、品目名を含まない請求書明細を挿入 してからその請求書明細を削除します。前のメソッドと同様、テストファクトリメソッドをコールして テストデータを作成してから、delete 操作を行う Database.delete をコールします。このテストは、 Database.DeleteResult の isSuccess メソッドが true を返すことを確認します。 – TestDeleteInvoiceWithoutLineItems: この 3 つ目のテストメソッドは、請求書のリストを一括削除 します。このテストメソッドは、2 つの請求書明細を含むリストを作成します。最初の請求書明細には 1 つの品目名があり、2 つ目の請求書明細には品目名がありません。削除する請求書明細のリストを渡 して Database.delete をコールします。今回は、第 2 パラメータがあります。これは省略可能な Boolean パラメータで、一部の sObject の削除が失敗した場合にすべての sObject に対する delete 操作をロールバッ クする必要があるかどうかを示します。渡した値は false であるため、失敗した sObject があっても delete DML 操作全体をロールバックするのではなく、エラーが発生しなかった sObject は削除されます。 この場合、テストは Database.DeleteResult の isSuccess メソッドをコールして、最初の請求書 明細が削除されず、2 つ目の請求書明細が削除されたことを確認します。 • 各テストメソッドは Test.startTest/Test.stopTest ブロック内で delete DML 操作を実行します。各テ ストメソッドには、このようなブロックを 1 つのみ作成できます。このブロック内で実行されるすべての コードには、テストの他のコードとは別に、新しいガバナ制限のセットが割り当てられます。これにより、 テスト内の他の設定コードと同じ制限を共有しなくなるため、ガバナ制限をテストできます。このテスト では、一度に削除するレコードは 1 件か 2 件で、制限に達することはないためガバナ制限のテストは重要 ではありませんが、実際のテストステートメントは Test.startTest/Test.stopTest で囲むことをお勧 めします。Test.startTest をコールする前に、前提条件の設定とテストデータ作成を実行し、 Test.stopTest の後に検証を含めることができます。Test.stopTest をコールした後に、元のガバナ制 限が再び適用されます。 61 第 3 章: コンテキストでの Apex テストの実行とコードカバー率 テストの実行とコードカバー率 テストを追加できたので、テストを実行して結果を調べます。テストに合格するかどうかに加えて、コードの うち、テストの対象になっている割合 (コードカバー率) を調べることもできます。 Apex にはさまざまなテスト実行方法があります。[Apex クラス] ページでは、1 つのクラスのすべてのテスト、 またはすべてのクラスのすべてのテストを実行できます。[Apex テスト実行] ページでは、テストを非同期に実 行することもできます。この場合、後で結果を確認し、保存されたテスト結果にアクセスできます。さらに、 API オブジェクトを挿入してクエリする Apex コードを記述して、テストの非同期実行を開始することもできま す。 このチュートリアルの目的に合わせて、ここでは Salesforce ユーザインターフェースで [Apex クラス] ページを使 用し、コードカバー率の結果を調査します。 1. [設定] で、[開発] > [Apex クラス] をクリックします。 2. クラスのリストで [TestInvoiceStatementDeletion] リンクを見つけ、クリックします。 3. TestInvoiceStatementDeletion クラスの詳細ページで [Run Test (テストを実行)] をクリックします。 4. クラスのすべてのテストメソッドが実行されたら、[Apex テスト結果] ページが開き、テスト結果の概要が 表示されます。 ページの概要セクションに、3 つのテストがエラーなしで実行されたことが表示されます。つまり、すべて のテストに合格しました。コードカバー率は 100% です。これは、このテストでトリガ内のすべてのコード 行が実行されたという意味です。このセクションには、実行されたコードのデバッグログ出力が含まれる デバッグログへのリンクも表示されます。 5. 下へスクロールしてコードカバー率セクションを確認します。このセクションには、組織内のすべてのト リガとクラスのコードカバー率が表示されます。表示されたトリガのコードカバー率を確認できます。 62 第 3 章: コンテキストでの Apex まとめ 6. カバー率の数値をクリックすると、トリガの対象となるステートメントが表示されます。この場合、青い 2 行がコードの対象となったステートメントに対応しているのがわかります。 テストの対象外のステートメントは、赤い行で表示されます。この例では、カバー率は 100% であるため、 赤い行はありません。 7. [Apex Triggers (Apex トリガ)] ページに表示されているトリガのコードカバー率の結果を更新するには、[Apex クラス] をクリックし、[組織のコードカバー率を計算する] をクリックします。 8. [Apex トリガ] をクリックします。 トリガのコードカバー率である「100%」が、[Code Coverage (コードカバー率)] 列に表示されます。 または、[すべてのテストを実行] をクリックすると、組織のすべてのテストが実行され、ページ上のコー ドカバー率の結果が一挙に更新されます。 まとめ このチュートリアルでは、テストクラスおよびテストメソッドの構文と、@isTest アノテーションが付加さ れたテストメソッドが含まれるテストクラスを使用する利点について学習しました。テストデータファクトリ クラスを作成してテストデータを作成しました。すべてのテストを実行し、テスト結果とコードカバー率を確 認しました。さらに、Apexを別の組織にリリースするための要件として、テストカバー率を 75% 以上にするこ との重要性を学習しました。 ガバナ実行制限内での Apex の実行 前提条件: • sObject とデータベース 従来のソフトウェア開発と異なり、マルチテナントのクラウド環境である Force.com プラットフォームでソフ トウェアを開発すると、コードに拡張性を持たせる必要がなくなります。Force.comプラットフォームが自動で 拡張を行うためです。マルチテナントプラットフォームではリソースが共有されるため、Apexランタイムエン ジンは、一連のガバナ実行制限を適用して、1 つのトランザクションが共有リソースを独占しないようにしま す。Apexコードは、これらの事前定義された実行制限内で実行する必要があります。ガバナ制限を超えると、 処理できない実行時例外が発生します。コードで次のベストプラクティスに従うことで、この制限に達するの を回避できます。たとえば、100 枚の T シャツを洗わなければならないとします。T シャツを 1 枚ずつ、つまり 1 回の洗濯で 1 枚ずつ洗いますか、または何枚かずつまとめて数回の洗濯ですむようにしますか。クラウドで のコーディングの利点は、より効率的なコードを書いて、消費するリソースを減らす方法を学ぶことにありま す。 ガバナ実行制限は、トランザクション単位で適用されます。たとえば、1 つのトランザクションは SOQL クエリ を最大 100 回、DML ステートメントを最大 150 回発行できます。一度にキューに入れることができるか、有効 にできる一括処理ジョブの数など、こうしたトランザクション単位の制限が適用されない方法もあります。 特定のガバナ制限を超えないコードを記述するためには、次に示すいくつかのベストプラクティスがありま す。 63 第 3 章: コンテキストでの Apex ガバナ実行制限内での Apex の実行 DML コールを一括処理する 個々の sObject ではなく、sObject のリストに対して DML コールを行うと、DML ステートメント制限に達する可 能性が低くなります。次の最初の例は、DML 操作を一括処理しないコール方法です。その次の例は、推奨され る DML ステートメントのコール方法です。 例: 個々の sObject に対する DML コール for ループが、liList List 変数に含まれる品目名を反復処理します。品目名ごとに、Description__c 項目に新し い値を設定し、品目名を更新します。リストに150を超える品目が含まれる場合、151 回目の update コールは、 DML ステートメント制限の 150 を超えるため実行時例外を返します。これをどう修復すればよいでしょうか。 2 つ目の例は、単純な解決策です。 for(Line_Item__c li : liList) { if (li.Units_Sold__c > 10) { li.Description__c = 'New description'; } // Not a good practice since governor limits might be hit. update li; } 推奨される代替方法: sObject リストに対する DML コール この拡張バージョンの DML コールは、更新された品目名を含むリスト全体に対して更新を実行します。まず、 新しいリストを作成し、次にループ内ですべての更新品目名を新しいリストに追加します。続いて、新しいリ ストに対して一括更新を実行します。 List<Line_Item__c> updatedList = new List<Line_Item__c>(); for(Line_Item__c li : liList) { if (li.Units_Sold__c > 10) { li.Description__c = 'New description'; updatedList.add(li); } } // Once DML call for the entire list of line items update updatedList; より効率的な SOQL クエリ SOQL クエリを for ループブロック内に置くと、SOQL クエリが反復ごとに実行されて、100 回というトランザ クションあたりの SOQL クエリ数制限を超える可能性があるため、よい方法とはいえません。次の最初の例で は、SOQL クエリを Trigger.new の品目ごとに実行するため、非効率的です。代替方法の例では、SOQL クエ リを 1 回だけ使用して子品目を取得するクエリに変更されています。 例: 非効率的な子品目のクエリ この例の for ループは、Trigger.new に含まれるすべての請求書明細を反復処理します。ループ内で実行さ れるSOQLクエリは、各請求書明細の子品目名を取得します。100件を超える請求書明細が挿入または更新され 64 第 3 章: コンテキストでの Apex ガバナ実行制限内での Apex の実行 て Trigger.new に含まれている場合、SOQL 制限に達するため、実行時例外が発生します。2 つ目の例では、 1 回だけコールできる別の SOQL クエリを作成してこの問題を解決しています。 trigger LimitExample on Invoice_Statement__c (before insert, before update) { for(Invoice_Statement__c inv : Trigger.new) { // This SOQL query executes once for each item in Trigger.new. // It gets the line items for each invoice statement. List<Line_Item__c> liList = [SELECT Id,Units_Sold__c,Merchandise__c FROM Line_Item__c WHERE Invoice_Statement__c = :inv.Id]; for(Line_Item__c li : liList) { // Do something } } } 推奨される代替方法: SOQL クエリを 1 回のみ使用した子品目のクエリ この例では、品目ごとに SOQL クエリをコールするという問題を回避しています。変更された SOQL クエリで は、Trigger.new に含まれるすべての請求書明細を取得し、さらにネストしたクエリでその品目名を取得し ます。この方法では SOQL クエリが 1 回だけ実行され、制限内に収まっています。 trigger EnhancedLimitExample on Invoice_Statement__c (before insert, before update) { // Perform SOQL query outside of the for loop. // This SOQL query runs once for all items in Trigger.new. List<Invoice_Statement__c> invoicesWithLineItems = [SELECT Id,Description__c,(SELECT Id,Units_Sold__c,Merchandise__c from Line_Items__r) FROM Invoice_Statement__c WHERE Id IN :Trigger.newMap.KeySet()]; for(Invoice_Statement__c inv : invoicesWithLineItems) { for(Line_Item__c li : inv.Line_Items__r) { // Do something } } } SOQL for ループ レコードに対して 200 件のバッチ単位で操作を行うには、SOQL for ループを使用します。これにより、6 MB の ヒープサイズ制限を回避できます。この制限は、同期して実行されるコードに対するもので、非同期のコード 実行では制限がより厳しくなります。 例: for ループを使用しないクエリ 次の例の SOQL クエリでは、すべての商品品目を取得し、List 変数に保存します。返された商品品目のサイズが 大きく、大量のレコードが返された場合、ヒープサイズ制限に達する可能性があります。 List<Merchandise__c> ml = [SELECT Id,Name FROM Merchandise__c]; 推奨される代替方法: for ループを使用したクエリ 65 第 3 章: コンテキストでの Apex Apex のスケジュール実行 このヒープサイズ制限を回避するには、2 つ目のバージョンで SOQL for ループを使用し、返された結果を 200 レ コードのバッチ単位で反復処理します。これにより、ml List 変数が、クエリ結果のすべての品目名ではなく 200 品目を保持するため、サイズが小さくなり、また、バッチごとに再作成されます。 for (List<Merchandise__c> ml : [SELECT Id,Name FROM Merchandise__c]){ // Do something. } Apex ガバナ実行制限の完全なリストは、『Force.com Apex コード開発者ガイド』を参照してください。 Apex のスケジュール実行 Apex スケジューラを使用すると、Apex クラスを指定した日時に実行できます。これは、日次または週次のメ ンテナンス作業に最適です。スケジューラを利用するには、Schedulable インターフェースを実装する Apex クラスを記述し、特定のスケジュールで実行されるようにスケジュール設定します。 スケジュール可能なインターフェースを実装するクラスの追加 前提条件: • Warehouse カスタムオブジェクトの作成 • 開発者コンソールの使用 • sObject とデータベース このレッスンでは、Schedulable インターフェースを実装するクラスを作成します。つまり、このジョブは 指定された日時に実行されるようにスケジュールできます。 1. 開発者コンソールで、[ファイル] > [New (新規)] > [Apex クラス] をクリックします。 2. クラス名として「MySchedulableClass」と入力し、[OK] をクリックします。 3. 自動生成されたコードを削除し、次のコードを追加します。 global class MySchedulableClass implements Schedulable { global void execute(SchedulableContext ctx) { CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime FROM CronTrigger WHERE Id = :ctx.getTriggerId()]; System.debug(ct.CronExpression); System.debug(ct.TimesTriggered); Merchandise__c m = new Merchandise__c( Name='Scheduled Job Item', Description__c='Created by the scheduler', Price__c=1, Total_Inventory__c=1000); insert m; } } 4. [Save (保存)] をクリックします。 もうひとこと... 66 第 3 章: コンテキストでの Apex スケジュール可能なクラスのテストの追加 • クラスの宣言の末尾には、implements Schedulable が追加されています。これは、クラスが Schedulable インターフェースを実装しており、このインターフェースに含まれるメソッドを実装する 必要があることを示します。含まれるメソッドは、次の execute メソッド 1 つのみです。 global void execute(SchedulableContext sc){} このメソッドのパラメータは SchedulableContext オブジェクトです。このオブジェクトは、CronTrigger API オブジェクトの ID を返す getTriggerId メソッドを提供します。クラスがスケジュールされると、ス ケジュール済みジョブを表す CronTrigger オブジェクトが作成されます。 • CronTrigger オブジェクトは、スケジュール済みジョブに関する追加情報を取得するためにクエリされます。 Cron 式とこれまでジョブが実行された回数がデバッグログに書き出されます。 • 最後に、execute メソッドが商品レコードを作成します。 スケジュール可能なクラスのテストの追加 前提条件: • Apex 単体テスト スケジュール可能なクラスを追加したので、クラスがテスト対象となるようにテストメソッドも追加する必要 があります。このレッスンでは 1 つのテストメソッドが含まれるテストクラスを追加します。そのテストメ ソッドが System.Schedule をコールしてクラスをスケジュールします。 [Apex クラス] ページに切り替えて、テストクラスを作成します。テストはこのページから実行します。 1. [設定] で、[開発] > [Apex クラス] > [新規] をクリックします。 2. コードエディタボックスで、次のテストクラスを追加します。 @isTest private class TestSchedulableClass { // CRON expression: midnight on March 15. // Because this is a test, job executes // immediately after Test.stopTest(). public static String CRON_EXP = '0 0 0 15 3 ? 2022'; static testmethod void test() { Test.startTest(); // Schedule the test job String jobId = System.schedule('ScheduleApexClassTest', CRON_EXP, new MySchedulableClass()); // Get the information from the CronTrigger API object CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime FROM CronTrigger WHERE id = :jobId]; // Verify the expressions are the same System.assertEquals(CRON_EXP, ct.CronExpression); 67 第 3 章: コンテキストでの Apex ジョブのスケジュールとスケジュール済みジョブの監視 // Verify the job has not run System.assertEquals(0, ct.TimesTriggered); // Verify the next time the job will run System.assertEquals('2022-03-15 00:00:00', String.valueOf(ct.NextFireTime)); // Verify the scheduled job hasn't run yet. Merchandise__c[] ml = [SELECT Id FROM Merchandise__c WHERE Name = 'Scheduled Job Item']; System.assertEquals(ml.size(),0); Test.stopTest(); // Now that the scheduled job has executed after Test.stopTest(), // fetch the new merchandise that got added. ml = [SELECT Id FROM Merchandise__c WHERE Name = 'Scheduled Job Item']; System.assertEquals(ml.size(), 1); } } 3. [Save (保存)] をクリックします。 4. [Run Test (テストを実行)] をクリックしてテストメソッドを実行します。 もうひとこと... • このテストメソッドは、System.schedule メソッドをコールして MySchedulableClass クラスをスケ ジュールします。System.Schedule メソッドは、ジョブの名前、ジョブの実行予定日時を表すために使 用する式、クラスの名前という 3 つの引数を取ります。System.schedule メソッドでは、すべてのスケ ジュールの基準としてユーザのタイムゾーンが使用されます。 • System.schedule へのコールは Test.startTest と Test.stopTest ブロック内に含まれます。これに より、cron 式で指定されたスケジュールに関係なく、ジョブは Test.stopTest コールの後に実行されま す。Test.startTest および Test.stopTest で囲まれたブロックに含まれる非同期コードは Test.stopTest の後に同期して実行されます。 • 最後に、テストメソッドは、スケジュール済みクラスによって新しい商品品目が追加されたことを確認し ます。 ヒント: • System.Schedule メソッドは、ジョブの名前、ジョブの実行予定日時を表すために使用する式、ク ラスの名前という 3 つの引数を取ります。 • 一度にスケジュール設定できるクラスの数は 100 です。 ジョブのスケジュールとスケジュール済みジョブの監視 スケジュール可能クラスを作成してテストする方法を確認したので、ユーザインターフェースを使用してクラ スをスケジュールする方法を見ていきましょう。また、組織のスケジュール済みジョブのリストを表示する方 法も学習します。 68 第 3 章: コンテキストでの Apex まとめ 1. [Apex クラス] をクリックして [Apex クラス] ページに戻ります。 2. [Apex をスケジュール] をクリックします。 3. ジョブ名として「TestSchedulingApexFromTheUI」と入力します。 4. Apexクラスの横にあるルックアップボタンをクリックし、検索語に「*」と入力して、スケジュール可能な すべてのクラスのリストを取得します。検索結果の中にある、MySchedulableClass をクリックします。 5. [毎週] または [毎月] の頻度を選択して、必要な頻度を設定します。 6. 開始日と終了日、適切な開始時刻を選択します。 スケジュール済み Apex ジョブのスケジュールは、ユーザのタイムゾーンに対して相対的に設定されます。 7. [保存] をクリックします。 8. [スケジュール済みジョブ] ページに移動するには、[設定] から、[監視] > [スケジュール済みジョブ] または [ジョブ] > [スケジュール済みジョブ] をクリックします。 ジョブがジョブキューのリストに含まれていることを確認できます。 9. ジョブの名前の横にある [管理] をクリックします。 ページに、ジョブの実行スケジュールを含む、ジョブの詳細が表示されます。 まとめ このチュートリアルでは、Schedulable インターフェースを実装するクラスを作成しました。また、そのテ ストを追加しました。最後に、クラスをスケジュールして指定された日時に実行する方法と、ユーザインター フェースでスケジュール済みジョブを表示する方法を学習しました。 スケジュール済みジョブは、定期的にメンテナンス作業を実行する場合に非常に便利です。 Apex 一括処理 Apex一括処理クラスを使用すると、レコードをバッチ単位で非同期に一括処理できます。データの整理やアー カイブなど、処理するレコードの数が多い場合、Apex一括処理が適しています。一括処理クラスを呼び出すご とに、ジョブは Apex の実行待ちジョブキューに置かれます。 一括処理クラスの実行ロジックは、レコードのバッチごとに 1 回コールされます。デフォルトのバッチサイズ は 200 レコードです。カスタムバッチサイズも指定できます。さらに、各バッチ実行は、別個のトランザク ションとみなされます。新しいレコードのバッチごとに、新しいガバナ制限のセットが適用されます。このた め、コードをガバナ実行制限内に抑えやすくなります。一括処理が別個のトランザクションとして扱われるこ とのもう 1 つの利点は、レコードのバッチの部分処理が可能になることです。あるバッチの処理が失敗した場 合でも、正常に処理された他のすべての一括処理トランザクションが影響を受けたり、ロールバックされたり することはありません。 69 第 3 章: コンテキストでの Apex Apex 一括処理 Apex 一括処理構文 Apex一括処理クラスを記述するには、クラスに Database.Batchable インターフェースを実装する必要があ ります。クラス宣言には、implements キーワードに続けて Database.Batchable<sObject> を含める必要 があります。次に例を示します。 global class CleanUpRecords implements Database.Batchable<sObject> { さらに次の 3 つのメソッドも実装する必要があります。 • start メソッド global (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {} start メソッドは、Apex 一括処理ジョブの開始時にコールされます。このメソッドは、インターフェース メソッド execute に渡すレコードまたはオブジェクトを収集します。 • execute メソッド: global void execute(Database.BatchableContext BC, list<P>){} execute メソッドは、メソッドに渡すレコードのバッチごとにコールされます。データの処理単位ごとに 必要な処理をすべて実行する場合に、このメソッドを使用します。 このメソッドは次を取得します。 – Database.BatchableContext オブジェクトへの参照。 – List<sObject> などの sObjects のリスト、またはパラメータ化された型のリスト。 Database.QueryLocator を使用している場合は、返されたリストが使用されます。 レコードのバッチが start メソッドから受け取った順序で実行される保証はありません。 • finish メソッド global void finish(Database.BatchableContext BC){} finish メソッドは、すべてのバッチが処理された後にコールされます。確認メールの送信や後処理操作 を行う場合に、このメソッドを使用します。 一括処理クラスの呼び出し 一括処理クラスを呼び出すには、最初に一括処理クラスをインスタンス化し、一括処理クラスのインスタンス を使用して Database.executeBatch をコールします。 BatchClass myBatchObject = new BatchClass(); Database.executeBatch(myBatchObject); このチュートリアルの各ステップでは、一括処理クラスの作成とテストの方法、および一括処理ジョブを呼び 出す方法を学習します。 70 第 3 章: コンテキストでの Apex Apex 一括処理クラスの追加 Apex 一括処理クラスの追加 前提条件: • 開発者コンソールの使用 このレッスンでは、Database.Batchable インターフェースを実装する Apex 一括処理クラスを作成します。 この一括処理クラスは、start メソッドで渡されるレコードをクリーンアップします。 1. 開発者コンソールで、[ファイル] > [New (新規)] > [Apex クラス] をクリックします。 2. クラス名として「CleanUpRecords」と入力し、[OK] をクリックします。 3. 自動生成されたコードを削除し、次のコードを追加します。 global class CleanUpRecords implements Database.Batchable<sObject> { global final String query; global CleanUpRecords(String q) { query = q; } global Database.QueryLocator start(Database.BatchableContext BC){ return Database.getQueryLocator(query); } global void execute( Database.BatchableContext BC, List<sObject> scope){ delete scope; Database.emptyRecycleBin(scope); } global void finish(Database.BatchableContext BC){ AsyncApexJob a = [SELECT Id, Status, NumberOfErrors, JobItemsProcessed, TotalJobItems, CreatedBy.Email FROM AsyncApexJob WHERE Id = :BC.getJobId()]; // Send an email to the Apex job's submitter // notifying of job completion. Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage(); String[] toAddresses = new String[] {a.CreatedBy.Email}; mail.setToAddresses(toAddresses); mail.setSubject('Record Clean Up Status: ' + a.Status); mail.setPlainTextBody ('The batch Apex job processed ' + a.TotalJobItems + ' batches with '+ a.NumberOfErrors + ' failures.'); Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail }); } } 4. [Save (保存)] をクリックします。 71 第 3 章: コンテキストでの Apex Apex 一括処理クラスのテストの追加 もうひとこと... • この一括処理クラスには、query 変数で指定されたクエリに基づいてレコードが渡されます。このクエリ 変数は、このクラスのコンストラクタに設定されます。 • レコードのバッチごとに、このクラスの 3 つのメソッドが、start メソッド、execute メソッド、finish メソッドの順で実行されます。 • start は、execute メソッドで処理するレコードのバッチを提供するため、 Database.getQueryLocator(query); をコールし、処理するレコードのリストを返します。 Database.QueryLocator オブジェクトで返せるレコードの最大数は、5000万件です。 • 一括処理するレコードのリストは、execute メソッドの第 2 パラメータで渡されます。execute メソッド は、delete DML ステートメントでレコードを削除します。削除されたレコードはごみ箱に 15 日間保管さ れるため、このメソッドはさらにごみ箱を空にしてこれらのレコードを完全に削除します。 • Apex一括処理ジョブが呼び出されると、新しいレコードが AsyncApexJob テーブルに追加されます。この テーブルには、一括処理ジョブに関する情報 (状況、処理済みバッチ数、処理対象バッチ合計数など) が含 まれます。finish メソッドは、ジョブの送信者にジョブの完了を確認するためのメールを送信します。 AsyncApexJob オブジェクトへのクエリを実行し、ジョブの状況、送信者のメールアドレスなどの情報を 取得します。続いて、新しいメールメッセージを作成し、Messaging.SingleEmailMessage メソッドを 使用して送信します。 次に、この一括処理クラスを呼び出すテストメソッドを追加します。 Apex 一括処理クラスのテストの追加 前提条件: • Warehouse カスタムオブジェクトの作成 • sObject とデータベース • Apex 単体テスト このレッスンでは、CleanUpRecords 一括処理クラスのテストクラスを追加します。このクラスのテストは、 一括処理ジョブを呼び出し、これまでに購入されていない商品レコードをすべて削除したことを確認します。 1. [Repository (リポジトリ)] タブの [Setup Entity Type (設定エンティティ種別)] セクションで、[Classes (クラス)] をクリックし、[New (新規)] をクリックします。 2. クラス名として「TestCleanUpBatchClass」と入力し、[OK] をクリックします。 3. 自動生成されたコードを削除し、次のコードを追加します。 @isTest private class TestCleanUpBatchClass { static testmethod void test() { // The query used by the batch job. String query = 'SELECT Id,CreatedDate FROM Merchandise__c ' + 'WHERE Id NOT IN (SELECT Merchandise__c FROM Line_Item__c)'; // Create some test merchandise items to be deleted // by the batch job. Merchandise__c[] ml = new List<Merchandise__c>(); 72 第 3 章: コンテキストでの Apex Apex 一括処理クラスのテストの追加 for (Integer i=0;i<10;i++) { Merchandise__c m = new Merchandise__c( Name='Merchandise ' + i, Description__c='Some description', Price__c=2, Total_Inventory__c=100); ml.add(m); } insert ml; Test.startTest(); CleanUpRecords c = new CleanUpRecords(query); Database.executeBatch(c); Test.stopTest(); // Verify merchandise items got deleted Integer i = [SELECT COUNT() FROM Merchandise__c]; System.assertEquals(i, 0); } } 4. [Save (保存)] をクリックします。 もうひとこと... • このテストクラスには、test という 1 つのテストメソッドが含まれます。このテストメソッドは、最初に クエリ文字列を作成し、CleanUpRecords のコンストラクタに渡します。これまでに購入されていない商 品品目には、品目名が関連付けられていないはずなので、SOQL クエリでは次の条件を指定します。 WHERE Id NOT IN (SELECT Merchandise__c FROM Line_Item__c) サブクエリ SELECT Merchandise__c FROM Line_Item__c は、品目名で参照されているすべての商品品目のセットを取得します。クエリでは WHERE 句に NOT IN 演算 子を使用しているため、品目名で参照されていない商品品目が返されます。 • テストメソッドは品目名が関連付けられていない 10 件の商品品目を挿入してから、一括処理クラスメソッ ドでクリーンアップします。テストメソッドが実行できるバッチは合計で 1 つのみであるため、挿入する レコードの数がデフォルトのバッチサイズである 200 件よりも少なくなっています。 • 次に、一括処理クラスが次のステートメントを含むクエリでインスタンス化されます。ここでは query 変 数が CleanUpRecords のコンストラクタに渡されます。 CleanUpRecords c = new CleanUpRecords(query); • 一括処理クラスを呼び出すために、Database.executeBatch をコールし、一括処理クラスのインスタン スを渡します。 Database.executeBatch(c); • Database.executeBatch へのコールは Test.startTest と Test.stopTest で囲まれたブロック内に 含まれます。これは、一括処理ジョブをテストメソッドで実行するために必要な条件です。ジョブは、 73 第 3 章: コンテキストでの Apex 一括処理ジョブの実行 Test.stopTest へのコールの後に実行されます。Test.startTest および Test.stopTest で囲まれた ブロックに含まれる非同期コードは Test.stopTest の後に同期して実行されます。 • 最後に、テストは商品品目の件数が 0 であることをチェックして、このテストで作成されたすべてのテス ト商品品目が削除されたことを確認します。 • 一括処理クラスの finish メソッドは状況を通知するメールメッセージを送信しますが、メールメッセー ジはテストメソッドからは送信されないため、この場合はメールは送信されません。 一括処理ジョブの実行 一括処理クラスは、トリガ、クラス、または開発者コンソールから呼び出すことができます。指定したスケ ジュールで一括処理ジョブを実行する必要が生じる場合もあります。ここでは、レッスン 1 で作成した一括処 理クラスを、すぐに結果が得られるように開発者コンソールから送信する方法を説明します。さらに、スケ ジューラクラスも作成して一括処理クラスをスケジュールできるようにします。 まず、品目名が関連付けられていない商品レコードをいくつか組織に作成します。以前にテストで作成したレ コードは保持されていないため、新しいレコードを作成して一括処理ジョブで処理するレコードを用意しま す。 1. [Logs (ログ)] タブをクリックし、[Execute (実行)] ウィンドウで次のコードを実行します。 Merchandise__c[] ml = new List<Merchandise__c>(); for (Integer i=0;i<250;i++) { Merchandise__c m = new Merchandise__c( Name='Merchandise ' + i, Description__c='Some description', Price__c=2, Total_Inventory__c=100); ml.add(m); } insert ml; 2. [Execute (実行)] をクリックします。 これにより 250 件の商品品目が作成されるため、一括処理クラスを最初の 200 レコードに対して 1 回、残り の 50 レコードに対して 1 回、合わせて 2 回実行することになります。 3. では、開発者コンソールから Database.executeBatch をコールして一括処理クラスを送信しましょう。 [Execute (実行)] ウィンドウから次のコードを実行します。 String query = 'SELECT Id,CreatedDate FROM Merchandise__c ' + 'WHERE Id NOT IN (SELECT Merchandise__c FROM Line_Item__c)'; CleanUpRecords c = new CleanUpRecords(query); Database.executeBatch(c); ジョブが完了するとメールで通知されます。メールが到着するまで数分かかる場合があります。メールに は、2 つのバッチが実行されたことが記載されています。 4. 一括処理ジョブ実行の状況を表示するには、[設定] から、[監視] > [Apex ジョブ] または [ジョブ] > [Apex ジョ ブ] をクリックします。ジョブは終了しているため、状況は「Completed」と表示され、2 つのバッチが処理 されたことを確認できます。 74 第 3 章: コンテキストでの Apex まとめ 5. 一括処理ジョブをプログラムでスケジュールするには、Schedulable インターフェースを実装するクラス を作成し、そのクラスの execute メソッドから一括処理クラスを呼び出します。[設定] から、[開発] > [Apex クラス] > [新規] をクリックします。 6. コードエディタボックスに、次のクラス定義を追加します。 global class MyScheduler implements Schedulable { global void execute(SchedulableContext ctx) { // The query used by the batch job. String query = 'SELECT Id,CreatedDate FROM Merchandise__c ' + 'WHERE Id NOT IN (SELECT Merchandise__c FROM Line_Item__c)'; CleanUpRecords c = new CleanUpRecords(query); Database.executeBatch(c); } } 7. 「ジョブのスケジュールとスケジュール済みジョブの監視」と同様の手順に従い、MyScheduler クラスを スケジュールします。 まとめ このチュートリアルでは、データクリーンアップを行う Apex 一括処理クラスを作成しました。その後、テス トメソッドを作成して実行し、一括処理クラスをテストしました。また、一括処理クラスをスケジュールする 方法も学習しました。 Apex一括処理を使用すると、レコードをバッチ単位で一括処理できるため、大量のレコードを処理する場合に 便利です。 Apex REST Apex クラスを REST リソースとして公開することで、Force.com プラットフォームまたは Database.com 上にカスタ ム REST Web サービス API を作成できます。クライアントアプリケーションは、REST を使用して Apex クラスのメ ソッドをコールし、プラットフォームで Apex コードを実行できます。 Apex REST は、REST 要求および応答で送信されるリソース形式として XML と JSON の両方をサポートしています。 デフォルトでは、Apex REST は JSON を使用してリソースを表します。 認証には、Apex REST は OAuth 2.0 と Salesforce セッションをサポートします。このチュートリアルでは、ワーク ベンチを使用して REST クライアントをシミュレーションします。ワークベンチでは、ログインユーザのセッ ションを、Apex REST メソッドをコールするための認証メカニズムとして使用します。 75 第 3 章: コンテキストでの Apex REST リソースとしてのクラスの追加 メモ: ワークベンチは、無料のオープンソースのコミュニティサポートツールです (ワークベンチのヘル プページを参照)。Salesforceは、デモ目的でのみワークベンチのホスト型インスタンスを提供しています。 このワークベンチのホスト型インスタンスを本番データベースのデータへのアクセスに使用することは お勧めしません。ワークベンチを本番データベースに使用する場合は、各自で所有するリソースを使用 してワークベンチをダウンロード、ホスト、および設定してください。 REST リソースとしてのクラスの追加 前提条件: • Warehouse カスタムオブジェクトの作成 • 開発者コンソールの使用 • sObject とデータベース 2 つのメソッドを含むクラスを追加し、Apex REST を使用して公開しましょう。 1. 開発者コンソールで、[ファイル] > [New (新規)] > [Apex クラス] をクリックします。 2. クラス名として「MerchandiseManager」と入力し、[OK] をクリックします。 3. 自動生成されたコードを削除し、次のコードを追加します。 @RestResource(urlMapping='/Merchandise/*') global with sharing class MerchandiseManager { @HttpGet global static Merchandise__c getMerchandiseById() { RestRequest req = RestContext.request; String merchId = req.requestURI.substring( req.requestURI.lastIndexOf('/')+1); Merchandise__c result = [SELECT Name,Description__c,Price__c,Total_Inventory__c FROM Merchandise__c WHERE Id = :merchId]; return result; } @HttpPost global static String createMerchandise(String name, String description, Decimal price, Double inventory) { Merchandise__c m = new Merchandise__c( Name=name, Description__c=description, Price__c=price, Total_Inventory__c=inventory); insert m; return m.Id; } } 4. [Save (保存)] をクリックします。 もうひとこと... 76 第 3 章: コンテキストでの Apex Apex REST POST メソッドを使用したレコードの作成 • クラスはグローバルで、@RestResource(urlMapping='/Invoice_Statement__c/*') アノテーション を使用して定義されます。REST API として公開する Apex クラスは、グローバルであり、@RestResource ア ノテーションが付加されている必要があります。@RestResource アノテーションのパラメータである urlMapping は、リソースを一意に識別するために使用され、基本 URL https://instance.salesforce.com/services/apexrest/ に対する相対的 URL を指定します。基本 URL と urlMapping 値は、クライアントが REST 要求で送信する URI を形成します。この場合、urlMapping の 値には、ワイルドカード文字のアスタリスクが含まれます。つまり、リソース URI の /Merchandise/ の 後には任意の値を含めることができます。このチュートリアルのステップ 3 では、レコードを取得するた めに URI に ID 値を付加します。 • クラスには、Apex REST アノテーションで定義された 2 つのグローバル静的メソッドが含まれます。すべて の Apex REST メソッドはグローバルに静的である必要があります。 • 最初のクラスメソッド getMerchandiseById は @HttpGet アノテーションを使用して定義されます。 – @HttpGet アノテーションは、クライアントから HTTP GET 要求が送信されるとコールされる REST API と してメソッドを公開します。 – このメソッドは、クライアントが要求 URI で送信した ID に対応する商品品目を返します。 – また、要求と要求 URI を Apex 静的 RestContext クラスを介して取得します。 – 続いて、URI を解析し、最後の「/ 」の後に渡された値を検出し、SOQL クエリを実行して、この ID の商 品レコードを取得します。最後に、このレコードを返します。 • 2 つ目のクラスメソッド createMerchandise は、@HttpPost アノテーションを使用して定義されます。 このアノテーションは、クライアントから HTTP POST 要求が送信されるとコールされる REST API としてメ ソッドを公開します。このメソッドは、クライアントが送信した指定データを使用して商品レコードを作 成します。また、insert DML 操作をコールして、新しいレコードをデータベースに挿入し、新しい商品レ コードの ID をクライアントに返します。 Apex REST POST メソッドを使用したレコードの作成 このレッスンでは、ワークベンチで REST Explorer を使用し、REST クライアント要求を送信して新しい商品レコー ドを作成します。この要求は、レッスン 1 で実装した Apex REST メソッドの 1 つを呼び出します。 ワークベンチの REST Explorer は REST クライアントをシミュレーションします。ログインユーザのセッションを、 Apex REST メソッドをコールする認証メカニズムとして使用します。 前のチュートリアルですでにサンプルデータをワークベンチで設定している場合、この手順の最初の数ステッ プを省略してもかまいません。 1. https://developer.salesforce.com/page/Workbench に移動します。 2. ログイン情報の入力を求められたら、ログイン情報を入力し、[Login (ログイン)] をクリックします。 3. [Environment (環境)] で [Production (本番)] を選択します。 4. サービスの利用規約に同意し、[Salesforce でログイン] をクリックします。 5. [Allow (許可)] をクリックして、ワークベンチが自分の情報にアクセスできるようにします。 6. ログインしたら、[utilities] > [REST Explorer] をクリックします。 7. [POST (投稿)] をクリックします。 77 第 3 章: コンテキストでの Apex Apex REST GET メソッドを使用したレコードの取得 8. REST Explorer では、組織のインスタンス URL に対する相対パスを指定する必要があるため、インスタンス URL に追加するパスのみを入力します。URL の入力ボックスで、デフォルトの URL を /services/apexrest/Merchandise/ で置き換えます。 9. リクエストボディに、挿入するオブジェクトを示す次の JSON 文字列を挿入します。 { "name" : "Eraser", "description" : "White eraser", "price" : 0.75, "inventory" : 1000 } 作成するオブジェクトの項目名は、コールするメソッドのパラメータ名と大文字小文字を含め一致させる 必要があります。 10. [Execute (実行)] をクリックします。 これにより、createMerchandise メソッドがコールされます。応答には、新しい商品レコードの ID が含 まれます。 11. 応答から ID 値を取得するには、[Show Raw Response (未加工の応答を表示)] をクリックし、応答の下部に表 示される ID 値を引用符なしでコピーします。たとえば、"a04R00000007xX1IAI" などが表示されていま すが、値はそれぞれ異なります。 次のレッスンでは、この ID を使用して挿入したレコードを取得します。 Apex REST GET メソッドを使用したレコードの取得 このレッスンでは、ワークベンチを使用して REST クライアント要求を送信し、前のレッスンで作成した新し い商品レコードを取得します。この要求は、レッスン 1 で実装した Apex REST メソッドの 1 つを呼び出します。 1. REST Explorer で [GET (取得)] をクリックします。 2. URL の入力ボックスで、このチュートリアルのレッスン 2 からコピーしたレコードの ID を URL の末尾に付加 します (/services/apexrest/Merchandise/)。 3. [Execute (実行)] をクリックします。 これにより、getMerchandiseById メソッドがコールされます。返される応答には、新しい商品レコード の項目が含まれます。 78 第 3 章: コンテキストでの Apex まとめ 4. 必要に応じて、[Show Raw Response (未加工の応答を表示)] をクリックし、HTTP ヘッダーと JSON 形式のレス ポンスボディを含む、応答全体を表示します。 まとめ このチュートリアルでは、Apex クラスを記述し、REST リソースとして公開して、カスタムの REST ベースの API を作成しました。クラスの 2 つのメソッドは、HTTP GET 要求と POST 要求を受信するとコールされます。ま た、ワークベンチの REST Explorer を使用して実装したメソッドをコールし、未加工の JSON 応答を表示する方法 も学びました。 Apex コントローラを使用する Visualforce ページ Visualforce は、Force.com プラットフォーム用のコンポーネントベースのユーザインターフェースフレームワー クです。Visualforceを使用すると、HTML に似たタグベースのマークアップ言語、拡張と再利用が可能なコンポー ネントのライブラリ、Apexベースのコントローラモデルを含むビューフレームワークにより、高度なユーザイ ンターフェースを構築できます。Visualforce は、Model-View-Controller (MVC) スタイルのユーザインターフェースを サポートするため、非常に柔軟性があります。 Visualforce には、組織で使用できるすべての sObject の標準コントローラが含まれており、Visualforce 以外のコー ドを記述することなく、一般機能を処理する Visualforce ページを作成できます。高度にカスタマイズされたア プリケーションの場合、Visualforce では標準コントローラを拡張したり、独自の Apex コードに置き換えたりで きます。Visualforce アプリケーションの使用を社内のみに制限したり、Web に公開したりできます。 このチュートリアルでは、Visualforce を使用して、単純な店舗ページを作成します。最初に、Visualforce の簡単 な概要を知るためにApexを使用しない単純な商品リストページを作成します。次に、単純なショッピングカー 79 第 3 章: コンテキストでの Apex Visualforce 開発モードの有効化 トなどいくつかの機能を追加して、Visualforce が Apex で記述されたコントローラにどのように接続されるかを 確認します。 Visualforce 開発モードの有効化 Visualforce の使用を開始する最も簡単な方法は、開発モードを有効にする方法です。開発モードでは、ブラウ ザに Visualforce ページエディタが組み込まれます。Visualforce ページエディタでは、コードの表示と編集、およ びページのプレビューを同時に行うことができます。開発モードではまた、コントローラと拡張機能を編集す るための Apex エディタも追加されます。 1. 任意のSalesforceページ上部で、名前の横にある下向き矢印をクリックします。名前の下にあるメニューで、 [設定] または [私の設定] のどちらか表示される方を選択します。 2. 左ペインで、次のいずれかを選択します。 • [設定] をクリックした場合は、[私の個人情報] > [個人情報] を選択します。 • [私の設定] をクリックした場合は、[個人用] > [高度なユーザの詳細] を選択します。 3. [編集] をクリックします。 4. [開発モード] チェックボックスをオンにします。 5. [保存] をクリックします。 開発モードを有効にすると、すべての Visualforce ページで、ブラウザウィンドウの下部に開発モードフッター が表示されます。 単純な Visualforce ページの作成 このレッスンでは、「Hello World」と同等の非常に単純な Visualforce ページを新しく作成します。 1. ブラウザで、テキスト「/apex/Catalog」をSalesforceインスタンスの URL に追加します。たとえば、Salesforce インスタンスが https://na1.salesforce.com の場合、新しい URL は https://na1.salesforce.com/apex/Catalog になります。 エラーメッセージ「Page Catalog does not exist (Catalog ページは存在しません)」が表示されます。 2. [Create Page Catalog (ページカタログを作成)] リンクをクリックして、新しいページを作成します。 いくつかのデフォルトコードを含む Catalog ページが作成されます。 3. ページエディタの上部に新しいページのプレビュー、下部にコードが表示されます。次のようなページが 表示されます。 80 第 3 章: コンテキストでの Apex Visualforce ページでの商品データの表示 ページエディタが折りたたまれている場合は、ブラウザウィンドウの右下にある [Expand (展開)] ( ンをクリックします。 ) ボタ 4. ページのヘッダーに「Congratulations」と表示したくない場合は、<h1> タグの内容を「Product Catalog」に変 更し、コメントとその他のプレーンテキストを削除します。ページのコードは次のようになります。 <apex:page> <h1>Product Catalog</h1> </apex:page> タグの間に追加のテキストと HTML を追加できますが、Visualforce ページは <apex:page> で始まり、 </apex:page> で終わる必要があります。 5. ページエディタの上部にある [Save (保存)] ボタン ( ) をクリックします。 変更が反映されたページが再読み込みされます。 これでページのコードは標準 HTML のように見えます。Visualforce ページは、HTML タグ (<h1> など) に、<apex:> で始まる Visualforce 固有のタグを組み合わせているためです。 Visualforce ページでの商品データの表示 前提条件: • Warehouse カスタムオブジェクトの作成 • サンプルデータの作成 このレッスンでは、最初の Visualforce ページを拡張して、販売する商品のリストを表示します。このページは かなり単純に見えますが、たくさんの作業が必要なため Apex を使用するレッスンまで手早く進めましょう。 Visualforce の詳細は、『Visualforce ワークブック』を参照してください。 1. ブラウザで、https://<your-instance>.salesforce.com/apex/Catalog にある商品カタログページ を開き、ページエディタがまだ開いていない場合は開きます。 81 第 3 章: コンテキストでの Apex Visualforce ページでの商品データの表示 2. <apex:page> タグを編集して Merchandise__c 標準コントローラを有効にするようにコードを変更します。 <apex:page standardController="Merchandise__c"> これにより、ページが組み込みのコントローラを使用してプラットフォーム上の Merchandise__c カスタムオ ブジェクトに接続されます。このコントローラは、Merchandise__c オブジェクトの参照、更新、新規作成な ど、多くの基本機能を提供します。 3. 次に、標準リストコントローラ定義を追加します。 <apex:page standardController="Merchandise__c" recordSetVar="products"> これにより、コントローラが Merchandise__c レコードのリストを一度に処理できるように設定されます。た とえば、カタログの商品のリストを表示できます。これが必要な作業です。 4. [保存] をクリックします。または、キーボードを使用する場合は Ctrl + S キーを押します。 ページが再読み込みされ、[Merchandise] タブが表示されていれば、そのタブが選択されます。それ以外の場 合、ページに外観上の変化はありません。ただし、コントローラを使用するようにページを設定し、変数 products を定義したので、変数がページの本文で使用できるようになり、Merchandise__c レコードのリス トを表します。 5. 2 つの <apex:page> タグで囲まれたコードを、商品リストを保持する次のページブロックで置き換えま す。 <apex:pageBlock title="Our Products"> <apex:pageBlockSection> (Products Go Here) </apex:pageBlockSection> </apex:pageBlock> pageBlock タグと pageBlockSection タグは、ページ上に、プラットフォームの標準ビジュアルスタイ ルと同じユーザインターフェース要素を作成します。 メモ: これ以降、最新のコードを反映したページを確認する場合は適宜変更内容を保存してください。 6. それでは実際の商品リストを追加します。(Products Go Here) プレースホルダを選択し、それを削除し ます。「<apex:pageB」と入力を開始し、マウスまたは矢印キーを使用してドロップダウンリストから apex:pageBlockTable を選択し、Enter キーを押します。 82 第 3 章: コンテキストでの Apex Visualforce ページでの商品データの表示 エディタが開始タグと終了タグの両方を挿入し、カーソルはその間の挿入箇所に残ります。 7. 次に、属性を pageBlockTable タグに追加する必要があります。value 属性は、pageBlockTable コン ポーネントによる反復処理の対象となる品目のリストを示します。var 属性は、1 回の反復ごとにそのリ ストの各品目を pitem 変数に割り当てます。次の属性をタグに追加します。 <apex:pageBlockTable value="{!products}" var="pitem"> 8. 次に、各列を定義し、pitem 変数の適切な項目を検索して列のデータを取得するようにします。次のコー ドを pageBlockTable の開始タグと終了タグの間に追加します。 <apex:pageBlockTable value="{!products}" var="pitem"> <apex:column headerValue="Product"> <apex:outputText value="{!pitem.Name}"/> </apex:column> </apex:pageBlockTable> 9. [保存] をクリックすると、商品リストが表示されます。 headerValue 属性によって列のヘッダータイトルが設定され、その下に各商品レコードに対して 1 行ず つ、リストが表示されます。式 {!pitem.Name} は、現在の行の Name 項目を表示することを示します。 10. 最初の列の終了タグの後に、さらに次の 2 つの列を追加します。 <apex:column headerValue="Description"> <apex:outputField value="{!pitem.Description__c}"/> </apex:column> <apex:column headerValue="Price"> <apex:outputField value="{!pitem.Price__c}"/> </apex:column> 83 第 3 章: コンテキストでの Apex Visualforce ページでのカスタム Apex コントローラの使 用 11. 列が 3 つになると、テーブルの幅が狭いため、リストは圧縮されます。<apex:pageBlockSection> タグ を変更して幅を広くします。 <apex:pageBlockSection columns="1"> この変更により、セクションが 2 列から 1 列に変更され、1 つの列の幅が広くなります。 12. コードは次のようになります。 <apex:page standardController="Merchandise__c" recordSetVar="products"> <apex:pageBlock title="Our Products"> <apex:pageBlockSection columns="1"> <apex:pageBlockTable value="{!products}" var="pitem"> <apex:column headerValue="Product"> <apex:outputText value="{!pitem.Name}"/> </apex:column> <apex:column headerValue="Description"> <apex:outputField value="{!pitem.Description__c}"/> </apex:column> <apex:column headerValue="Price"> <apex:outputField value="{!pitem.Price__c}"/> </apex:column> </apex:pageBlockTable> </apex:pageBlockSection> </apex:pageBlock> </apex:page> これで商品カタログが完成しました。 もうひとこと... • pageBlockTable コンポーネントは、行を含むテーブルを作成し、各行はリストを反復処理して出力され ます。このページに使用した標準コントローラは Merchandise__c に設定され、recordSetVar は products に設定されました。その結果、コントローラはデータベースから取得された商品レコードを使 用して商品リスト変数に自動的にデータを入力します。このリストを pageBlockTable コンポーネントが 使用します。 • リストを反復処理するときに、現在のレコードを参照する方法が必要です。var="pitem" ステートメント は、現在の行のレコードを保持する pitem という変数を割り当てます。 Visualforce ページでのカスタム Apex コントローラの使用 これで、すべての商品レコードを表示する Visualforce ページが完成しました。前のチュートリアルではデフォ ルトのコントローラを使用しましたが、今度は自分でコントローラコードを記述します。コントローラは通 常、Visualforce ページに表示するデータを取得します。また、コマンドボタンがクリックされるなどのページ アクションに応答して実行されるコードが含まれます。 84 第 3 章: コンテキストでの Apex Visualforce ページでのカスタム Apex コントローラの使 用 このレッスンでは、標準コントローラを使用するのではなく独自のカスタム Apex コントローラを使用するよ うにページを変更します。Apexを使用するコントローラを記述すると、標準コントローラで提供される基本的 な動作以外のことも実行できます。次のレッスンでは、このコントローラを拡張し、E コマース機能を追加し てリストをオンラインストアに変更します。 新しいコントローラクラスを作成する手順は、次のとおりです。 1. [設定] で、[開発] > [Apex クラス] をクリックします。 2. [新規] をクリックします。 3. クラスの定義として次のコードを追加し、[Quick Save (適用)] をクリックします。 public class StoreFrontController { List<Merchandise__c> products; public List<Merchandise__c> getProducts() { if(products == null) { products = [SELECT Id, Name, Condition__c, Price__c FROM Merchandise__c]; } return products; } } 4. https://<your-instance>.salesforce.com/apex/Catalog にある商品カタログページに戻り、ペー ジエディタがまだ開いていない場合は開きます。 5. <apex:page> 開始タグを変更して、ページを新しいコントローラクラスにリンクします。 <apex:page controller="StoreFrontController"> 属性名が standardController から controller に変更されたことに注目してください。recordSetVar 属性は標準コントローラでのみ使用されるため、削除します。 6. [保存] をクリックして変更を保存し、ページを再読み込みします。 唯一の変化は [Merchandise] タブが選択されなくなる点です。 7. 次のコードを追加して、アプリケーションタブスタイルを Merchandise に再度設定します。 <apex:page controller="StoreFrontController" tabStyle="Merchandise__c"> 8. ページエディタのツールバーの上に、[StoreFrontController]ボタンが表示されています。このボタンをクリッ クして、ページのコントローラコードを表示し、編集します。[Catalog (カタログ)]をクリックしてVisualforce ページのコードに戻ります。 85 第 3 章: コンテキストでの Apex Apex コントローラでの内部クラスの使用 これは次のレッスンで使用します。 もうひとこと... • 前のレッスンと同様に、pageBlockTable の値属性は {!products} に設定され、テーブルコンポーネン トが products というリストを反復処理することを示します。カスタムコントローラを使用しているの で、Visualforce は、{!products} 式を評価するとき、Apex コントローラ内でメソッド getProducts() を 自動的に検出します。 • StoreFrontController クラスは、Visualforce カタログページで必要なデータを提供するために最小限必 要な処理しか行いません。このクラスには 1 つのメソッド getProducts() が含まれ、データベースをク エリして Merchandise__c レコードのリストを返します。 • 公開インスタンス変数 (products) と getter メソッド (getProducts()) を組み合わせて初期化を行い、アク セスを可能にするのは、Apex で記述された Visualforce コントローラの一般的なパターンです。 Apex コントローラでの内部クラスの使用 前のレッスンでは、Visualforceカタログページ用のカスタムコントローラを作成しました。ただし、コントロー ラはカスタムオブジェクトをデータベースから直接ビューに渡します。これは最適な方法とはいえません。こ のレッスンでは、MVC デザインパターンをより正確に使用するようにコントローラをリファクタリングし、さ らにページにいくつか機能を追加します。 1. [StoreFrontController] をクリックして、ページのコントローラコードを編集します。 2. クラスの定義を次のように修正し、[Quick Save (適用)] をクリックします。 public class StoreFrontController { List<DisplayMerchandise> products; public List<DisplayMerchandise> getProducts() { if(products == null) { products = new List<DisplayMerchandise>(); for(Merchandise__c item : [ SELECT Id, Name, Description__c, Price__c, Total_Inventory__c FROM Merchandise__c]) { products.add(new DisplayMerchandise(item)); } } return products; } // Inner class to hold online store details for item public class DisplayMerchandise { private Merchandise__c merchandise; public DisplayMerchandise(Merchandise__c item) { this.merchandise = item; } // Properties for use in the Visualforce view public String name { 86 第 3 章: コンテキストでの Apex Apex コントローラでの内部クラスの使用 get { return merchandise.Name; } } public String description { get { return merchandise.Description__c; } } public Decimal price { get { return merchandise.Price__c; } } public Boolean inStock { get { return (0 < merchandise.Total_Inventory__c); } } public Integer qtyToBuy { get; set; } } } 3. [Catalog (カタログ)] をクリックしてページの Visualforce コードを編集します。 4. 列定義を、新しい内部クラスのプロパティ名を処理するように変更します。既存の列定義を次のコードで 置き換えます。 <apex:column headerValue="Product"> <apex:outputText value="{!pitem.Name}"/> </apex:column> <apex:column headerValue="Condition"> <apex:outputText value="{!pitem.Condition}"/> </apex:column> <apex:column headerValue="Price"> <apex:outputText value="{!pitem.Price}"/> </apex:column> outputField コンポーネントは自動的に sObject 項目を処理しますが、カスタムクラスはまったく処理し ません。outputText はどの値も処理します。 5. [保存] をクリックして変更を保存し、ページを再読み込みします。 価格列が currency として書式設定されなくなりました。 6. 価格の outputText タグを次のコードに変更します。 <apex:outputText value="{0,number,currency}"> <apex:param value="{!pitem.Price}"/> </apex:outputText> outputText コンポーネントを使用して、異なるデータ型を自動的に書式設定できます。 7. コードが、次のようになっていることを確認して、[保存] をクリックします。 <apex:page controller="StoreFrontController" tabStyle="Merchandise__c"> <apex:pageBlock title="Our Products"> <apex:pageBlockSection columns="1"> <apex:pageBlockTable value="{!products}" var="pitem"> <apex:column headerValue="Product"> <apex:outputText value="{!pitem.Name}"/> 87 第 3 章: コンテキストでの Apex Apex コントローラへの action メソッドの追加 </apex:column> <apex:column headerValue="Condition"> <apex:outputText value="{!pitem.Condition}"/> </apex:column> <apex:column headerValue="Price" style="text-align: right;"> <apex:outputText value="{0,number,currency}"> <apex:param value="{!pitem.Price}"/> </apex:outputText> </apex:column> </apex:pageBlockTable> </apex:pageBlockSection> </apex:pageBlock> </apex:page> カタログページは次のようになります。 もうひとこと... • DisplayMerchandise クラスは、すでにデータベースに存在する Merchandise__c 型をラップし、新しいプ ロパティとメソッドを追加します。コンストラクタを使用すると、既存の Merchandise__c レコードを渡すこ とで新しい DisplayMerchandise インスタンスを作成できます。これで、インスタンス変数 products は DisplayMerchandise インスタンスのリストとして定義されます。 • getProducts() メソッドは、Merchandise__c レコードを返すクエリ (SOQL クエリをコールする角括弧内の テキスト) を実行します。その後、クエリから返されたレコードを反復処理し、DisplayMerchandise 商 品のリストに追加して、そのリストを返します。 Apex コントローラへの action メソッドの追加 このレッスンでは、action メソッドをコントローラに追加して新しい [Add to Cart (カートに追加)] ボタンのク リックを処理できるようにし、またショッピングカートの内容を出力する新しいメソッドも追加します。 Visualforceが透過的にデータをコントローラに戻して処理できるようにする方法を確認します。Visualforce側で、 ボタンと、買い物客が入力するフォーム項目をページに追加します。 1. [StoreFrontController] をクリックして、ページのコントローラコードを編集します。 88 第 3 章: コンテキストでの Apex Apex コントローラへの action メソッドの追加 2. 次のショッピングカートコードを StoreFrontController の定義の products インスタンス変数の直後 に追加し、[Quick Save (適用)] をクリックします。 List<DisplayMerchandise> shoppingCart = new List<DisplayMerchandise>(); // Action method to handle purchasing process public PageReference addToCart() { for(DisplayMerchandise p : products) { if(0 < p.qtyToBuy) { shoppingCart.add(p); } } return null; // stay on the same page } public String getCartContents() { if(0 == shoppingCart.size()) { return '(empty)'; } String msg = '<ul>\n'; for(DisplayMerchandise p : shoppingCart) { msg += '<li>'; msg += p.name + ' (' + p.qtyToBuy + ')'; msg += '</li>\n'; } msg += '</ul>'; return msg; } これで、商品カタログに購入用ユーザインターフェースを追加できる状態になりました。 3. [Catalog (カタログ)] をクリックしてページの Visualforce コードを編集します。 4. 商品カタログを form タグでラップします。ページ構造は次のコードのようになります。 <apex:page controller="StoreFrontController"> <apex:form> <!-- rest of page code --> </apex:form> </apex:page> <apex:form> コンポーネントを使用すると、ユーザが送信したデータをページからコントローラに返送で きます。 5. 次のコードを使用して 4 つ目の列を商品リストテーブルに追加します。 <apex:column headerValue="Qty to Buy"> <apex:inputText value="{!pitem.qtyToBuy}" rendered="{! pitem.inStock}"/> <apex:outputText value="Out of Stock" rendered="{! NOT(pitem.inStock)}"/> </apex:column> この列は、購入数量入力用のフォーム項目、または各商品の DisplayMerchandise.inStock() メソッド の値に基づいた在庫切れ通知のためのフォーム項目になります。 6. [保存] をクリックしてページを再読み込みします。 購入客が各商品の購入数量を入力するための新しい列があります。 89 第 3 章: コンテキストでの Apex Apex コントローラへの action メソッドの追加 7. 次のコードを </apex:pageBlock> タグの直前に入力して、ショッピングカートボタンを追加します。 <apex:pageBlockSection> <apex:commandButton action="{!addToCart}" value="Add to Cart"/> </apex:pageBlockSection> この段階で [保存] をクリックしてフォームを試すと、すべてが機能しますが、ショッピングカートが表示 されないため効果は確認できません。 8. 次のコードをページの </apex:form> 終了タグのすぐ上に追加します。 <apex:pageBlock title="Your Cart" id="shopping_cart"> <apex:outputText value="{!cartContents}" escape="false"/> </apex:pageBlock> 9. [保存]をクリックして、フォームを試します。ショッピングカートに品目を追加できるようになりました。 この場合、単純なテキスト表示のみです。現実のシナリオでは、注文のメール送信、Web サービスの呼び 出し、データベースの更新などが考えられます。 10. 効果を追加するために、[Add to Cart (カートに追加)] commandButton のコードを変更します。 <apex:commandButton action="{!addToCart}" value="Add to Cart" reRender="shopping_cart"/> [保存] をクリックしてフォームを使用すると、ショッピングカートがページの再読み込みではなく Ajax 経 由で更新されます。 もうひとこと... • このレッスンで確認したように、Visualforce は自動的にフォームでのデータ変更を元の商品変数に複製しま す。この機能はきわめて強力で、フォームやその他の複雑な入力ページをすぐに作成できます。 • [Add to Cart (カートに追加)] ボタンをクリックすると、画面全体は更新されずに、ショッピングカートパネ ルが更新されます。これを行う Ajax 効果には、通常複雑な JavaScript 操作が必要ですが、単純な reRender 属性で実現することができました。 • [Qty to Buy (購入数量)] 項目の値を変えて [Add to Cart (カートに追加)] を複数回クリックすると、ショッピン グカートで商品が重複するというバグがあります。これまでの Apex の知識でバグを発見して修復できるで 90 第 3 章: コンテキストでの Apex まとめ しょうか。1 つの方法として考えられるのは、特定の List を Map に変更し、重複した ID を記録してチェック できるようにすることです。このドキュメントや紹介した参考資料から、必要な Map メソッドを探してみ てください。 まとめ このチュートリアルでは、Apex コントローラクラスを使用して Visualforce ページを記述し、Warehouse アプリ ケーションのカスタムユーザインターフェースを作成しました。Visualforce ページで MVC デザインパターンを どのように使用できるか、また Apex クラスがそのパターンにどのように適合するかを確認しました。また、 送信されたフォームデータの処理、アプリケーションおよびセッションデータの管理、内部クラスを使用した 便利なメソッドの追加が簡単に行えることを確認しました。 91
© Copyright 2024 Paperzz