mirror of https://github.com/vapor/docs.git
630 lines
26 KiB
HTML
630 lines
26 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
|
||
<title>Vapor Documentation</title>
|
||
|
||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Code+Pro">
|
||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Quicksand:400,700,300">
|
||
<link rel="stylesheet" href="/1.5/styles/vapor-code.css">
|
||
<link rel="stylesheet" href="/1.5/styles/main.css">
|
||
</head>
|
||
<body>
|
||
|
||
<header>
|
||
<a class="logo" href="/1.5/">
|
||
<img src="/1.5/images/droplet.svg" alt="Vapor">
|
||
<h1>Vapor <em>Docs</em></h1>
|
||
</a>
|
||
<ul>
|
||
<li>
|
||
<a href="http://vapor.codes">Home</a>
|
||
</li>
|
||
<li>
|
||
<a href="http://example.vapor.codes">Example</a>
|
||
</li>
|
||
<li>
|
||
<a href="https://github.com/vapor/vapor">GitHub</a>
|
||
</li>
|
||
<li>
|
||
<a href="https://twitter.com/@codevapor">Twitter</a>
|
||
</li>
|
||
<li>
|
||
<a href="http://vapor.team">Slack</a>
|
||
</li>
|
||
</ul>
|
||
</header>
|
||
|
||
|
||
|
||
<nav>
|
||
<a href="#" class="toggle show">☰</a>
|
||
<a href="#" class="toggle close">×</a>
|
||
|
||
<div class="scroll">
|
||
<section>
|
||
<h3>Getting Started</h3>
|
||
<ul>
|
||
<li class="">
|
||
<a href="/1.5/getting-started/install-swift-3-macos.html">
|
||
Install Swift 3: macOS
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/getting-started/install-swift-3-ubuntu.html">
|
||
Install Swift 3: Ubuntu
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/getting-started/install-toolbox.html">
|
||
Install Toolbox
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/getting-started/hello-world.html">
|
||
Hello, World
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/getting-started/manual.html">
|
||
Manual
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/getting-started/xcode.html">
|
||
Xcode
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
<section>
|
||
<h3>Guide</h3>
|
||
<ul>
|
||
<li class="">
|
||
<a href="/1.5/guide/droplet.html">
|
||
Droplet
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/guide/folder-structure.html">
|
||
Folder Structure
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/guide/json.html">
|
||
JSON
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/guide/config.html">
|
||
Config
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/guide/views.html">
|
||
Views
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/guide/leaf.html">
|
||
Leaf
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/guide/controllers.html">
|
||
Controllers
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/guide/middleware.html">
|
||
Middleware
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/guide/validation.html">
|
||
Validation
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/guide/provider.html">
|
||
Provider
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/guide/sessions.html">
|
||
Sessions
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/guide/hash.html">
|
||
Hash
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/guide/commands.html">
|
||
Commands
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
<section>
|
||
<h3>Routing</h3>
|
||
<ul>
|
||
<li class="">
|
||
<a href="/1.5/routing/basic.html">
|
||
Basic
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/routing/parameters.html">
|
||
Route Parameters
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/routing/query-parameters.html">
|
||
Query Parameters
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/routing/group.html">
|
||
Group
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/routing/collection.html">
|
||
Collection
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
<section>
|
||
<h3>Fluent</h3>
|
||
<ul>
|
||
<li class="">
|
||
<a href="/1.5/fluent/driver.html">
|
||
Driver
|
||
</a>
|
||
</li>
|
||
<li class="active">
|
||
<a href="/1.5/fluent/model.html">
|
||
Model
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/fluent/query.html">
|
||
Query
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/fluent/relation.html">
|
||
Relation
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
<section>
|
||
<h3>Auth</h3>
|
||
<ul>
|
||
<li class="">
|
||
<a href="/1.5/auth/user.html">
|
||
User
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/auth/middleware.html">
|
||
Middleware
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/auth/request.html">
|
||
Request
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/auth/protect.html">
|
||
Protect
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
<section>
|
||
<h3>HTTP</h3>
|
||
<ul>
|
||
<li class="">
|
||
<a href="/1.5/http/request.html">
|
||
Request
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/http/response.html">
|
||
Response
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/http/body.html">
|
||
Body
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/http/response-representable.html">
|
||
ResponseRepresentable
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/http/responder.html">
|
||
Responder
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/http/client.html">
|
||
Client
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/http/server.html">
|
||
Server
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/http/cors.html">
|
||
CORS
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
<section>
|
||
<h3>WebSockets</h3>
|
||
<ul>
|
||
<li class="">
|
||
<a href="/1.5/websockets/droplet.html">
|
||
Droplet
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/websockets/custom.html">
|
||
Custom
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
<section>
|
||
<h3>Testing</h3>
|
||
<ul>
|
||
<li class="">
|
||
<a href="/1.5/testing/modules.html">
|
||
Modules
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/testing/basic.html">
|
||
Basic
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
<section>
|
||
<h3>Deploy</h3>
|
||
<ul>
|
||
<li class="">
|
||
<a href="/1.5/deploy/nginx.html">
|
||
Nginx
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/deploy/supervisor.html">
|
||
Supervisor
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
<section>
|
||
<h3>Version (1.5)</h3>
|
||
<ul>
|
||
<li class="">
|
||
<a href="/1.5/switch/1_5.html">
|
||
1.5
|
||
</a>
|
||
</li>
|
||
<li class="">
|
||
<a href="/1.5/switch/2_0.html">
|
||
2.0
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</section>
|
||
</div>
|
||
</nav>
|
||
|
||
<main>
|
||
<a href="https://github.com/vapor/documentation/blob/master/CONTRIBUTING.md" class="edit">✎ Edit on GitHub</a>
|
||
<h1 id="model">Model</h1>
|
||
<p><code>Model</code> is the base protocol for any of your application's models, especially those you want to persist.</p>
|
||
<blockquote>
|
||
<p><code>Model</code> is only available in Vapor, the Fluent equivalent is <code>Entity</code></p>
|
||
</blockquote>
|
||
<h2 id="example">Example</h2>
|
||
<p>Let's create a simple <code>User</code> model.</p>
|
||
<pre><code class="language-swift">final class User {
|
||
var name: String
|
||
|
||
init(name: String) {
|
||
self.name = name
|
||
}
|
||
}</code></pre>
|
||
<p>The first step to conforming to <code>Model</code> is to import Vapor and Fluent.</p>
|
||
<pre><code class="language-swift">import Vapor
|
||
import Fluent</code></pre>
|
||
<p>Then add the conformance to your class.</p>
|
||
<pre><code class="language-swift">final class User: Model {
|
||
...
|
||
}</code></pre>
|
||
<p>The compiler will inform you that some methods need to be implemented to conform.</p>
|
||
<h3 id="id">ID</h3>
|
||
<p>The first required property is an identifier. This property will contain the identifier when the model is fetched from the database. If it is <code>nil</code>, it will be set when the model is saved.</p>
|
||
<pre><code class="language-swift">final class User: Model {
|
||
var id: Node?
|
||
...
|
||
}</code></pre>
|
||
<h3 id="node-initializable">Node Initializable</h3>
|
||
<p>The next requirement is a way to create the model from the persisted data. Model uses <code>NodeInitializable</code> to achieve this.</p>
|
||
<pre><code class="language-swift">final class User: Model {
|
||
init(node: Node, in context: Context) throws {
|
||
id = try node.extract("id")
|
||
name = try node.extract("name")
|
||
}
|
||
...
|
||
}</code></pre>
|
||
<p>The keys <code>id</code> and <code>name</code> are what we expect the columns or fields in the database to be named. The <code>extract</code> call is marked with a <code>try</code> because it will throw an error if the value is not present or is the wrong type.</p>
|
||
<h3 id="node-representable">Node Representable</h3>
|
||
<p>Now that we have covered initializing the model, we need to show how to save it back into the database. Model uses <code>NodeRepresentable</code> to achieve this.</p>
|
||
<pre><code class="language-swift">final class User: Model {
|
||
func makeNode(context: Context) throws -> Node {
|
||
return try Node(node: [
|
||
"id": id,
|
||
"name": name
|
||
])
|
||
}
|
||
...
|
||
}</code></pre>
|
||
<p>When a <code>User</code> is saved, the <code>makeNode()</code> method will be called and the resulting <code>Node</code> will be saved to the database. The keys <code>id</code> and <code>name</code> are what we expect the columns or fields in the database to be named.</p>
|
||
<blockquote>
|
||
<p>In most of the cases you do not need to be concerned about <code>context</code> argument of the <code>makeNode(context:)</code> method. It’s a part of the protocol that allows extensibility in more advanced or specific scenarios.</p>
|
||
</blockquote>
|
||
<h2 id="preparations">Preparations</h2>
|
||
<p>Some databases, like MySQL, need to be prepared for a new schema. In MySQL, this means creating a new table. Preparations are also equatable to migrations, as they can be used to alter schemas after they've already been created.</p>
|
||
<h3 id="prepare">Prepare</h3>
|
||
<p>Let's assume we are using a SQL database. To prepare the database for our <code>User</code> class, we need to create a table. If you are using a database like Mongo, you can leave this method unimplemented.</p>
|
||
<pre><code class="language-swift">final class User {
|
||
static func prepare(_ database: Database) throws {
|
||
try database.create("users") { users in
|
||
users.id()
|
||
users.string("name")
|
||
}
|
||
}
|
||
...
|
||
}</code></pre>
|
||
<p>Here we create a table named <code>users</code> that has an identifier field and a string field with the key <code>name</code>. This matches both our <code>init(node: Node)</code> and <code>makeNode() -> Node</code> methods.</p>
|
||
<h3 id="revert">Revert</h3>
|
||
<p>An optional preparation reversion can be created. This will be run if <code>vapor run prepare --revert</code> is called.</p>
|
||
<pre><code class="language-swift">final class User {
|
||
static func revert(_ database: Database) throws {
|
||
try database.delete("users")
|
||
}
|
||
...
|
||
}</code></pre>
|
||
<p>Here we are deleting the table named <code>users</code>.</p>
|
||
<h3 id="preparations-as-migrations">Preparations as Migrations</h3>
|
||
<p>If you want to add a field to your table after you've already created the initial schema, you can create a struct or class that conforms to <code>Preparation</code> like so:</p>
|
||
<pre><code class="language-swift">
|
||
struct AddFooToBar: Preparation {
|
||
static func prepare(_ database: Database) throws {
|
||
try database.modify("bars", closure: { bar in
|
||
bar.string("foo", length: 150, optional: false, unique: false, default: nil)
|
||
})
|
||
}
|
||
|
||
static func revert(_ database: Database) throws {
|
||
|
||
}
|
||
}</code></pre>
|
||
<p>Then, in your Droplet setup, add this line: <code>drop.preparations.append(AddFooToBar.self)</code></p>
|
||
<h3 id="droplet">Droplet</h3>
|
||
<p>To run these prepations when the applications boots, you must add the Model to the <code>Droplet</code>.</p>
|
||
<pre><code class="language-swift">let drop = Droplet()
|
||
|
||
drop.preparations.append(User.self)</code></pre>
|
||
<blockquote>
|
||
<p>Note: Preparations must be appended before the Droplet is run.</p>
|
||
</blockquote>
|
||
<h2 id="full-model">Full Model</h2>
|
||
<p>This is what our final <code>User</code> model looks like:</p>
|
||
<pre><code class="language-swift">import Vapor
|
||
import Fluent
|
||
|
||
final class User: Model {
|
||
var id: Node?
|
||
var name: String
|
||
|
||
init(name: String) {
|
||
self.name = name
|
||
}
|
||
|
||
init(node: Node, in context: Context) throws {
|
||
id = try node.extract("id")
|
||
name = try node.extract("name")
|
||
}
|
||
|
||
func makeNode(context: Context) throws -> Node {
|
||
return try Node(node: [
|
||
"id": id,
|
||
"name": name
|
||
])
|
||
}
|
||
|
||
static func prepare(_ database: Database) throws {
|
||
try database.create("users") { users in
|
||
users.id()
|
||
users.string("name")
|
||
}
|
||
}
|
||
|
||
static func revert(_ database: Database) throws {
|
||
try database.delete("users")
|
||
}
|
||
}</code></pre>
|
||
<h2 id="interacting">Interacting</h2>
|
||
<p>Now that <code>User</code> conforms to <code>Model</code>, it has a plethora of new methods like <code>find()</code>, <code>query()</code>, <code>makeJSON()</code> and more.</p>
|
||
<h3 id="fetch">Fetch</h3>
|
||
<p>Models can be fetched by their database identifier.</p>
|
||
<pre><code class="language-swift">let user = try User.find(42)</code></pre>
|
||
<h3 id="save">Save</h3>
|
||
<p>Newly created models can be saved to the database.</p>
|
||
<pre><code class="language-swift">var user = User(name: "Vapor")
|
||
try user.save()
|
||
print(user.id) // prints the new id</code></pre>
|
||
<h3 id="delete">Delete</h3>
|
||
<p>Persisted models with identifiers can be deleted.</p>
|
||
<pre><code class="language-swift">try user.delete()</code></pre>
|
||
<h2 id="model-vs-entity">Model vs. Entity</h2>
|
||
<p>Model has a couple of extra conformances that a pure Fluent entity doesn't have.</p>
|
||
<pre><code class="language-swift">public protocol Model: Entity, JSONRepresentable, StringInitializable, ResponseRepresentable {}</code></pre>
|
||
<p>As can be seen in the protocol, Vapor models can automatically convert to <code>JSON</code>, <code>Response</code>, and even be used in type-safe routing.</p>
|
||
<h2 id="options">Options</h2>
|
||
<p>Change the table/collection name</p>
|
||
<pre><code class="language-swift">static var entity = "new_name"</code></pre>
|
||
</main>
|
||
|
||
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
|
||
<script src="/1.5/scripts/highlight.pack.js"></script>
|
||
|
||
<script>
|
||
hljs.registerLanguage("leaf", function (hljs) {
|
||
/* Author: Hale Chan <halechan@qq.com> */
|
||
return {
|
||
c: [
|
||
{
|
||
cN: 'function',
|
||
b: '#+',
|
||
e: '[)] ',
|
||
rB: true,
|
||
eE: false,
|
||
c : [
|
||
{
|
||
b: '[(]',
|
||
e: '[)]',
|
||
eE: true,
|
||
c: [
|
||
{
|
||
cN: 'string',
|
||
b: '"',
|
||
e: '"'
|
||
},
|
||
{
|
||
cN: 'title',
|
||
b: '[A-Za-z_][A-Za-z_0-9]*'
|
||
}
|
||
]
|
||
},
|
||
{
|
||
cN: 'keyword',
|
||
b: '#+([A-Za-z$_][0-9A-Za-z$_]*)?',
|
||
eP: true
|
||
},
|
||
]
|
||
}
|
||
]
|
||
};
|
||
});
|
||
</script>
|
||
|
||
<script>
|
||
$(function() {
|
||
// Syntax highlighting
|
||
hljs.initHighlightingOnLoad();
|
||
|
||
if (navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) {
|
||
$('body').addClass('safari');
|
||
}
|
||
|
||
$('h1, h2, h3, h4, h5, h6').each(function(key, item) {
|
||
var $item = $(item);
|
||
var id = $item.attr('id');
|
||
if (id) {
|
||
var link = $('<a>');
|
||
link.attr('href', '#' + id);
|
||
$item.wrap(link);
|
||
}
|
||
})
|
||
|
||
$('a.toggle').on('click', function(e){
|
||
e.preventDefault();
|
||
console.log('hi');
|
||
var body = $('body');
|
||
if (body.hasClass('toggled')) {
|
||
body.removeClass('toggled');
|
||
} else {
|
||
body.addClass('toggled');
|
||
}
|
||
})
|
||
|
||
var lastScroll = 0;
|
||
var isUp = false;
|
||
var win = $(window);
|
||
|
||
win.on('scroll', function(){
|
||
var scrollTop = win.scrollTop();
|
||
var scrollBottom = win.scrollTop() + win.height();
|
||
var docHeight = $(document).height();
|
||
|
||
if ( scrollTop < 0 || scrollBottom >= docHeight ){
|
||
return;
|
||
}
|
||
|
||
if (scrollTop > (lastScroll + 0) && scrollTop >= 0) {
|
||
if (!isUp) {
|
||
$('a.toggle.show').addClass('hide')
|
||
}
|
||
isUp = true;
|
||
lastScroll = scrollTop;
|
||
} else if (scrollTop < (lastScroll - 0)) {
|
||
if (isUp) {
|
||
$('a.toggle.show').removeClass('hide')
|
||
}
|
||
isUp = false;
|
||
lastScroll = scrollTop;
|
||
}
|
||
});
|
||
|
||
// scroll to the active nav item
|
||
$("nav .scroll").animate({
|
||
scrollTop: $("nav .active").offset().top - 157
|
||
}, 0);
|
||
|
||
// var lastScrollTop = 0;
|
||
// $(window).scroll(function(event){
|
||
// var st = $(this).scrollTop();
|
||
// if (st > lastScrollTop){
|
||
// $('a.toggle.show').addClass('hide')
|
||
// } else {
|
||
// $('a.toggle.show').removeClass('hide')
|
||
// }
|
||
// lastScrollTop = st;
|
||
// });
|
||
});
|
||
</script>
|
||
|
||
<script>
|
||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||
|
||
ga('create', 'UA-76177358-4', 'auto');
|
||
ga('send', 'pageview');
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|