今話題の対話型AIチャットのChatGPTにApexトリガーを書かせてみました。
ChatGPTとは何ですか?
ChatGPTとは何かChatGPTに聞いてみましょう。
このように会話のコンテキストを理解して返事をしてくれます。本当に人間と会話しているレベルの返答を返してくれて衝撃です。
ネットでもかなり話題になっています。
ChatGPTっていうチャットボットAIで遊んでるんだけど有能すぎてやばい
— 銀河P (@galaxy14141356) December 1, 2022
エンジニア終了の日も近いかも pic.twitter.com/6ObDabuJkQ
AIチャットの新しい使い方見つけた!
— 深津 貴之 / THE GUILD / note.com (@fladdict) December 6, 2022
AI同士にディベートさせると、メジャーな論点が洗い出せる。
これ、いろんなことの予行練習に使えそう。素晴らしい。 pic.twitter.com/6LGU1tFlOO
詳しくはたくさん記事があるので他の記事を読んでください。
Apexを書かせてみよう
これはSalesforceの開発もできるのではないかと、とりあえずApexトリガーを書かせてみました。
#ChatGPT さんすごい。「取引先の請求先住所が変更されたときにGoogle Geocoding APIを実行し緯度経度を取得し保存するApexトリガーのコードを教えて下さい。」って聞いたらけっこうちゃんとしたコード書いてくれた。
— Atsuhiko Kimura (@atskimura) December 4, 2022
コード全体は以下。https://t.co/iyzROlWDVY pic.twitter.com/Ra5gtpVWSm
すっごい。
再度、緯度経度取得Apexトリガーを書いてもらう
質問する度に回答は異なるので、記事を書くために再度同じ質問をしてみました。今回はさらに対話してコードをよりよくしてもらってみます。
Q1. 取引先の請求先住所が変更されたときにGoogle Geocoding APIを実行し緯度経度を取得し保存するApexトリガーのコードを教えてください。
A1. ChatGPTの回答
Google Geocoding APIを使用するためには、Google Maps APIキーが必要です。まずは、Google Cloud Platformでプロジェクトを作成し、Google Maps Geocoding APIを有効化してAPIキーを取得する必要があります。
次に、Apexトリガーのコードを書く前に、Google Geocoding APIを呼び出すためのApexクラスを作成します。下記のようなコードで実装できます。
// GoogleGeocodingAPI.cls
public with sharing class GoogleGeocodingAPI {
public static String GEOCODING_API_KEY = 'YOUR_API_KEY';
public static String GEOCODING_API_ENDPOINT = 'https://maps.googleapis.com/maps/api/geocode/json';
public static GeocodingResponse getGeocodingResponse(String address) {
Http h = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint(GEOCODING_API_ENDPOINT + '?address=' + EncodingUtil.urlEncode(address, 'UTF-8') + '&key=' + GEOCODING_API_KEY);
req.setMethod('GET');
HttpResponse res = h.send(req);
return (GeocodingResponse) JSON.deserialize(res.getBody(), GeocodingResponse.class);
}
}
次に、GeocodingResponseクラスを定義します。このクラスは、Google Geocoding APIから取得したレスポンスを表すためのクラスです。
// GeocodingResponse.cls
public class GeocodingResponse {
public String status;
public List<Result> results;
public String error_message;
public class Result {
public List<AddressComponent> address_components;
public String formatted_address;
public Geometry geometry;
}
public class AddressComponent {
public String long_name;
public String short_name;
public List<String> types;
}
public class Geometry {
public Location location;
}
public class Location {
public Double lat;
public Double lng;
}
}
最後に、Apexトリガーを定義します。このトリガーは、取引先の請求先住所が変更されたときに実行され、Google Geocoding APIを呼び出して、請求先住所の緯度経度を取得し保存します。
// GeocodingTrigger.apxt
trigger GeocodingTrigger on Account (after update) {
Set<Id> accountIds = new Set<Id>();
for (Account account : Trigger.new) {
if (account.BillingStreet != Trigger.oldMap.get(account.Id).BillingStreet) {
accountIds.add(account.Id);
}
}
if (accountIds.isEmpty()) {
return;
}
List<Account> accounts = [
SELECT Id, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry
FROM Account
WHERE Id IN :accountIds
];
for (Account account : accounts) {
String address = account.BillingStreet + ', ' + account.BillingCity + ', ' + account.BillingState + ' ' + account.BillingPostalCode + ' ' + account.BillingCountry;
GeocodingResponse response = GoogleGeocodingAPI.getGeocodingResponse(address);
if (response.status == 'OK') {
Result result = response.results[0];
account.BillingLatitude = result.geometry.location.lat;
account.BillingLongitude = result.geometry.location.lng;
}
}
update accounts;
}
このトリガーは、取引先の請求先住所が変更されたときに、その住所の緯度経度を取得して、取引先の請求先住所の緯度経度を更新します。
A1.の感想
Geocoding API実行クラスが分かれて、さらによくなっている。
しかし、住所が変更されたかの確認に BillingStreet
しか見ていないので、直してもらいましょう。
Q2. 住所が変更されたかの確認はBillingStreetのチェックだけでは不足しています。Apexトリガーを修正してください。
A2. ChatGPTの回答
そうですね。上記のApexトリガーでは、BillingStreet以外のフィールドも更新された場合に、住所の緯度経度を更新するようにする必要があります。
修正するには、トリガー内で更新されたフィールドを確認し、BillingStreet以外のフィールドも更新されている場合は、その住所の緯度経度を更新するようにします。
次のように修正します。
// GeocodingTrigger.apxt
trigger GeocodingTrigger on Account (after update) {
Set<Id> accountIds = new Set<Id>();
for (Account account : Trigger.new) {
Account oldAccount = Trigger.oldMap.get(account.Id);
if (account.BillingStreet != oldAccount.BillingStreet || account.BillingCity != oldAccount.BillingCity || account.BillingState != oldAccount.BillingState || account.BillingPostalCode != oldAccount.BillingPostalCode || account.BillingCountry != oldAccount.BillingCountry) {
accountIds.add(account.Id);
}
}
if (accountIds.isEmpty()) {
return;
}
List<Account> accounts = [
SELECT Id, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry
FROM Account
WHERE Id IN :accountIds
];
for (Account account : accounts) {
String address = account.BillingStreet + ', ' + account.BillingCity + ', ' + account.BillingState + ' ' + account.BillingPostalCode + ' ' + account.BillingCountry;
GeocodingResponse response = GoogleGeocodingAPI.getGeocodingResponse(address);
if (response.status == 'OK') {
Result result = response.results[0];
account.BillingLatitude = result.geometry.location.lat;
account.BillingLongitude = result.geometry.location.lng;
}
}
update accounts;
}
A2.の感想
すごい。いい感じに直った。
しかし、Apexトリガーに直接コードを書きたくないので、Apexクラスに分離してもらいましょう。
Q3. Apexトリガーに直接コードを書くのではなく、Apexクラスを呼び出すようにしたいです。
A3. ChatGPTの回答
そうですね。Apexトリガーでは、トリガーのロジックを直接書くことが多いですが、Apexクラスを呼び出すことで、トリガーのロジックを分離することができます。
Apexクラスを呼び出すためには、Apexトリガー内でApexクラスをインスタンス化し、メソッドを呼び出す必要があります。
具体的には、次のように修正します。
// GeocodingTrigger.apxt
trigger GeocodingTrigger on Account (after update) {
Set<Id> accountIds = new Set<Id>();
for (Account account : Trigger.new) {
Account oldAccount = Trigger.oldMap.get(account.Id);
if (account.BillingStreet != oldAccount.BillingStreet || account.BillingCity != oldAccount.BillingCity || account.BillingState != oldAccount.BillingState || account.BillingPostalCode != oldAccount.BillingPostalCode || account.BillingCountry != oldAccount.BillingCountry) {
accountIds.add(account.Id);
}
}
if (accountIds.isEmpty()) {
return;
}
List<Account> accounts = [ SELECT Id, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry FROM Account WHERE Id IN :accountIds ];
Geocoding geocoding = new Geocoding();
geocoding.updateGeocoding(accounts);
}
// Geocoding.cls
public with sharing class Geocoding {
public void updateGeocoding(List<Account> accounts) {
for (Account account : accounts) {
String address = account.BillingStreet + ', ' + account.BillingCity + ', ' + account.BillingState + ' ' + account.BillingPostalCode + ' ' + account.BillingCountry;
GeocodingResponse response = GoogleGeocodingAPI.getGeocodingResponse(address);
if (response.status == 'OK') {
Result result = response.results[0];
account.BillingLatitude = result.geometry.location.lat;
account.BillingLongitude = result.geometry.location.lng;
}
}
update accounts;
}
}
上記のように修正することで、Apexトリガー内でApexクラスを呼び出すことができます。Apexクラスを使用することで、トリガーのロジックを分離することができ、コードの見通しがよくなります。
A4.の感想
分けてくれた。すごい。
でも、条件分岐もクラスに分けて欲しい。
Q4. ApexトリガーからはApexクラスを呼び出すだけにして欲しいです。
A4. ChatGPTの回答
そうですね。ApexトリガーからはApexクラスを呼び出すだけにすることができます。
Apexクラスを呼び出すだけにするためには、Apexトリガーではトリガーのロジックを記述せず、Apexクラスのみでトリガーのロジックを実装するようにします。
具体的には、次のように修正します。
コードは変わらなかったので省略😢
A4.の感想
コード変わらなかった…
依頼文を変えてみよう。
Q5. 住所が変更された取引先レコードを取得するロジックもApexクラスに分離して欲しいです。
A5. ChatGPTの回答
そうですね。上記のApexトリガーでは、住所が変更された取引先レコードを取得するロジックがApexクラスに分離されていません。
Apexクラスに分離するためには、Apexトリガーでは住所が変更された取引先レコードを取得するロジックを実装せず、Apexクラスのみで実装するようにします。
具体的には、次のように修正します。
// GeocodingTrigger.apxt
trigger GeocodingTrigger on Account (after update) {
List<Account> accounts = Geocoding.getAccountsForGeocoding(Trigger.new, Trigger.oldMap);
if (accounts.isEmpty()) {
return;
}
Geocoding geocoding = new Geocoding();
geocoding.updateGeocoding(accounts);
}
// Geocoding.cls
public with sharing class Geocoding {
public static List<Account> getAccountsForGeocoding(List<Account> newAccounts, Map<Id, Account> oldAccountMap) {
List<Account> accounts = new List<Account>();
for (Account account : newAccounts) {
Account oldAccount = oldAccountMap.get(account.Id);
if (account.BillingStreet != oldAccount.BillingStreet || account.BillingCity != oldAccount.BillingCity || account.BillingState != oldAccount.BillingState || account.BillingPostalCode != oldAccount.BillingPostalCode || account.BillingCountry != oldAccount.BillingCountry) {
accounts.add(account);
}
}
return accounts;
}
public static void updateGeocoding(List<Account> accounts) {
for (Account account : accounts) {
String address = account.BillingStreet + ', ' + account.BillingCity + ', ' + account.BillingState + ' ' + account.BillingPostalCode + ' ' + account.BillingCountry;
GeocodingResponse response = GoogleGeocodingAPI.getGeocodingResponse(address);
if (response.status == 'OK') {
Result result = response.results[0];
account.BillingLatitude = result.geometry.location.lat;
account.BillingLongitude = result.geometry.location.lng;
}
}
update accounts;
}
}
A5.の感想
うーん。。。期待したのとはちょっと違うけど、まあこんなところかな。終わろう。
終わりに
すごくない?
でも、全然ひどいコードを書いて、いくら言っても直らない時もありましたw