App 2: insert Database Function (Scala 3 Video)
As a quick reminder, in the following lessons where we’ll create database-related code, all of these functions will be placed inside a Database
class that looks like this:
class Database(dbFilename: String):
// def insert = ???
// def selectAll = ???
// def delete = ???
end Database
insert
The first Database
function we’ll need is an insert
function. Because this is just a flat-file database, this function will append a single new record to the database file. Therefore, I know that it takes a string as input, and I start to sketch the insert
function like this:
def insert(record: String)
Next — and mostly because I’ve been through this process before — I know that I want this function to use another writeToFile
function to do the actual file-writing. I know this because the delete
and update
functions I’ll write in the following lessons will require me to read in the entire file and then re-write it completely. So writeToFile
will need to be able to both (a) append to a file and (b) completely overwrite the file (and I don’t want to have a boolean append
parameter in the insert
function).
Therefore, let’s forget about the insert
function for a moment and first work on the writeToFile
function.
writeToFile
writeToFile
will be another function inside the Database
class, and because nobody outside of Database
will need to access it, I start by making it private:
private def writeToFile
Next, because I know that delete
and update
will require me to completely re-write the file, I define it to accept a list of strings, which in the most generic sense in Scala is a Seq[String]
:
private def writeToFile(lines: Seq[String])
------------------
I also know that I want to set a boolean append
flag when calling this function. This lets me declare that I want to overwrite the file, or append to it:
private def writeToFile(lines: Seq[String], append: Boolean):
---------------
Now the final thing to think about is its return type. As I mentioned in our rules, any function that communicates with the outside world must return a Try
value, so that gives me this:
private def writeToFile(lines: Seq[String], append: Boolean): Try[
----
And now the only question is, “What should the success value be inside Try
?”
Since I don’t need any data back when writing a file — I just need a Success
or Failure
to know if it worked or now — this type can be Unit
. (As mentioned before, this means that I’m not interested in the return type.) So I complete the function signature like this:
private def writeToFile(lines: Seq[String], append: Boolean): Try[Unit] =
---------
Implementing writeToFile
Next, I’m going to do something controversial and violate two of my rules: I’m going to use a var
field and a null
value. However, I’m only doing this because:
- I suspect that many people reading this book are familiar with languages like Java and Kotlin, and therefore writing to files with Java classes and methods.
- The more-correct solution requires a little more Scala knowledge that I don’t want to get into yet. (I share this correct solution in the “
Using
” lesson in the Appendix.)
Therefore, because this is Java/OOP code that uses the java.io.*
classes, I write the body of the function as shown here, with comments describing the important parts:
import java.io.*
import scala.util.{Try, Success, Failure}
/**
* Notice that `dbFilename` comes in through a side door.
*/
private def writeToFile(lines: Seq[String], append: Boolean): Try[Unit] =
// i create this variable before the 'try' block because i need to
// access it in the 'finally' block:
var bw: BufferedWriter = null
try
// this is basically standard Java code that you use to
// create a BufferedWriter:
bw = BufferedWriter(FileWriter(File(dbFilename), append))
// here i loop over every String in 'lines', and write each
// String (one at a time) to the file. each String is
// followed by a newline character:
for line <- lines do bw.write(s"$line\n")
// if the previous code does not throw an exception, flow
// comes here, and we will return this Success value:
Success(true)
catch
// if the previous code DOES throw an exception, control
// comes here and we will return a Failure:
case e: Throwable => Failure(e)
finally
// the 'finally' block always executes, so this is where
// we release/close resources we opened. note that you need
// to check if 'bw' is null first, because the previous code
// may explode before 'bw' is bound to the BufferedWriter:
if bw != null then bw.close
I added a lot of comments in there to explain how that code works. So you can read that function more easily, here it is again without those comments:
private def writeToFile(lines: Seq[String], append: Boolean): Try[Unit] =
var bw: BufferedWriter = null
try
bw = BufferedWriter(FileWriter(File(dbFilename), append))
for line <- lines do bw.write(s"$line\n")
Success(true)
catch
case e: Throwable => Failure(e)
finally
if bw != null then bw.close
Again, this is a very Java/OOP solution to this problem, and if you’d like to see the Scala solution, see the “Using
” lesson in the Appendix.
Getting back to the insert function
Now that I have the writeToFile
function, all I have to do is finish the insert
function. I already sketched this portion of it:
def insert(record: String)
and now that I know I want to call writeToFile
, I know that insert
’s body should look like this:
def insert(record: String) ...
writeToFile(List(record), true)
In that code, I wrap the String
that’s received in a List
, because writeToFile
expects to receive a Seq[String]
. And because I know that writeToFile
returns a Try[Unit]
, and I also want to return a Try
from insert
, I use that as this function’s return type as well:
def insert(record: String): Try[Unit] =
writeToFile(List(record), true)
That completes the insert
function.
Update: All of my new videos are now on
LearnScala.dev