アクションは、ウェブブラウザ、モバイルアプリケーション、またはサーバーと通信できるその他のシステムからのリクエストに応答する役割を担います。多くの場合、モデルとビューの仲介者として機能し、プロジェクトのビジネスロジックの大部分を調整します。アクションを使用してウェブページを提供したり、フォーム送信を処理したり、サードパーティAPIリクエストを管理したり、その他あらゆる処理を行うことができます。
アクションは、アプリケーション内のルートにバインドされています。ユーザーエージェントが特定のURLをリクエストすると、そのルートにバインドされたアクションがビジネスロジックを実行し、レスポンスを返します。たとえば、アプリケーションのGET /hello
ルートは、次のようなアクションにバインドできます。
async function (req, res) {
return res.send('Hi there!');
}
ウェブブラウザがアプリケーションサーバーの/hello
URLにアクセスするたびに、「こんにちは!」というメッセージが表示されます。
アクションは、api/controllers/
フォルダとそのサブフォルダに定義されます(コントローラについては後で詳しく説明します)。Sailsがファイルをアクションとして認識するには、ファイル名がケバブケース(小文字、数字、ハイフンのみを使用)である必要があります。Sails内(ほとんどの場合、ルートにバインドする場合)でアクションを参照する際には、ファイル拡張子を除いたapi/controllers
からの相対パスを使用します。たとえば、api/controllers/user/find.js
にあるアクションにルートをバインドするには、URLをuser/find
に指定します。
デフォルトでは、Sailsは.js
ファイルしか解釈できませんが、アプリケーションをカスタマイズしてCoffeeScriptやTypeScriptなども使用できます。アクションには、.md
(Markdown)と.txt
(テキスト)以外の任意のファイル拡張子を使用できます。
アクションファイルには、actions2(推奨)またはクラシックの2つの形式のいずれかを使用できます。
Sails v1.0のリリース以降、よりモダンな"actions2"構文でアクションを作成することを推奨しています。これはSailsのヘルパーとほぼ同じように機能します。この方法でアクションを定義することで、本質的に自己文書化され、自己検証されます。
actions2を使用すると、いくつかの利点があります。
sails generate action
を使用して、actions2ファイルをすばやく作成できます。exits
)はすべて明確に表示され、コードを詳しく調べる必要がありません。req
とres
に直接依存しないため、ヘルパーに再利用または抽象化しやすくなります。actions2を使用する場合、リクエストオブジェクトには
this.req
としてアクセスできます。
あるいは、inputs
とexits
を使ってenv
を関数に渡すことで、this.req
を使わずにreq
にアクセスできます。
要約すると、コードは後で再利用および変更しやすくなるように標準化されます。また、アクションのパラメータを事前に宣言するため、エッジケースやセキュリティホールを公開する可能性が大幅に低くなります。
actions2形式の例を次に示します。
module.exports = {
friendlyName: 'Welcome user',
description: 'Look up the specified user and welcome them, or redirect to a signup page if no user was found.',
inputs: {
userId: {
description: 'The ID of the user to look up.',
// By declaring a numeric example, Sails will automatically respond with `res.badRequest`
// if the `userId` parameter is not a number.
type: 'number',
// By making the `userId` parameter required, Sails will automatically respond with
// `res.badRequest` if it's left out.
required: true
}
},
exits: {
success: {
responseType: 'view',
viewTemplatePath: 'pages/welcome'
},
notFound: {
description: 'No user with the specified ID was found in the database.',
responseType: 'notFound'
}
},
fn: async function ({userId}) {
// Look up the user whose ID was specified in the request.
// Note that we don't have to validate that `userId` is a number;
// the machine runner does this for us and returns `badRequest`
// if validation fails.
var user = await User.findOne({ id: userId });
// If no user was found, respond "notFound" (like calling `res.notFound()`)
if (!user) { throw 'notFound'; }
// Display a personalized welcome view.
return {
name: user.name
};
}
};
Sailsはmachine-as-actionモジュールを使用して、上記の例のようにフォーマットされたアクションからルート処理関数を自動的に作成します。詳細については、machine-as-actionのドキュメントを参照してください。
アクション、ヘルパー、またはスクリプト内で何かをスローすると、デフォルトでerror
終了がトリガーされます。他の終了をトリガーする場合は、「特別な終了信号」をスローすることで行えます。これは、文字列(終了の名前)または終了の名前をキーとして、出力データを値として持つオブジェクトのいずれかになります。たとえば、通常の構文の代わりに
return exits.hasConflictingCourses();
簡略構文を使用できます。
throw 'hasConflictingCourses';
または、出力データを含めるには
throw { hasConflictingCourses: ['CS 301', 'M 402'] };
読みやすい簡略構文であることに加えて、終了信号は、for
ループ、forEach
などの中にいる場合でも、特定の終了を介して終了したい場合に特に役立ちます。
既存のコードベースまたはv0.12からアップグレードされたアプリケーションを使用している場合は、従来のアクション形式の方が使い慣れているかもしれません。クラシックアクションは、req
とres
引数を持つ関数として宣言されます。クライアントがこのタイプのアクションにバインドされたルートをリクエストすると、関数はその最初の引数(req
)として着信リクエストオブジェクト、2番目の引数(res
)として発信レスポンスオブジェクトを使用して実行されます。
IDでユーザーを検索し、「ようこそ」ビューを表示するか、ユーザーが見つからない場合はサインアップページにリダイレクトするサンプルアクションを次に示します。
module.exports = async function welcomeUser (req, res) {
// Get the `userId` parameter from the request.
// This could have been set on the querystring, in
// the request body, or as part of the URL used to
// make the request.
var userId = req.param('userId');
// If no `userId` was specified, or it wasn't a number, return an error.
if (!_.isNumeric(userId)) {
return res.badRequest(new Error('No user ID specified!'));
}
// Look up the user whose ID was specified in the request.
var user = await User.findOne({ id: userId });
// If no user was found, redirect to signup.
if (!user) {
return res.redirect('/signup' );
}
// Display the welcome view, setting the view variable
// named "name" to the value of the user's name.
return res.view('welcome', {name: user.name});
}
sails generate action
に--no-actions2
を付けて使用すると、クラシックアクションをすばやく作成できます。
よりシンプルなプロジェクトやプロトタイプでは、Sailsアプリケーションの作成を開始する最も速い方法は、アクションをコントローラファイルに整理することです。コントローラファイルは、パスカルケースのファイル名で、Controller
で終わる必要があり、アクションのディクショナリを含んでいます。たとえば、「ユーザーコントローラ」は、次のようなアクションを含むapi/controllers/UserController.js
ファイルに作成できます。
module.exports = {
login: function (req, res) { ... },
logout: function (req, res) { ... },
signup: function (req, res) { ... },
};
sails generate controller
を使用して、コントローラファイルをすばやく作成できます。
アクションファイルと同様に、アプリケーションをカスタマイズしてCoffeeScriptやTypeScriptを使用できますが、Sailsはデフォルトで.js
ファイルしか解釈できません。コントローラには、.md
(Markdown)と.txt
(テキスト)以外の任意のファイル拡張子を使用できます。
より大きく、より成熟したアプリケーションでは、コントローラファイルよりもスタンドアロンアクションの方が適切なアプローチです。このスキームでは、複数のアクションを単一のファイルに配置するのではなく、各アクションをapi/controllers
の適切なサブフォルダ内の独自のファイルに配置します。たとえば、次のファイル構造はUserController.js
ファイルと同等になります。
api/
controllers/
user/
login.js
logout.js
signup.js
スタンドアロンアクションを使用すると、コントローラファイルよりもいくつかの利点があります。
foo/bar/baz.js
対foo/BarController.baz
)よりも直感的です。api/controllers/index.js
ファイルを作成して、アプリケーションの/
ルートに自動的にバインドさせることができます(ルートアクションを保持するための任意のコントローラファイルを作成する代わりに)。ほとんどのMVCフレームワークの伝統に従って、成熟したSailsアプリケーションは通常、「薄い」コントローラを持っています。つまり、再利用可能なコードがヘルパーに移動したり、場合によっては個別のノードモジュールに抽出されたりするため、アクションコードはシンプルになります。このアプローチは、アプリケーションの複雑さが増すにつれて、保守を容易にすることができます。
しかし同時に、再利用可能なヘルパーにコードをあまりにも早く抽出すると、時間と生産性を浪費する保守上の問題が発生する可能性があります。正しい答えは中間のどこかにあります。
Sailsでは、この経験則をお勧めします。**同じコードを3回目に使用しようとするまで、別々のヘルパーに抽出するのを待つ**ことです。しかし、どんな教義にも言えることですが、自分の判断で使いましょう!問題のコードが非常に長く複雑な場合は、ヘルパーに抽出する方がはるかに早く意味を持つ可能性があります。逆に、構築しているものが迅速な使い捨てのプロトタイプであることがわかっている場合は、時間を節約するためにコードをコピー&ペーストするだけでよいでしょう。
情熱のために開発しているか、利益のために開発しているかに関わらず、最終的な目標はエンジニアとしての時間を最大限に活用することです。ある日にはより多くのコードを記述し、別の日はプロジェクトの長期的な保守性を考慮する必要があります。開発の現在の段階でどちらの目標がより重要であるかわからない場合は、一歩下がって考えてみる(さらに良いことに、チームの他のメンバーやNode.js/Sailsでアプリケーションを構築している他のユーザーとチャットしてみる)のも良いでしょう。