Creating a series of classes that represent database objects and facilitate interaction with the database makes a lot of sense to me but how do you do this when the database is a RESTful API server and not a persistent database connection such as MySQL or SQLite? I can still create classes but I can't figure out how to embed the URLConnection. How do you create common CRUD methods and then link them to the URLConnection?
@Tim P I usually create a module [ServiceName], a base class to authenticate and handle errors, and then subclass the base for each endpoint I need. It's straight forward, and very similar to the ActiveRecord structure generated by ARGen.
I'm giving Tim a gold star for this one. (Do we do that here?) His answer, as vague as it was, pointed me in the right direction and I finally figured it out. I say, 'vague' because it was a couple of levels above my current Xojo Wizarding Level and it made a few assumptions that I didn't get... at first. A big thanks to Tim as I would never have figured this out otherwise.
In the interest of sharing, here is how I managed to construct an object-oriented system for interfacing with a REST server. If it saves you some time, let me know.
Remember, the goal is to have a system of classes and subclasses that not only define your data objects (aka database tables) but to also handle all the communication with the server for all CRUD operations. The class structure looks like this:
Public Class Base Public Class Team As Base Public Class Player As Base
First, I created a 'Base' class within a module. I subclass my data objects from this. It handles all communication to and from the REST server by defining the URLConnection as a property and instantiating it in code.
Public Property Connection as URLConnection
Next, I created a method, within Base, called, 'Read' that will be called from its subclasses (and the instance of those subclasses) that instantiates the URLConnection and then uses it to send a request to the server. Before it sends it's request, it adds a handler to the instance that will be triggered when the content is received. This was the 'ah ha' moment for me. I had never done this before so this was a pretty big deal. This is how you use URLConnection without having to drag it into your window!
Public Sub Read(url As String, primaryKey As Integer) Connection = New URLConnection AddHandler Connection.ContentReceived, WeakAddressOf HandleRead Connection.Send("GET", url + "/" + primaryKey.ToString) End Sub
I then created the method that handles the response from the server. It parses the content into a JSONItem and then politely removes itself from the Connection instance. Finally, it sends the JSONItem to an event called, 'Read Complete'.
Public Sub HandleRead(sender As URLConnection, URL As String, HTTPStatus As Integer, content As String) Var data As New JSONItem(content) RemoveHandler Connection.ContentReceived, WeakAddressOf HandleRead RaiseEvent ReadComplete(data) End Sub
The key to this was to first define an event that could then be 'raised'. Notice how it accepts my JSONItem and 'passes' it along to the subclass when it handles the event. This was the key to handing the response back down to the subclass... and ultimately, to the instance of it in my window.
Event ReadComplete(data As JSONItem)
So, now let's say that I have a subclass of, 'Base', called, 'Team'. It has a single property called, 'TeamName' As String. I can now create a method called, 'Read' that extends the Read method in Base. It simply passes up the url and primary key needed. (If your REST server uses a single endpoint for all resources, you could just pass the name of the resource here... if only we could get the name of the class... hmmmm....)
Public Sub Read() Super.Read("https://myapi.example.com/Team", 123) End Sub
At this point, we're finally ready to implement the class in our window. I made a window called, 'TeamWindow' and it's the edit window that appears when the user double-clicks on the name of a team in the list of teams. I added a TextField called, 'TeamName' and the usual buttons for deleting, saving and cancelling. I also added a ProgressWheel, called, 'Wheel'... just for fun.
I added one property to my window that will contain all the data and functionality associated with the object... I called it, 'This'.
Public Property This as Team
Then I added a method that displays the ProgressWheel, adds a handler when the response comes back, and calls the Team.Read() method that sends the url and primary key up to the Base.Read() method that actually sends the request to the REST server.
Public Sub ReadRecord() Wheel.Visible = True AddHandler This.ReadComplete, WeakAddressOf HandleRead This.Read End Sub
When the response comes back, it is handled by Base.HandleRead() which then sends the data down to Team via the ReadComplete event. Team catches the data and populates it's properties with the fresh data.
Sub ReadComplete(data As JSONItem) Handles ReadComplete Me.TeamName = data.Value("TeamName") ReadComplete End Sub
But wait, there's more! Finally, our TeamWindow.HandleRead() method also gets the call because Team.ReadComplete calls itself at the end which allows the window to also handle it.
Public Sub HandleRead(sender As City) RemoveHandler This.ReadComplete, WeakAddressOf HandleRead TeamName.Value = This.TeamName Changed = False Wheel.Visible = False DeleteButton.Enabled = True End Sub
After politely removing itself (as all good handlers should), it makes sure that all fields have the latest Team-related data, tells the window that nothing has changed, turns off the ProgressWheel and enables the delete button.
Rinse and repeat for Create, Update, and Delete functions.
The result is that I now have very clean windows without a lot of messy code. Just the basics. All my API communications are handled in my data classes and can be easily ported to other projects.
I even created self-containing PopupMenus and ListBoxes that populate themselves by automatically querying the server when needed! :-)