Getting Started with Server Side Swift: 1.2

Jonathan Guthrie
Server Side Swift and More
9 min readMar 13, 2017

Introducing StORM

Welcome to the third installment in my “Getting Started with Server Side Swift” series. Throughout this series I’m going to explain how to do the basics of getting up and running with various different aspects of Server Side Siwft usng the Perfect toolkit.

Today I will be introducing the Swift ORM “StORM”, which has been designed to work with Perfect and it’s database connectors. I’ll cover setting up a class to work with StORM, and the basics of reading, writing, deleting data.

First of all, let’s look at what an ORM is.

ORM stands for Object Relational Mapping, and is a methodology that is meant to provide a link between your objects in your swift code and a database entry.

It means that you can create and populate an object in code, and invoke a “save” command, and it will save the record — updating it if it already exists, otherwise creating a new one.

Conversely, you can then also instruct the ORM to get a specific row from the database and it will populate the object, or if more than one row matches your find criteria, return an ordered array of populated objects.

And lastly, an ORM will help you delete an entry.

All this without having to create all the connections, handle all the database open/close operations, and all the other detailed code that is often required to handle database operations. That’s all covered inside an ORM so you don’t have to.

The real magic of an ORM however is in two other concepts:

  • If you are using a SQL database, you don’t need to know SQL to make all this happen.
  • Second, the basic operations and syntax should be exactly the same from one database to another. A “Get” operation is the same as if you are using MySQL, as it is with MongoDB. The same with a “Save” or a “Delete”.

Today we’re going to look at ORM operations in Perfect using SQLite StORM. As I have previously mentioned, all these operations will be virtually identical for the other StORM variants: MySQL, Postgres, CouchDB, and MongoDB.

First of all lets create a new project in Perfect Assistant. We’ll make an “Empty Executable Project”, and call it “StORMDemo”

Now, this has no dependencies at all right now — lets add the SQLiteStorm dependency… and then press “Save Changes”.

Immediately it will start fetching all the needed files, and will re-create the Xcode project. Once it’s finished, click the “Open: Xcode Project” button.

Opening up the main.swift, the default is a print hello world statement. Before we run that we need to select the executable target scheme, and while we are at it we will set the project working directory which we are going to need later.

Set the correct executable target.
Add the working directory property.

Running this we see that “Hello, World!” has been printed out in the console view, then the program exists. Unlike a web server executable which keeps running till you close it, this executable is a simple run-and-exit.

This “Hello, World!” isn’t what we are here for — the first step is going to be defining the database configuration. For SQLite, we only need to set up one thing — the database name and location. For others you’ll have to set up parameters like IP address, port, username and password, but it’s a lot easier to get going with SQLite as a teaching tool.

First of all, we need to add to our main.swift file:

import SQLiteStORM
import StORM
SQLiteConnector.db = "./stormdemo"

This tells the ORM that the location and name of the file is at the surface of our working directory, and it’s going to be called “stormdemo”

The next thing we need to do is add a class, give it some properties, and hook it up to be used with the ORM.

Create a new file for our class, Monster.swift

In this file we also need to import SQLiteStORM, because we are going to use it as a super class. Lets start our class and give it some properties:

import SQLiteStORMclass Monster: SQLiteStORM {
var id: Int = 0
var name: String = ""
var legs: Int = 4
var dangerlevel: String = "none"
}

Note that I’ve given the properties both a type, and an initial default value. This is simply so that we don’t have to create an initializer and can dive straight into using it.

We need to add a couple of functions as well. The first one, the “to” describes how to interpret the values coming back from the database. This might seem redundant at first but later I’ll show you why.

The next is a simple iterator that will help the ORM make an array of our Monster object out of multiple results.

override func to(_ this: StORMRow) {
id = this.data["id"] as? Int ?? 0
name = this.data["name"] as? String ?? ""
legs = this.data["legs"] as? Int ?? 0
dangerlevel = this.data["dangerlevel"] as? String ?? "none"
}
func rows() -> [Monster] {
var rows = [Monster]()
for i in 0..<self.results.rows.count {
let row = Monster()
row.to(self.results.rows[i])
rows.append(row)
}
return rows
}

Back to main.swift to get things started.

var start = Monster()
try? start.setup()

We’re creating an instance of the class so we can run “setup”, which will check to make sure the table “monster” exists in the database, and will create it if not. In this situation we’re going to ignore the possibility of a fail, because for this a fail is not necessarily wrong.

Next we’re going to make a new instance, give the properties some content, and save it. The save has to be in a do/catch so we can handle any errors well.

var thing1 = Monster()
thing1.name = "Randall"
thing1.legs = 4
thing1.dangerlevel = "moderate"
do {
try thing1.save()
print("Randall was saved")
} catch {
print(error)
}

Lets run this, and see what happens.

[INFO] Running setup: monster
Randall was saved
Program ended with exit code: 0

OK so it’s saved, but where’s the data?

For this demo I’m going to use a free, open source tool called “DB Browser for SQLite”, which you can download from http://sqlitebrowser.org

Opening the database in this, it shows you that there’s a table called “monster”. Looking at the data itself, yes, there’s the row with the data we just saved about Randall.

Now there’s one thing thats different — the “id” column. We didn’t set that value, in fact we deliberately left that out. What happened is that the database itself gave it a value, because in the “setup” function that created the table, it assumes the first property in the class is the primary key, and if it is of type Int it will tell the database to make that column self-assigning, and it will start at 1 and climb for every new row. This quality is also known as implementing “auto-incrementing primary keys”.

This also means that if we don’t assign a value, it will create a new one. In fact if we re-run this, we will now get 2 Randalls.

Back in our main.swift, lets add the id property and assign in the value of 1, which was the row’s id in the database, and crank up his danger level.

thing1.id = 1
thing1.name = "Randall"
thing1.legs = 4
thing1.dangerlevel = "nasty"

Running this and looking at the database, you will see that the data has not been duplicated but it has changed. That’s because the ORM has seen that we have a value for the id and so it attempts to perform an update instead of making a new row.

Now lets make some changes to our class… adding an initializer, and making it a little easier to set danger levels.

override init(){
super.init()
}
init(_ name: String, legs: Int = 4, danger: String = "none") {
super.init()
self.name = name
self.legs = legs
self.dangerlevel = danger
}

So now lets fix the danger levels. We don’t really want to have inconsistency in the data in here — it makes it harder to find things if someone makes a typo. So lets create an enum.

enum DangerLevels: String {
case none, tame, moderate, nasty, toxic, lethal
}

Now we update the property for the class:

var dangerlevel: DangerLevels = .none

… change the init:

danger: DangerLevels = .none

… and change the “to” function:

if let testVal = DangerLevels(
rawValue: (this.data[“dangerlevel”] as! String)
) {
dangerlevel = testVal
}

Now if we go back to the main.swift we need to also change the input so it’s a value from the enum:

thing1.dangerlevel = .nasty

Change to .toxic, then run and lets look at the data: and the danger level is now “toxic”.

Lets add another couple of monsters and then we can look at delete and find operations.

func makeNew() {
let thing2 = Monster()
thing2.name = "Sully"
thing2.legs = 2
thing2.dangerlevel = .tame
do {
try thing2.save()
print("Sully was saved")
} catch {
print(error)
}
let thing3 = Monster()
thing3.name = "Mike"
thing3.legs = 2
thing3.dangerlevel = .tame
do {
try thing3.save()
print("Mike was saved")
} catch {
print(error)
}
let thing4 = Monster()
thing4.name = "Roz"
thing4.legs = 2
thing4.dangerlevel = .lethal
do {
try thing4.save()
print("Roz was saved")
} catch {
print(error)
}
}
makeNew()

Note that after running this, I’m commenting out the makeNew function call because it would give us a lot of duplicates if we keep it there for each run.

[INFO] Running setup: monster
Randall was saved
Sully was saved
Mike was saved
Roz was saved
Program ended with exit code: 0

Now we have more than one record, we can do a find. To illustrate, we will find all “tame” monsters:

var tameMonsters = Monster()
try? tameMonsters.find([“dangerlevel”:”tame”])
print(“======”)
print(“Tame Monsters:”)
for thing in tameMonsters.rows() {
print(thing.name)
}

Which will output:

[INFO] Running setup: monster
Randall was saved
=========
Tame Monsters
Sully
Mike
Program ended with exit code: 0

So I’m going to go and make a new row in the db, and I’ll show you how to load it, and then delete it.

let lethalDonkey = Monster()
lethalDonkey.name = “lethalDonkey”
lethalDonkey.legs = 19
lethalDonkey.dangerlevel = .moderate
do {
try lethalDonkey.save()
print(“lethalDonkey was saved”)
} catch {
print(error)
}

Now we find it, print the id and then delete it:

let q = Monster()
try? q.find([“name”:”lethalDonkey”])
print(“lethalDonkey id is: \(q.id)”)
print(“lethalDonkey has \(q.legs) legs”)
try? q.delete()

Lets look back in the database and check it’s not there…

We can also “get” a row if we know the id: we know the id of Randall from the start. Lets try this:

let r = Monster()
try? r.get(1)
print(“Randall danger level is: \(r.dangerlevel)”)
print(“Randall has \(r.legs) legs”)

Running this, we should now see something like this, with the whole “chain” of what we’ve done.

[INFO] Running setup: monster
Randall was saved
=========
Tame Monsters
Sully
Mike
lethalDonkey was saved
lethalDonkey id is: 5
lethalDonkey has: 19 legs
====
1
Randall
4
moderate
Program ended with exit code: 0

The great thing is these same basic techniques work the same across all the SQL versions, and almost the same in the NoSQL versions. The only difference is that NoSQL use strings for primary keys so we have to do things a little differently but that’s for another day.

StORM is documented here at https://www.perfect.org/docs/StORM.html — go check it out for a deeper dive.

For examples visit GitHub.com/perfectexamples — there is an ever growing library of examples and demos there.

And of course if you’re looking for live help from our awesome community, join our Slack channel via www.perfect.ly

The complete “demo” files are located on GitHub. Note that it is the final working copy, so I recommend not running the project as such, but copy-pasting if needed, or compare to your code if there’s something not quite working for you.

If you prefer to watch the video of this, heres the link: Getting Started with Perfect — Introduction to StORM

--

--

Jonathan Guthrie
Server Side Swift and More

Developer Evangelist, Musician, and Active Activist... Newmarket ON, Canada