SFDC Spring'21 Sandbox プレビューでの激震
日々の業務に追われ、久々の更新になってしまいました。。。
2021年が始まり、さぁ仕事初めだという出鼻、私が手伝わせていただいている会社のSandbox環境で事件は起こりました。
この事件があったので、いつも書いているPythonの記事はお休みしてSalesforceの記事を書きますm(_ _)m
ことの発端はとある開発プロジェクトでサイト周りの機能の動作確認をしているときのことでした。(サイトに関しての説明は省きますが、知りたい方はこちらを参照してください⇒
https://help.salesforce.com/articleView?id=sites_configuring_sites.htm&type=5
)変更を加えていないはずの機能で昨日まで使えていた機能が突然アクセス権限エラーを起こしたのです。権限も変更した記憶がありませんでしたので、困惑しましたが設定変更履歴を追ってみると
同じ時間にSalesforceによるゲストプロファイルに対しての権限変更が大量にされていました……。諸悪?の根源であるこいつが何だったかというと、Spring'21 Sandbox プレビューだったんですね。これが世の中に一斉適用され、ゲストプロファイルに対してのオブジェクトの編集・削除権限がはく奪されてしまったんですね。
何ができなくなった?
Visualforce とそれに関連するApexクラス(VisualforceページとVisualforceコンポーネントでも発生する模様)の組み合わせによる機能で、
Apexクラス側で定義されるオブジェクト(XXX__c)のList値等を<apex:inputText>、<apex:inputField>、<apex:inputHidden>タグで(objectName).(fieldName)形式で参照していた場合、更新アクセスエラーが発生し、コントローラアクションメソッドが実行されません。詰まる話、ゲストユーザのアクセス・操作によってレコードの更新処理をするような実装をしていた部分でエラーが発生します。
対応策は?
私が手伝わせていただいている組織では、あの手この手でVisualforceとApexを捏ね繰り回して、ゲストユーザの操作をトリガーにレコードを更新させる実装だったので、大打撃を受けましたが、こういうことをしている会社さんも多いのでは?と思っています。
上の問題に対しての対応方法ですが、実は簡単な方法がありました。
<apex:inputText>タグや<apex:inputField>タグで実装されている部分を
VisualforceのAPIバージョン49.0以降にし、<apex:inputField>タグで ignoreEditPermissionForRendering というtrueで属性を付与することで上記の更新アクセスエラーは回避できます。
実はこの話、Winter'21でも触れられてたんですね(笑)
ただし、この ignoreEditPermissionForRendering 属性を設定したことによる情報漏洩に対してSalesforceは責任を負わないとヘルプページに記載されているので、属性の付与は必要最小限にしておきましょう。
https://developer.salesforce.com/docs/atlas.ja-jp.pages.meta/pages/pages_compref_inputField.htm
リリースノート公開からプレビュー、本番適用まで毎度時間が短い気もしますが、ちゃんとチェックしないと痛い目に遭いますね。個人的には新機能よりも何ができなくなるか?をもっとアピールしてほしい今日この頃でした―――
リリースノートはこちらから・・・
第10回:Pythonプログラミング入門 dict(辞書)
第8回、9回に引き続きコンテナの紹介です。
第8回:Pythonプログラミング入門 List(リスト) - 週末SEのメモブログ
第9回:Pythonプログラミング入門 tuple(タプル) - 週末SEのメモブログ
dict(辞書型)は、Listやtupleと同じくコンテナの一種です。
違いとしては、Listやtupleが1つのオブジェクト単位で格納できたのに対し、dictは2つのオブジェクトを関連付けて格納します。
2つのうち片方のオブジェクトはキーと呼ばれ、オブジェクトの格納時や取得の際に必要になります。もう一方のオブジェクトは、バリューと呼ばれ紐づけられる(呼び出し対象になる)値になります。
これは、javaでいうところのMapにあたります。
Mapと同様ですが、キーからバリューを取得できますが、バリューからキーは取得できません。
また、辞書はミュータブル(変更可能)なため、キーとバリューのペアを追加できます。注意点としては、(これもjavaのMapと同じですが)辞書は格納するオブジェクトの順番は指定できません。
つまり、インデックスを使った呼び出しはできないということです。
辞書(dict)の書式は下記のいずれかです。
test_dict = dict()
# または
test_dict = {}
dictもListやtuple同様、宣言時にキーとバリューのペアを入力できます。
キーとバリューをコロン(:)で区切ります。
※複数のペアを入力する際は、カンマで区切ります
country = {"Japan": "日本","America": "アメリカ"}
追加・更新
辞書の追加・更新は、
[辞書名]キー = [バリュー]
で、できます。
country["China"] = "中国"
取得
辞書の取得は、
[辞書名]キー
で、できます。
country["Japan"]
削除
辞書の削除は、
del [辞書名]キー
で、できます。
del country["China"]
おまけ
辞書のバリューには文字列の他、整数値などの数値オブジェクトも利用できます。
Listなども利用できます。
hoge = ["Apple", "Banana", "Orange"]
country["Japan"] = hoge
しかし、キーには文字列やタプルなどのイミュータブル(変更不可)なオブジェクトしか使えません。
第9回:Pythonプログラミング入門 tuple(タプル)
前回に引き続き、コンテナの紹介をしていきます。
第8回:Pythonプログラミング入門 List(リスト) - 週末SEのメモブログ
リストと違いタプルは好きな順にオブジェクトを保存できます。
そして、タプルは一度作ると格納されている値を変更できない(イミュータブル)であることも違いの一つです。
※格納されている要素が変更できないだけでなく、要素の追加も削除もできません
タプルの宣言は下記の通りです。
test_tuple = tuple()
# または
test_tuple = ()
要素を入れるときは下記のようにカンマで区切ります。
test_tuple = ("TEST", 100, True)
また、もしタプルの要素が一つしかない場合は、下記のように末尾にカンマを付けます。これがないとPythonは数式演算の優先度を決めるかっこと見分けられないためです。
test_tuple = ("TEST",)
タプルにはリストと同じ部分もあります。
それは値の取得時にインデックスが使えること、in演算子が使えることです。
test_tuple = ("TEST", 100, True)
test_tuple[1]
"TEST" in test_tuple
また値が含まれないことを確認するnot in演算子も使えます。
"TEST" not in test_tuple
in演算子、not in演算子を使った場合、上記の例ではそれぞれTrueとFalseを返します。
タプルの意味
リストより使い勝手の悪く見えるタプルの存在意義についてですが、タプルは要素が変わらないことが保証されているので、変わってほしくない値を扱う際に使用します。javaで言うところの final 修飾子に近いですね。
例えば干支や国の地理情報が挙げられるでしょうか。
プログラム内での意図しない変更を回避するためにタプルは使用されます。
またタプルを変更しようとすると下記のような
test_tuple = ("TEST", 100, True)
test_tuple[2] = False
TypeError: 'tuple' object does not support item assignment
例外が発生します。
第8回:Pythonプログラミング入門 List(リスト)
リストの前にコンテナの概念について、紹介します。
コンテナは本棚のようなものでデータ構造を保持しています。
リストは好きな順番でオブジェクトを保持できるコンテナの一部です。
リストを作成する際は下記のどちらかの方法で作成できます。
hoge = list()
# または
hoge = [ ]
どちらもリストを作成できますが、後者では、リストの中に要素を入れた状態のリストを作成することができます。
要素はカンマで区切り複数設定する事ができます。
上記の hoge というリストには、"Apple"、"Banana"、"Orange"という3つの文字列の要素が順番に格納されます。
コンテナに格納されるオブジェクトには、インデックスと呼ばれるコンテナ内での位置を表す印があります。インデックスは順番の認識でOKです。
ちなみにインデックスは0から始まるので注意してください。
上の例で続けます。
hoge = ["Apple", "Banana", "Orange"]
hoge[0]
hoge[1]
hoge[2]
角カッコ内にインデックスを指定することで、その位置に格納された値が取得されます。この例では、’Apple’、’Banana’、’Orange’がそれぞれ出力されます。
もし存在しないインデックスを指定(例えば、hoge[4])してしまうと先に紹介した例外(IndexError: list index out of range)が発生します。
上記では文字列しか設定しませんでしたが、Pythonのリストでは、同時にどんなオブジェクトでも格納することができます。
fuga = []
fuga.append("Apple")
fuga.append(100)
fuga.append(100.1)
fuga.append(True)
javaでは、異なるデータ型の値をリストに格納することはできませんね。
余談ですが、リストのように繰り返し処理で要素(値)を1つずつ取得できるオブジェクトはイテラブル(繰り返し可能)と言われます。
また、リストは一度値を格納した後でも変更可能(ミュータブル)です。
ミュータブルなコンテナは値を追加・変更・削除することが可能です。
hoge = ["Apple", "Banana", "Orange"]
hoge[1] = "Grape"
このように任意のインデックスの値を、上記の例では2番目の"Banana"を"Grape"に変更しています。
削除については、例えば、リストの末尾から要素を取り除く場合は、popというメソッドを利用します。
hoge = ["Apple", "Banana", "Orange"]
hoge.pop()
上記の例では末尾の”Orange”が取り除かれ、"Apple"、"Banana"が出力されます。
リスト内にいくつの要素が含まれているかを確認するときは、len関数を使用します。
hoge = ["Apple", "Banana", "Orange"]
len(hoge)
ある要素がそのリストに含まれるかを確認するときはin演算子を利用します。
hoge = ["Apple", "Banana", "Orange"]
"Grape" in hoge
この例では False が返ってきます。
最後にリストはプラス演算子を使用することで連結できます。
hoge = ["Apple", "Banana", "Orange"]
fuga = ["Grape", "Peach", "Strawberry"]
hoge + fuga
上記では、['Apple', 'Banana', 'Orange', 'Grape', 'Peach', 'Strawberry']が出力されます。
第7回:Pythonプログラミング入門 例外処理 try-except
前回の記事で例外の話がありましたので、例外について紹介していきます。
第6回:Pythonプログラミング入門 必須引数とオプション引数 - 週末SEのメモブログ
例外、つまりエラーの発生する理由ですが、ユーザーから意図していない値を関数に渡されたり、(基盤の問題にもなりますが)件数やクエリの発行数が多すぎてガバナ制限に抵触したりと様々なものがあります。
そのうちプログラムを書いていて最初に注意しなければならないのが、ユーザーから意図しない値を入力(input関数)された際の例外処理です。
よくある例:0で割ってしまう
算数の話ですが、0で割ることはできませんよね?同じことをPythonに実行させると例外が発生します。サンプルコードは下記のようなものです。
x = input("input a number:")
y = input("input another:")
x = int(x)
y = int(y)
print(x / y)
最初の入力で 5 、2回目の入力で 0 を入力した場合、
ZeroDivisionError: integer division or modulo by zero
※エラーメッセージはバージョンによって若干異なることがあります
が発生します。
例外の発生に対して何の対応もしていないとこのエラーメッセージがユーザーの画面に出てしまいます。普段アプリを使っていてこんなメッセージを出されてフリーズしたら堪りませんよね?
そこで、例外処理を行う必要があります。
Pythonの例外処理は try と except のキーワードを利用します。
javaの経験がある方はtry-catchの代わりと考えてください。
では、実際にどうハンドリングするかですが、下記のように例外をハンドリングします。
x = input("input a number:")
y = input("input another:")
x = int(x)
y = int(y)
try:
print(x / y)
except ZeroDivisionError:
print("2個目の数字に0は使用できません")
このようにプログラムすることで、ユーザーがもし2個目の数字に 0 を入力してもプログラムは中断せずに済みます。上記の例では、2個目の数字に0以外の数字を入力した場合は、tryのブロック内に書かれた処理のみ実行され、exceptブロックの処理は実行されません。
数字として入力してほしいところで文字列を入力されてしまうなど、ユーザーのinputに依存する処理では、他にも色々な例外発生のリスクがあります。
それぞれの例外に対して、適切に対応していく必要があります。
補足ですが、Pythonの各例外はオブジェクトなので、プログラムで利用できます。
公式の例外一覧を参照するようにしましょう。
第6回:Pythonプログラミング入門 必須引数とオプション引数
前回の記事で関数について紹介しました。
第5回:Pythonプログラミング入門 関数 - 週末SEのメモブログ
関数には引数がありますが、この引数には2種類のタイプがあります。
一つが必須引数、もう一方がオプション引数と呼ばれます。
必須引数は名前の通り、ユーザーが関数を呼び出すときに省略できません。
もし、必須引数を省略してしまうとPythonは例外(エラー)を吐いてしまいます。
一方、オプション関数は省略ができます。
オプション関数は省略した場合、デフォルトで設定した値が引数として使用されます。
オプション引数も必須引数と同様にカンマで区切ることで複数設定できます。
オプション引数の構文は下記の通り、
[関数名]([引数名] = [引数のデフォルト値])
実際のコードは下記のようになります。
def f(x, y=2):
return x * y
print(f(2))
print(f(2, 4))
このサンプルコードでは、1回目のprint()で 4 が出力され、2回目では 8 が出力されます。
なお、オプション引数を利用する場合に下記の順番に引数を宣言するとエラー(SyntaxError: non-default argument follows default argument)になってしまいますので、注意してください。
def f(x=2, y):
return x * y
ちなみにエラーは
SyntaxError: non-default argument follows default argument
直訳すると「デフォルト以外の引数はデフォルトの引数の後に続きます」・・・
デフォルト値を取る引数の後にデフォルト値を取らない引数が来てますよ、ということですね。
下記のように書くことで回避できます。
def f(y, x=2):
return x * y
第5回:Pythonプログラミング入門 関数
関数は、入力値を受け取り、命令を実行、その結果を返すという一連の処理が書かれた複合文のことを言います。
関数はプログラム内で定義することで、何度でも呼び出す(利用)ことができます。
引数
上記の受け取られる「入力値」のことを引数を言います。一般的に「関数に引数を渡す」というような表現で引数の受け取りは表現されます。
関数の定義
[関数名] ([(カンマで区切られた複数の)引数])
Pythonでは上記の実行式で関数が表されます。定義の際は、関数名を決め、引数を定義し、出力(戻り値)を構文に沿って定義します。
# 構文
def [関数名]([引数]):
[関数定義]
具体的には下記のようになります。
def f(x):
return x ** 2
def はPythonで関数を定義する際のキーワードです。これを宣言することでPythonにインデント下に関数が定義されていることを伝えられます。関数名の制約は変数名と同様です。強いて言うと関数名には大文字を使わず、単語と単語をアンダーバーで区切ります。例:add_this
関数名を決めたら丸カッコを後ろに記載します。カッコ内には関数で使用する引数を定義します。
丸カッコの後ろには、前述のif文と同様にコロンを書いて改行します。
改行後はインデント(半角スペース4つ)からPythonには関数の定義として受け取られます。
上記の例では、「return x ** 2」1行のみです。今回は関数の定義内容と戻り値が一緒に記載されているパターンなので分かりにくいですが、
関数内容・・・x(引数)を2乗する
戻り値 ・・・x(引数)を2乗した値
ということになります。
returnキーワードは関数f(x)が呼ばれたときに何を出力するか?を定義しています。
上記の例では、引数が1つの関数で紹介しましたが、関数には引数が1つのもの複数のもの、関数のないものが存在します。引数を必要としない関数を定義する場合、定義の際に丸カッコ内に何も書かずに定義します。
具体的には下記の通りです。
def f():
return "サンプル"
sample = f()
print(sample)
ここでは、「サンプル」という文字列を返すf()関数を定義し、sampleという変数にf()の戻り値(「サンプル」)を代入します。そして最後にsampleの値をprint関数で出力しています。