I decided to build the web service for my iOS and Android app using Perfect which is a Swift server implementation. I am using MySQL to host a database and wanted to a nice way to get the database tables written out as JSON. I had some problems but here is what I came up with:
My first attempt was to create a base class that I could subclass for each table type and do the read/write database operations and the JSON operations in the base class. But I ran into a lot of problems getting the base class to access the overridden properties and methods of the subclasses. Then I discovered this. Fist I create a simple protocol that all the record classes will use:
protocol DatabaseRecord : RequestHandler {
var sqlTableName: String {get}
var loaded: Bool {get set}
func loadRow(row: [String])
func getDictionary() -> [String : String]
func load(forName:String)
func load(forID:Int)
func getAll() -> [AnyObject]
init()
}
Then I create an extension to the protocol so all of this will get added to all of the classes that implement the DatabaseRecord protocol.
extension DatabaseRecord {
The methods here to load from the database don't seem like a big deal, but this just didn't work as a super class because despite all my google searching and efforts I couldn't get the base class to call the subclass methods or get the sqlTableName from the sub classes. But as an extension this jus works without any strange code.
func loadFromDatabase(query:String) {
var mysql = MySQLConnection.mysql;
MySQLConnection.connectToMySQL()
defer {
mysql.close()
}
if mysql.query(query) {
if let results = mysql.storeResults() {
if results.numRows() >= 1 {
if let row = results.next() {
loadRow(row)
return
}
}
}
}
}
func load(forName:String) {
self.loadFromDatabase("SELECT * FROM \(sqlTableName) WHERE name = \"\(forName)\"")
}
func load(forID:Int) {
self.loadFromDatabase("SELECT * FROM \(sqlTableName) WHERE id = \"\(forID)\"")
}
This next method was where I had the most problems. I tried using Self, dynamicType, and everything else I could think of to get the sqlTableName from the subclasses and got it going but the creating objects of my subclasses and adding them to an array that I would return seemed impossible. Now as an extension the sqlTableName just works, and I can create a new object of the correct class using self.dynamicType.init() and add it to the array.
func getAll() -> [AnyObject] {
var records = [AnyObject]()
var mysql = MySQLConnection.mysql;
MySQLConnection.connectToMySQL()
defer {
mysql.close()
}
if mysql.query("SELECT * FROM \(sqlTableName)") {
if let results = mysql.storeResults() {
results.forEachRow { row in
let obj = self.dynamicType.init()
obj.loadRow(row)
if let o = obj as? AnyObject {
records.append(o)
}
}
}
}
return records
}
PerfectLib has a JSON encoder but when I tried to use it, it would crash. It was late and the JSON that I need to generate was simple so I just quickly wrote this to create the JSON for my table:
func encodeAsJSON(dicts:Array<[String:String]>) -> String {
var str: String = "["
for dict in dicts {
var first = true
for (key, value) in dict {
if !first {
str.appendContentsOf(",")
} else {
first = false
}
str.appendContentsOf("{\"\(key)\":\"\(value)\"}")
}
}
str.appendContentsOf("]")
return str
}
Now I can add a request handler to the extension and now all my database record classes have a easy way to take a http get request and turn it into JSON array.
func handleRequest(request: WebRequest, response: WebResponse) {
let records = getAll()
var dicts = Array<[String:String]>()
for rec in records {
if let r = rec as? DatabaseRecord {
let dict = r.getDictionary()
dicts.append(dict)
}
}
response.appendBodyString(encodeAsJSON(dicts))
response.requestCompletedCallback()
}
}
So then I have a simple Swift class representing a SQL database record. For example:
/*
auto_make SQL table
+-------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(255) | YES | | NULL | |
+-------------+------------------+------+-----+---------+----------------+
*/
class AutoMakeRecord: DatabaseRecord {
var id: Int?
var name: String = ""
var loaded: Bool
var sqlTableName: String {
return "auto_make"
}
func loadRow(row: [String])
{
id = Int(row[0]) ?? -1
name = row[1]
loaded = true
}
func getDictionary() -> [String : String] {
var dict = [String: String]()
dict["id"] = String(id!)
dict["name"] = name
return dict
}
required init() {
loaded = false
}
}
Now a public method that registers the routes.
public func PerfectServerModuleInit() {
Routing.Handler.registerGlobally()
// Create Routes
Routing.Routes["/auto_make"] = { _ in return AutoMakeRecord() }
}
And it is easy to create additional classes for database tables and add them to the routes.
Of course I will want to create http handlers that will allow to more than just dump out tables to JSON, but this now gives me the base I need to create the rest of my web service.