Let's start by defining our Post
model:
struct Post {
let title: String
var rawBody: String { // The original, markdown body.
didSet {
// For updating body, truncatedBody and readingTime automatically.
// didSet doesn't get called on init too, sadly.
}
}
fileprivate(set) var body: String // The html body.
var truncatedBody: String // The html body, truncated to x chars.
fileprivate(set) var readingTime: String
let datetime: String // The date, in yyyy-MM-dd-HHmm format.
let link: String // The link, created from the title, in post-title format.
let date: String // The short date, to be used as subtitle.
var modified: String // The last modified date, in datetime format.
}
I'm not going into details on how body truncation, Markdown -> HTML conversion and reading time work, as they can be found here, and they are pretty nicely documented.
Next, making Post
database compliant: Vapor contains a framework called Fluent
, an ORM tool for a handful of database providers, and we'll use it for talking with PostgreSQL. We have to conform to the NodeInitializable
, NodeRepresentable
, Preparation
and Model
protocols:
final class Post: NodeInitializable {
let storage = Storage()
// Indicates if the object was retrieved from the database, or created.
// Do not modify it directly.
var exists = false
// [...]
init(row: Row) throws {
title = try row.get("title")
body = try row.get("body")
rawBody = try row.get("rawbody")
truncatedBody = try row.get("truncatedbody")
datetime = try row.get("datetime")
date = try row.get("date")
modified = try row.get("modified")
link = try row.get("link")
readingTime = try row.get("readingtime")
}
init(node: Node) throws {
title = try node.get("title")
body = try node.get("body")
rawBody = try node.get("rawbody")
datetime = try node.get("datetime")
modified = try node.get("modified")
link = try node.get("link")
truncatedBody = try node.get("truncatedbody")
readingTime = try node.get("readingtime")
date = try node.get("date")
}
}
// Will be in the same file as well, just because it's really short.
extension Post: NodeRepresentable {
// Helps Fluent save a Post to the database.
func makeNode(context: Context) throws -> Node {
return try Node(node: [
"id": id,
"title": title,
"body": body,
"rawBody": rawBody,
"truncatedBody": truncatedBody,
"datetime": datetime,
"link": link,
"readingTime": readingTime,
"date": date,
"modified": modified]
)
}
}
// Post+Model.swift:
extension Post: Model {
func makeRow() throws -> Row {
var row = Row()
try row.set("title", title)
try row.set("body", body)
try row.set("rawbody", rawBody)
try row.set("truncatedbody", truncatedBody)
try row.set("datetime", datetime)
try row.set("date", date)
try row.set("modified", modified)
try row.set("link", link)
try row.set("readingtime", readingTime)
return row
}
}
extension Post: Preparation {
// This tells Fluent how the table schema should look like.
// These will be all lowercase, because PostgreSQL column names are all lowercase.
static func prepare(_ database: Database) throws {
try database.create(self) { posts in
posts.id()
posts.string("title", length: 9_999, optional: false, unique: false, default: nil)
posts.string("body", length: 999_999, optional: false, unique: false, default: nil)
posts.string("rawbody", length: 999_999, optional: false, unique: false, default: nil)
posts.string("truncatedbody", length: 1200, optional: false, unique: false, default: nil)
posts.string("datetime", length: 15, optional: false, unique: false, default: nil)
posts.string("date", length: 12, optional: false, unique: false, default: nil)
posts.string("modified", length: 15, optional: false, unique: false, default: nil)
posts.string("link", length: 100, optional: false, unique: true, default: nil)
posts.string("readingtime", length: 15, optional: false, unique: false, default: nil)
}
}
// This tells Fluent what reverting means, in our case it will just drop the table.
static func revert(_ database: Database) throws {
try database.delete(self)
}
}
Configuring PostgreSQL is a matter of filling the postgresql.json
file, located in Config/secrets
:
{
"host": "127.0.0.1",
"user": "roland", // Usually your Mac's username.
"password": "",
"database": "roland", // Usually your Mac's username.
"port": 5432
}
Now, making use of PostgreSQL is as easy as:
// Saving:
// Has to be a variable, because the save() method is mutating - it updates the id field.
var post = Post(title: "Test title", rawBody: "Test body", datetime: "2017-03-09-1550")
try post.save()
// Fetching:
let posts = try Post.all()
let firstPost = posts.first
try firstPost?.delete()
// More complex fetching:
let sortedPosts = try Post.makeQuery()
.sort("datetime", .descending)
.sort("title", .ascending)
sortedPosts.forEach { print($0.datetime + ": " + $0.title) }