Sailsには、モデルの属性を自動的に検証する機能が組み込まれています。レコードが更新されるたび、または新しいレコードが作成されるたびに、各属性のデータは、事前に定義されたすべての検証ルールに対してチェックされます。これにより、無効なエントリがアプリのデータベースに入り込むのを防ぐための便利なフェイルセーフが提供されます。
unique(データベースレベルの制約として実装されています。「ユニーク」を参照)を除き、以下のすべての検証はJavaScriptで実装され、Sailsと同じNode.jsサーバープロセスで実行されます。また、どの検証を使用する場合でも、属性は常に組み込みのデータ型(string、number、jsonなど)のいずれかを指定する必要があることに注意してください。
// User
module.exports = {
attributes: {
emailAddress: {
type: 'string',
unique: true,
required: true
}
}
};
Sails/Waterlineでは、モデル属性には常に何らかのデータ型の保証があります。これは、基盤となるデータベースに存在する可能性のある物理層の制約を超えており、開発者が特定のモデルに入力または出力されるデータについて妥当な想定を維持するための手段を提供することに重点が置かれています。
このデータ型の保証は、論理的な検証や結果および基準の強制変換に使用されます。以下は、SailsとWaterlineでサポートされているデータ型のリストです。
| データ型 | 使用法 | 説明 |
|---|---|---|
type: 'string' |
任意の文字列。 | |
type: 'number' |
任意の数値。 | |
type: 'boolean' |
trueまたはfalse。 |
|
type: 'json' |
数値、ブール値、文字列、配列、辞書(プレーンなJavaScriptオブジェクト)、およびnullを含む、JSONシリアライズ可能な任意の値。 |
|
type: 'ref' |
undefinedを除く任意のJavaScript値。(アダプター固有の動作を利用する場合にのみ使用する必要があります。) |
SailsのORM(Waterline)とそのアダプターは、基準辞書に指定された値や、.create()または.update()の値として提供された値が、予期されるデータ型と一致するように、緩やかな検証を実行します。
注意:ネイティブにJSON.stringify()が呼び出されてから、型がtextに設定された列に格納されます。レコードが返されるたびに、データにはJSON.parse()が呼び出されます。これは、パフォーマンスと、他のアプリケーションまたはデータベース内の既存のデータとの互換性を考慮する際に認識しておくべきことです。公式のPostgreSQLおよびmongoDBアダプターは、
string、number、およびbooleanデータ型は、レコードの作成または更新時にnullを値として受け入れません。null値を設定できるようにするには、属性のallowNullフラグを切り替えます。allowNullフラグは上記のデータ型でのみ有効です。型jsonまたはrefの属性、関連付け、または主キー属性では有効ではありません。
空の文字列("")は文字列であるため、通常はtype: 'string'属性でサポートされています。ただし、いくつかの例外があります。主キー(主キーは空の文字列をサポートしないため)と、required: trueを持つ属性です。
属性がrequired: trueの場合、.create()を呼び出すときに、常に値を指定する必要があります。これにより、作成または更新時に値がnullまたは空の文字列("")に設定されることも防ぎます。
次の検証ルールは、nullに対する追加の制限を課しません。つまり、nullが通常許可される場合、isEmail検証ルールを有効にしても、nullが無効として拒否されることはありません。
同様に、次の検証ルールのほとんどは、空の文字列("")に対する追加の制限を課していません。いくつかの例外(isNotEmptyString、およびisBoolean、isNumber、max、およびminなどの文字列関連以外のルール)がありますが、それ以外の場合、空の文字列("")が通常許可される属性では、検証ルールを追加しても拒否されることはありません。
以下の表では、「互換性のある属性型」列に、各検証ルールに適したデータ型(つまり、属性定義のtypeプロパティ)が示されています。多くの場合、検証ルールは複数の型で使用できます。以下の表では、ショートカットを使用していることに注意してください。が互換性がある場合
| ルール名 | チェック内容 | 使用上の注意 | 互換性のある属性型 |
|---|---|---|---|
| custom | カスタム関数の最初の引数として指定された場合に、関数がtrueを返すような値。 |
例 | 任意 |
| isAfter | 日付として解析した場合に、構成されたJavaScriptのDateインスタンスより後の時点を参照する値。 |
isAfter: new Date('Sat Nov 05 1605 00:00:00 GMT-0000') |
|
| isBefore | 日付として解析した場合に、構成されたJavaScriptのDateインスタンスより前の時点を参照する値。 |
isBefore: new Date('Sat Nov 05 1605 00:00:00 GMT-0000') |
|
| isBoolean | trueまたはfalseの値 |
isBoolean: true | |
| isCreditCard | クレジットカード番号である値。 | アプリがPCIに準拠していない限り、クレジットカード番号をデータベースに保存しないでください!ユーザーがクレジットカード情報を保存できるようにする場合は、Stripeなどの支払いAPIを使用するのが安全な代替手段です。 | |
| isEmail | メールアドレスのように見える値。 | isEmail: true |
|
| isHexColor | 16進数カラーである文字列。 | isHexColor: true |
|
| isIn | 指定された許可された文字列の配列に含まれる値。 | isIn: ['paid', 'delinquent'] |
|
| isInteger | 整数(整数)である数値 | isInteger: true |
|
| isIP | 有効なIPアドレス(v4またはv6)である値 | isIP: true |
|
| isNotEmptyString | 空の文字列ではない値 | isNotEmptyString: true |
|
| isNotIn | 構成された配列に含まれない値。 | isNotIn: ['profanity1', 'profanity2'] |
|
| isNumber | Javascriptの数値である値 | isNumber: true |
|
| isString | 文字列である値(つまり、typeof(value) === 'string') |
isString: true |
|
| isURL | URLのように見える値。 | isURL: true |
|
| isUUID | UUID(v3、v4、またはv5)のように見える値 | isUUID: true |
|
| max | 構成された数値以下の数値。 | max: 10000 |
|
| min | 構成された数値以上の数値。 | min: 0 |
|
| maxLength | 構成された文字数以下の文字列。 | maxLength: 144 |
|
| minLength | 構成された文字数以上の文字列。 | minLength: 8 |
|
| regex | 構成された正規表現に一致する文字列。 | regex: /^[a-z0-9]$/i |
次のように定義された属性がある場合を想像してください。
workEmail: {
type: 'string',
isEmail: true,
}
.create()_または_.update()を呼び出すと、この値は任意の有効なメールアドレス(「[email protected]」など)または空の文字列("")に設定できます。ただし、nullを設定することはできません。これは、type: 'string'によって課せられた型安全性の制限に違反するためです。
この属性で
nullを受け入れるようにするには(たとえば、既存のデータベースを操作している場合)、type: 'json'に変更します。通常はisString: trueも追加しますが、この例ではすでにisEmail: trueを強制しているため、そうする必要はありません。覚えておくべきより高度な機能は、データベースによっては、自動移行中に(該当する場合)どの列型を定義するかをSails/Waterlineに通知するために
columnTypeを利用することを選択できることです。
星評価のように、属性が特定の数値をサポートすることを示したい場合は、次のようにします。
starRating: {
type: 'number',
min: 1,
max: 5,
required: true,
}
星評価をオプションにする場合は、required: trueフラグを削除するのが最も簡単です。省略すると、starRatingはデフォルトでゼロになります。
nullを使用)しかし、星評価が常に数値であるとは限らない場合はどうでしょうか?星評価が数値または特別なnullリテラルのいずれかである可能性があるレガシーデータベースと統合する必要がある場合を想像してみてください。このシナリオでは、starRating属性が特定の数値とnullの両方をサポートするように定義する必要があります。
これを実現するには、allowNullを使用するだけです
starRating: {
type: 'number',
allowNull: true,
min: 1,
max: 5,
}
SailsとWaterlineの属性は、便宜上
allowNullをサポートしていますが、もう1つの実行可能な解決策は、starRatingをtype: 'number'からtype: 'json'に変更することです。ただし、json型はブール値、配列など、他のデータも許可することを忘れないでください。starRatingでこれらのデータ型がサポートされないように明示的に保護したい場合は、isNumber: true検証ルールを追加できますstarRating: { type: 'json', isNumber: true, min: 1, max: 5, }
uniqueは、上記にリストされているすべての検証ルールとは異なります。実際、これは実際には検証ルールではありません。これはデータベースレベルの制約です。詳細については、後ほど説明します。
属性がunique: trueを宣言している場合、Sailsは同じ値を持つ2つのレコードが許可されないようにします。典型的な例は、UserモデルのemailAddress属性です。
// api/models/User.js
module.exports = {
attributes: {
emailAddress: {
type: 'string',
unique: true,
required: true
}
}
};
uniqueが他の検証と異なるのはなぜですか?データベースに1,000,000件のユーザーレコードがあると想像してください。uniqueが他の検証のように実装された場合、新しいユーザーがアプリにサインアップするたびに、Sailsは100万件の既存のレコードを検索して、新しいユーザーが提供したメールアドレスを他に誰も使用していないことを確認する必要があります。これは非常に遅いため、すべてのレコードの検索が完了するまでに、別の人がサインアップしている可能性があります!
幸いなことに、この種のユニーク性チェックは、あらゆるデータベースにおいて最も普遍的な機能でしょう。これを活用するために、Sailsはuniqueのサポートを実装するためにデータベースアダプターに依存しています。具体的には、自動マイグレーション中に、データベース自体の関連するフィールド/カラム/属性にユニーク制約を追加します。つまり、アプリがmigrate:'alter'に設定されている間、Sailsは自動的に基礎となるデータベースにユニーク制約を組み込んだテーブル/コレクションを生成します。migrate:'safe'に切り替えると、データベース制約の更新はユーザーに委ねられます。
本番データベースを使い始めるときは、データベースのパフォーマンスを向上させるためにインデックスを設定するのが常に良い考えです。インデックスを設定するための正確なプロセスとベストプラクティスはデータベースによって異なり、このドキュメントの範囲を超えています。とは言え、これまでに行ったことがない場合は、心配しないでください。思っているより簡単です。
本番スキーマに関連する他のすべてと同様に、アプリでmigrate: 'safe'を使用するように設定すると、Sailsはデータベースインデックスを完全にユーザーに委ねます。
これは、手動マイグレーションを実行するときに、ユニーク制約とともにインデックスを必ず更新する必要があることを意味します。
バリデーションは、数百行の反復コードを記述する手間を省くことができますが、モデルのバリデーションはアプリケーションで作成または更新するたびに実行されることを覚えておいてください。属性定義の1つでバリデーションルールを使用する前に、アプリケーションが.create()または.update()を呼び出してその属性の新しい値を指定するたびに、それが適用されることに問題がないことを確認してください。そうでない場合は、コントローラーでインラインで着信値を検証するコードを記述するか、サービスまたはモデルクラスメソッドのいずれかでカスタム関数を呼び出してください。
Sailsアプリで、ユーザーが(A)メールアドレスとパスワードを入力してそのメールアドレスを確認するか、(B)LinkedInでサインアップすることでアカウントにサインアップできるとします。あなたのUserモデルには、manuallyEnteredEmailという属性と、linkedInEmailという別の属性があるかもしれません。これらのメールアドレス属性の1つは必須ですが、どちらが必須であるかは、ユーザーがどのようにサインアップするかによって異なります。この場合、Userモデルはrequired: trueバリデーションを使用できません。2つのメールのいずれかが提供されていること、および提供されたメールが有効であることを確認するには、コード内の関連する.create()および.update()呼び出しの前に、これらの値を手動で確認する必要があります。
if ( !_.isString( req.param('email') ) ) {
return res.badRequest();
}
さらに一歩進めて、アプリケーションが支払いを受け入れるとしましょう。サインアップフロー中に、ユーザーが有料プランでサインアップした場合、請求目的でメールアドレス(billingEmail)を提供する必要があります。一方、無料アカウントでサインアップした場合、そのステップをスキップします。アカウント設定ページでは、有料プランのユーザーには「請求メールアドレス」フォームフィールドが表示され、請求メールアドレスをカスタマイズできます。一方、無料プランのユーザーには、「プランをアップグレード」ページにリンクする行動喚起が表示されます。
これらの要件は具体的であるように見えますが、まだ答えられていない質問があります
linkedInEmailが保存された場合、請求メールアドレスはどうなりますか?linkedInEmailを保存した場合、請求メールアドレスはどうなりますか?このような質問への回答によっては、billingEmailにrequiredバリデーションを保持したり、新しい属性(hasBillingEmailBeenChangedManuallyなど)を追加したり、unique制約を使用するかどうかを再考したりする可能性があります。
最後に、いくつかのヒントを以下に示します
.update()と.create()をどのように呼び出しているかに依存する必要があります。コントローラーまたはヘルパー関数で手動で値をチェックすることを優先して、組み込みのバリデーションサポートを放棄することを恐れないでください。多くの場合、これが最もクリーンで保守しやすいアプローチです。uniqueです。開発中、アプリがmigrate: 'alter'を使用するように構成されている場合は、uniqueバリデーションを自由に追加または削除できます。ただし、migrate: safe(たとえば、本番データベースの場合)を使用している場合は、データベースの制約/インデックスを更新し、手動でデータを移行する必要があります。可能な限り、バックエンドコードの実装に本格的に時間を費やす前に、アプリのユーザーインターフェースの独自のワイヤーフレームを取得または肉付けすることをお勧めします。もちろん、これは常に可能とは限りません。それがブループリントAPIの目的です。UI中心または「フロントエンドファースト」の理念に基づいて構築されたアプリケーションは、保守が容易で、バグが少なくなる傾向があり、ユーザーエクスペリエンスへの配慮が核にあるため、よりエレガントなAPIを備えていることがよくあります。
属性にcustom関数を指定することで、独自のカスタムバリデーションルールを定義できます。
// api/models/User.js
module.exports = {
// Values passed for creates or updates of the User model must obey the following rules:
attributes: {
firstName: {
// Note that a base type (in this case "string") still has to be defined, even though validation rules are in use.
type: 'string',
required: true,
minLength: 5,
maxLength: 15
},
location: {
type: 'json',
custom: function(value) {
return _.isObject(value) &&
_.isNumber(value.x) && _.isNumber(value.y) &&
value.x !== Infinity && value.x !== -Infinity &&
value.y !== Infinity && value.y !== -Infinity;
}
},
password: {
type: 'string',
custom: function(value) {
// • be a string
// • be at least 6 characters long
// • contain at least one number
// • contain at least one letter
return _.isString(value) && value.length >= 6 && value.match(/[a-z]/i) && value.match(/[0-9]/);
}
}
}
}
カスタムバリデーション関数は、最初の引数として検証される着信値を受け取ります。有効な場合はtrue、それ以外の場合はfalseを返す必要があります。
Sails.jsは、カスタムバリデーションメッセージをそのままではサポートしていません。代わりに、コードは.create()または.update()呼び出しによってスローされたバリデーションエラーを調べ(または「ネゴシエート」し)、JSONレスポンスで特定のエラーコードを送信するか、HTMLエラーページで適切なメッセージをレンダリングするかなど、適切なアクションを実行する必要があります。