MovieMan – the database

MovieMan does not use any database software, but it still needs to store the movie data the user enters. Ideally, the data would be stored on disk, preferably in a platform-specific, per-user, application data directory, but the goal behind MovieMan is to demonstrate D language features, not to develop a fully featured program. To keep things simple, the book implementation will keep the movie data in memory. A good exercise for the reader after finishing the book could be to implement the saving and loading of the movie data to and from disk.

This section lays out the database API. It won't be fully implemented at this point; we'll complete the implementation later with features from the next two chapters. Using the skeleton API we develop here, we'll also flesh out the menu classes we implemented back in Chapter 3, Programming Objects the D Way.

db.d

$MOVIEMAN/source/movieman/db.d is the home of the Movie type and the database, a simple array wrapped by a custom type. The following two lines go at the top of the file:

module movieman.db;
import std.stdio;

Next is the declaration of the Movie type. It's a simple POD (Plain Old Data) type with no member functions. Later, we'll add an opCmp implementation to use for sorting:

struct Movie {
  string title;
  uint caseNumber;
  uint pageNumber;
}

Before we see the DB API, let's add the following to the bottom of the file:

private:
  DBTable!Movie _movies;

  struct DBTable(T) {
    T[] _items;
  }

DBTable is a template, though in the book it's only ever going to be instantiated with one type. As an exercise, you might expand the program to manage audio CDs or books, in which case the templated type will come in handy. For now, we're going to add only one member function to DBTable: an overload of the append operator that we'll use to add new movies to the database. Later we'll add logic to this function to indicate the array should be sorted:

void opOpAssign(string op : "~")(auto ref T t) {
  _items ~= t;
}

The last function we'll implement fully in this module is addMovie. Go back up and add the following below the declaration of Movie but above the private:, as it needs to be a public function:

void addMovie(Movie movie) {
  _movies ~= movie;
  writefln("
Movie '%s' added to the database.", movie.title);
}

The deleteMovies function will eventually remove one or more movies from the database, but for the moment all it does is pretend:

void deleteMovies(Movie[] movies) {
  writeln();
  foreach(ref movie; movies)
    writefln("Movie '%s' deleted from the database.", movie.title);
}

movieExists will be used to determine whether a movie has already been added to the database. It does a simple comparison of the titles, as it's possible for multiple movies to exist on the same page in the same case. It's also possible for more than one movie to have the same title, but that will be accounted for in the menu handler. For now, this function always returns false:

bool movieExists(string title) {
  return false;
}

Finally, there are four versions of getMovies: one to get all movies, one to fetch movies by title, one to fetch by case, and one to fetch by case and page number. For now, each simply returns the entire movie array:

auto getMovies() {
  return _movies._items;
}
auto getMovies(string title) {
  return _movies._items;
}
auto getMovies(uint caseNumber) {
  return _movies._items;
}
auto getMovies(uint caseNumber, uint pageNumber = 0) {
  return _movies._items;
}

Back to the menus

The menu classes can be updated to use the new database API, but before doing so we're going to need a utility function to print a movie to the screen. Idiomatic D encourages the use of generic functions that can be used with multiple types. From that perspective, something like this might be useful:

void printObject(T)(T obj) {
  import std.uni : asCapitalized;
  foreach(mem; __traits(allMembers, T)) {
    writefln("%s: %s", mem.asCapitalized, __traits(getMember, obj, mem));
  }
}

This uses compile time reflection to get a tuple of all of an object's member names and values, printing each pair on its own line to standard output. Note that it uses the function std.uni.asCaptialized to capitalize the member name without allocating any memory. That's an aesthetic touch, but doesn't really look that good with the Movie type, given that caseNumber is transformed into Casenumber. We can't change the name to case, since that's a keyword, but something such as folder could work. Change pageNumber to page and you're good to go. Or, you could forgo genericity in this case and simply do this:

import movieman.db;
void printMovie(Movie movie) {
  writeln("Title: ", movie.title);
  writeln("Case: ", movie.caseNumber);
  writeln("Page: ", movie.pageNumber);
}

With this in place, we can go back to $MOVIEMAN/source/movieman/menu/main.d and add the highlighted lines to the onAddMovie member function of MainMenu:

void onAddMovie() {
  import movieman.db : Movie, addMovie;
  import movieman.io;
  import std.stdio : writeln;

  auto title = readTitle();
  if(!validateTitle(title))
    return;

  auto caseNumber = readNumber("case");
  if(!validateNumber(caseNumber, "case"))
    return;

  auto pageNumber = readNumber("page");
  if(!validateNumber(pageNumber, "page"))
    return;

  auto movie = Movie(title, caseNumber, pageNumber);
  printMovie(movie);

  if(readChoice("add this movie to the database"))
     addMovie(movie);
  else
    writeln("
Discarding new movie.");
}

All of the highlighted lines are new, but the call to addMovie replaces the call to writeln that was used as a placeholder. Next, open display.d from the same folder. Add the following highlighted line to the top of the page:

import movieman.io,
       movieman.db,
       movieman.menu.menu;

Then go down to the handleSelection member function. We're only going to make one modification so that you can display any movie data you enter. We'll finalize the implementation when we come back to it later in the book. Replace the code in the all case of the switch with the highlighted lines:

override void handleSelection(uint selection) {
  final switch(cast(Options)selection) with(Options) {
    case byTitle:
      auto title = readTitle();
      writeln("Displaying ", title);
      break;
    case allOnPage:
      auto caseNumber = readNumber("case");
      auto pageNumber = readNumber("page");
      writefln("Displaying all on page %s of case %s", pageNumber, caseNumber);
      break;
    case allInCase:
      auto caseNumber = readNumber("case");
      writeln("Displaying all in case ", caseNumber);
      break;
    case all:
      auto movies = getMovies();
      foreach(movie; movies) {
        printMovie(movie);
        if(!readChoice("show the next movie"))
          break;
      }
      break;
    case exit:
      exitMenu();
      break;
  }
}

Now, when you run the program, you can add movie data, then select 2. Display Movie(s) from the main menu, and finally choose 4. Display All Movies from the display menu to see the data you've entered. In Chapter 7, Composing Functional Pipelines with Algorithms and Ranges, we'll finish off this version of MovieMan.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.198.174