================================
 Schevo Tutorial: Movie Reviews
================================

---------------
 Installment 1
---------------

In this installment of this tutorial, we will show you how to create a
skeleton Schevo application and a simple database schema. As you update the
schema, we will show you how to explore the database using the Python
interpreter. Here is what the final schema will look like:

.. code-block:: python

    """Schema for MovieReviews."""

    from schevo.schema import *
    schevo.schema.prep(locals())


    class SchevoIcon(E.Entity):

        _hidden = True

        name = f.string()
        data = f.image()

        _key(name)


    class Actor(E.Entity):

        name = f.string()

        _key(name)

        def x_movies(self):
            return [casting.movie
                    for casting in self.m.movie_castings()]


    class Director(E.Entity):

        name = f.string()

        _key(name)


    class Movie(E.Entity):

        title = f.string()
        release_date = f.date()
        director = f.entity('Director')
        description = f.string(multiline=True, required=False)

        _key(title)

        def x_actors(self):
            return [casting.actor
                    for casting in self.m.movie_castings()]

        def __unicode__(self):
            return u'%s (%i)' % (self.title, self.release_date.year)


    class MovieCasting(E.Entity):

        movie = f.entity('Movie', CASCADE)
        actor = f.entity('Actor')

        _key(movie, actor)


    E.Actor._sample = [
        ('Keanu Reeves', ),
        ('Winona Ryder', ),
        ]

    E.Director._sample = [
        ('Richard Linklater', ),
        ('Stephen Herek', ),
        ('Tim Burton', ),
        ]

    E.Movie._sample = [
        ('A Scanner Darkly', '2006-07-28', ('Richard Linklater', ),
            DEFAULT),
        ("Bill & Ted's Excellent Adventure", '1989-02-17',
            ('Stephen Herek', ), DEFAULT),
        ('Edward Scissorhands', '1990-12-14', ('Tim Burton', ),
            DEFAULT),
        ]

    E.MovieCasting._sample = [
        (('A Scanner Darkly', ), ('Keanu Reeves', )),
        (('A Scanner Darkly', ), ('Winona Ryder', )),
        (("Bill & Ted's Excellent Adventure", ), ('Keanu Reeves', )),
        (('Edward Scissorhands', ), ('Winona Ryder', )),
        ]


If you have not done so already, set up a Schevo environment as
described in `Getting Started With Schevo <../../install/>`__.

This tutorial is also a doctest, so let us set up doctest
environment::

    >>> from schevo.test import DocTest

For those not familiar with doctests, please note the following:

* Because of the way doctests are designed, you should be able to
  follow this tutorial by entering Python statements *exactly* as
  shown.

* Typically, you won't do this though; doctests are designed to be
  automatically tested for correctness, but aren't necessarily what
  one would do in a real-world situation.

* When you see code that creates a new `DocTest` instance (e.g. ``t =
  DocTest(schema)``), you can ignore it.

* When you see code that is adding to the `schema` variable
  (e.g. ``schema += ''' [some text] '''``), you should just add the
  text in the multi-line string to your schema file.


Create a Schevo application
===========================

At a shell prompt, change to a directory that you want your new
application in, then use the ``paster`` tool to create a new Schevo
application::

    $ paster create --template=schevo MovieReviews  # [1]
    $ cd MovieReviews                               # [2]
    $ python setup.py develop                       # [3]

1. Create a new Schevo application called "MovieReviews". A directory
   called ``MovieReviews`` will be created with the new application
   inside.

2. Change into the ``MovieReviews`` directory.

3. Install it into your Python environment in development mode.


The default database schema
---------------------------

In your favorite text editor, open the file
``moviereviews/schema/moviereviews_001.py``. This is the filename
convention that Schevo uses when creating and evolving databases. The
``001`` represents the version number of the schema.

The prefix, while typically the same as the name of the top-level
package of your project (in this case, `moviereviews`), can be
anything you like, although only one prefix may be used in a schema
package.


Namespace preparation
---------------------

In the schema you will see the following lines near the top. These
prepare the module namespace with the necessary "ingredients" for the
Schevo schema syntax::

    >>> schema = '''
    ...     from schevo.schema import *     # [1]
    ...     schevo.schema.prep(locals())    # [2]
    ...     '''

1. The developers of Schevo tend to eschew the careless use of
   ``import *``, but this one is rather useful.  It imports only about
   13 variables, carefully named to avoid namespace clashes with the
   database schema.

2. Prepare the module's namespace for building and storing the
   structure of the database schema about to be declared.


Icon storage
------------

The default schema also has a class called ``SchevoIcon``. We're not
going to take advantage of this in this tutorial, but let's explore
this class a little bit as it will give us a template for the other
classes you'll add to your schema::

    >>> schema += '''
    ...     class SchevoIcon(E.Entity):     # [1]
    ...         """Stores icons for Schevo database and application objects."""
    ...
    ...         _hidden = True              # [2]
    ...
    ...         name = f.string()           # [3]
    ...         data = f.image()            # [4]
    ...
    ...         _key(name)                  # [5]
    ...     '''


1. ``SchevoIcon`` is an *entity class*. All entity classes subclass from
   ``E.Entity``. See `Schevo Namespaces`_ to find out more about the ``E``
   namespace.

.. _Schevo Namespaces: ../../reference/namespaces.html

2. When the database is open, the ``SchevoIcon`` *extent* that is
   based on this class will be designated as hidden. A user interface
   *may* honor this and hide this extent from the user in some way.

3. ``name`` is a string *field*. See `Schevo Namespaces`_ to read
   more about the ``f`` namespace.

4. ``data`` is an Image field.

5. ``(name)`` is a key for the ``SchevoIcon`` extent. Schevo will not
   allow more than one *entity* with the same value in the ``name``
   field in this extent.


Some terminology
================

We've introduced some terms that are not necessarily unique to Schevo,
but may not be familiar for those accustomed to working with SQL. Here
are some basic definitions of those terms. We've left out some details
to keep this tutorial simple.

For more detail, visit the `Schevo Glossary <../../reference/glossary.html>`__.

Entity class
    A class definition in a schema that describes the fields,
    transactions, and other characteristics that each entity of that
    type will have.

Extent
    A collection object based on an entity class containing same-typed
    entities in a database. Analogous to a table in a SQL database.

Entity
    An object that represents a single item in the database. Analogous
    to a row in a SQL database.

Field
    An object that represents a property of an entity. Analogous to a
    column in a SQL database.


Add an entity class for movies
==============================

Add the following class to the database schema::

    >>> schema += '''
    ...     class Movie(E.Entity):
    ...
    ...         title = f.string()
    ...         release_date = f.date()
    ...         description = f.string(required=False,
    ...                                multiline=True)  # [1], [2]
    ...
    ...         _key(title)                             # [3]
    ...     '''


1. By default, `String` fields will store multi-line values, but user
   interfaces typically render them as single-line widgets. We can
   hint to the user interface that it may use a multi-line input
   widget by setting ``multiline=True``.

2. By default, all fields are required. ``description`` isn't.

3. ``(title)`` is a key for this extent. There can be no two movies
   with the same title. This is a bit unrealistic, and indeed Schevo
   allows you to specify keys such as ``(title, release_date)``, but
   we've simplified things for this tutorial.


Create a database
=================

Save the ``moviereviews_001.py`` file. At a shell prompt, use the
``schevo`` tool to create a new database with the application's
schema::

    $ schevo db create --app=moviereviews sample.db

That's it!

At this point, the ``sample.db`` file contains your new database,
including the database schema itself.

For the doctest, let us create an in-memory database with the current
schema::

    >>> t = DocTest(schema)
    >>> db = t.db


Add some movies
===============

The ``schevo`` tool gives you a handy way to work with a database
using a Python interpreter. We recommend installing IPython (just run
``"easy_install IPython"``) for a very comfortable experience.

At a shell prompt, open the database::

    $ schevo shell sample.db

A variable ``db``, representing the open database, is automatically
inserted into the namespace of the interactive session.


Transactions
------------

All changes to a Schevo database are done via *transaction* objects that are
responsible for ensuring that changes made to the database follow the rules
you specify. See `Schevo Transactions <../../reference/transactions.html>`__
for further discussion about this design decision and the benefits you gain
from it.

Make a new ``create`` transaction for creating a new ``Movie``
entity. Assign values to fields, then tell the database to execute the
transaction::

    >>> tx = db.Movie.t.create()           # [1], [2], [3]
    >>> tx.title = 'A Scanner Darkley'     # [4]
    >>> tx.release_date = '2006-07-28'     # [5]
    >>> movie = db.execute(tx)             # [6]

1. ``db.Movie`` is the ``Movie`` extent in the database.

2. Extents have a ``t`` namespace that contains *transaction methods*.

3. By calling the ``create`` transaction method, we receive a
   transaction object that will attempt to create a new Movie entity
   upon execution.

4. Set the transaction's fields to the values that you want the new
   entity to have.

5. ``date`` fields are smart and will accept ``datetime.date`` objects
   as well as ``YYYY-MM-DD`` and ``MM/DD/YYYY`` formatted strings.

6. When the transaction object executes successfully, it returns the
   ``Movie`` entity that it just created.

Inspecting the field values of the entity shows that the proper
information was stored in the database::

    >>> movie.title
    u'A Scanner Darkley'
    >>> movie.release_date
    datetime.date(2006, 7, 28)

Oops! There's a typo in the title of the movie.

The ``movie`` entity object has a ``t`` namespace as well. By default,
there are ``update`` and ``delete`` methods in an entity's ``t``
namespace.

Create an ``update`` transaction object, correct the name, then
execute the transaction and inspect the result::

    >>> tx = movie.t.update()
    >>> tx.title = 'A Scanner Darkly'
    >>> movie = db.execute(tx)
    >>> movie.title
    u'A Scanner Darkly'


Object representations
======================

At this point, you may notice that if you print the ``movie`` object
itself, it gives you a label based off the first *key* you define for
the extent::

    >>> print movie
    A Scanner Darkly

Suppose in a user interface we would like to include the year the
movie was released whenever a summary of a movie entity is to be
displayed.

Exit the Python session, then edit ``moviereviews_001.py`` again and
add the ``__unicode__`` method to the ``Movie`` class::

    >>> schema += '''
    ...     class Movie(E.Entity):
    ...
    ...         title = f.string()
    ...         release_date = f.date()
    ...         description = f.string(multiline=True, required=False)
    ...
    ...         _key(title)
    ...
    ...         def __unicode__(self):
    ...             return u'%s (%i)' % (self.title, self.release_date.year)
    ...     '''

At a shell prompt, update the existing database using the new schema,
then open a Python session again::

    $ schevo db update --app=moviereviews sample.db
    $ schevo shell sample.db

For the doctest, let us perform the equivalent operation:

    >>> t.update(schema)
    >>> db = t.db

Find the movie, and inspect it::

    >>> movie = db.Movie.findone(title='A Scanner Darkly')   # [1]
    >>> movie                       # [2]
    <Movie entity oid:1 rev:1>
    >>> print movie                 # [3]
    A Scanner Darkly (2006)

1. The ``findone`` method of an extent finds exactly one entity in
   that extent whose fields match those passed to the method. If it
   can't find any, it returns ``None``. If it finds more than one, it
   raises a ``FindOneFoundMany`` exception.

2. The ``repr`` representation of the entity gives you the extent
   name, the entity object identifier, and the entity revision.

3. The ``unicode`` representation now gives you a more human-friendly
   view of things, suitable not only for interactive use but also for
   user interface code.

Schevo likes to make things easy for humans to read, so you can go beyond
thinking of it as a "unicode representation" of something, and think of it
instead as a singular or plural *label* of an object. See `Object Labels
<../../reference/object-labels.html>`__ for further discussion about this
feature.


Relationships
=============

Schevo has many relational-like capabilities, and it makes it easy to
manage those.

We'll flex this by having our database keep track of the director of
each movie, and the actors that are in each movie. Close your Python
session, and modify the schema to add the ``Actor``, ``Director``, and
``MovieCasting`` classes, and to modify the ``Movie`` class.

Wait! Before we get started, think about how much data we'll have to
populate with those transaction objects. Schevo allows you to change
the structure of a database within the same schema version to some
degree to support rapid development. Wouldn't it also be nice to place
some sample data directly in the schema?

Schevo lets you do that, so take advantage of it when you modify the
schema by assigning ``_sample`` data lists to each entity class::

    >>> schema += '''
    ...     class Actor(E.Entity):
    ...
    ...         name = f.string()
    ...
    ...         _key(name)
    ...
    ...
    ...     class Director(E.Entity):
    ...
    ...         name = f.string()
    ...
    ...         _key(name)
    ...
    ...
    ...     class Movie(E.Entity):
    ...
    ...         title = f.string()
    ...         release_date = f.date()
    ...         director = f.entity('Director')
    ...         description = f.string(multiline=True, required=False)
    ...
    ...         _key(title)
    ...
    ...         def __unicode__(self):
    ...             return u'%s (%i)' % (self.title, self.release_date.year)
    ...
    ...
    ...     class MovieCasting(E.Entity):
    ...
    ...         movie = f.entity('Movie', CASCADE)
    ...         actor = f.entity('Actor')
    ...
    ...         _key(movie, actor)
    ...
    ...         def __unicode__(self):
    ...             return u'%s :: %s' (self.movie, self.actor)
    ...
    ...
    ...     E.Actor._sample = [
    ...         ('Keanu Reeves', ),
    ...         ('Winona Ryder', ),
    ...         ]
    ...
    ...     E.Director._sample = [
    ...         ('Richard Linklater', ),
    ...         ('Stephen Herek', ),
    ...         ('Tim Burton', ),
    ...         ]
    ...
    ...     E.Movie._sample = [
    ...         ('A Scanner Darkly', '2006-07-28', ('Richard Linklater', ),
    ...             DEFAULT),
    ...         ("Bill & Ted's Excellent Adventure", '1989-02-17',
    ...             ('Stephen Herek', ), DEFAULT),
    ...         ('Edward Scissorhands', '1990-12-14', ('Tim Burton', ),
    ...             DEFAULT),
    ...         ]
    ...
    ...     E.MovieCasting._sample = [
    ...         (('A Scanner Darkly', ), ('Keanu Reeves', )),
    ...         (('A Scanner Darkly', ), ('Winona Ryder', )),
    ...         (("Bill & Ted's Excellent Adventure", ), ('Keanu Reeves', )),
    ...         (('Edward Scissorhands', ), ('Winona Ryder', )),
    ...         ]
    ...     '''


Following relationships
-----------------------

Create a new database with sample data, deleting the old one first,
then open the database in a Python session::

    $ schevo db create --app=moviereviews --delete --sample sample.db
    $ schevo shell sample.db

For the doctest, let us do the equivalent::

    >>> t.done()
    >>> t = DocTest(schema)
    >>> db = t.db
    >>> db.populate()

Let us look at how we might use the relationships that Schevo has kept
for us.  Perhaps in the full implementation of a video store, some
code would like to find all of the movies that Winona Ryder starred
in. This information could be helpful for helping customers and for
promotion of new movies.

Here's how you would find those movies::

    >>> actor = db.Actor.findone(name='Winona Ryder')
    >>> castings = actor.m.movie_castings()
    >>> movies = [casting.movie for casting in castings]
    >>> for movie in movies:
    ...     print movie
    A Scanner Darkly (2006)
    Edward Scissorhands (1990)

If we have a specific movie, we can find the actors in it::

    >>> movie = db.Movie.findone(title='A Scanner Darkly')
    >>> castings = movie.m.movie_castings()
    >>> actors = [casting.actor for casting in castings]
    >>> for actor in actors:
    ...     print actor
    Keanu Reeves
    Winona Ryder

If we have a director, we can find the movies that he or she has
directed::

    >>> director = db.Director.findone(name='Richard Linklater')
    >>> movies = director.m.movies()
    >>> for movie in movies:
    ...     print movie
    ...
    A Scanner Darkly (2006)


Adding your own API extensions
==============================

Schevo lets you add API extensions within the ``x`` namespace. A
separate namespace is used to prevent clashes between extension
methods and field names.

When following the relationships above, there was some boilerplate
when finding out an actor's movies, or a movie's actors, due to the
``MovieCasting`` extent that ties them together in a many-to-many
relationship.

The developers of Schevo have found that such intermediary extents are
more useful than trying to embody that type of relationship some other
way. We have run into more than one instance where it became useful to
add additional information *about the relationship itself* via extra
fields in the intermediary extent.

You can use extension methods to reduce this sort of boilerplate,
while still retaining the full flexibility of Schevo's relationship
model. Add a new method called ``x_movies`` to the bottom of the
``Actor`` class::

    >>> schema += '''
    ...     class Actor(E.Entity):
    ...
    ...         name = f.string()
    ...
    ...         _key(name)
    ...
    ...         def x_movies(self):
    ...             return [casting.movie
    ...                     for casting in self.m.movie_castings()]
    ...     '''

Add a new method to the ``Movie`` class::

    >>> schema += '''
    ...     class Movie(E.Entity):
    ...
    ...         title = f.string()
    ...         release_date = f.date()
    ...         director = f.entity('Director')
    ...         description = f.string(multiline=True, required=False)
    ...
    ...         _key(title)
    ...
    ...         def x_actors(self):
    ...             return [casting.actor
    ...                     for casting in self.m.movie_castings()]
    ...
    ...         def __unicode__(self):
    ...             return u'%s (%i)' % (self.title, self.release_date.year)
    ...     '''


Close the Python session, update the database so its schema has the
new methods available, then open a Python session::

    $ schevo db update --app=moviereviews sample.db
    $ schevo shell sample.db

For the doctest, do the equivalent::

    >>> t.update(schema)
    >>> db = t.db

The relationships demonstrated above are now much easier to traverse::

    >>> actor = db.Actor.findone(name='Winona Ryder')
    >>> for movie in actor.x.movies():
    ...     print movie
    ...
    A Scanner Darkly (2006)
    Edward Scissorhands (1990)

    >>> movie = db.Movie.findone(title='A Scanner Darkly')
    >>> for actor in movie.x.actors():
    ...     print actor
    ...
    Keanu Reeves
    Winona Ryder

Since there is a one-to-many relationship between directors and
movies, the use of ``director.m.movies()`` stays the same since there
is no intermediate extent used.


Summary
=======

This tutorial has covered the following:

* Creating a skeleton for a new Schevo app

* Adding entity classes to the schema

* Creating and updating Schevo databases

* Use of transaction methods and objects to create and update entities

* Defining human-friendly summaries of entities

* Adding sample data to a database schema

* Defining and following relationships that Schevo maintains for you

* Extending the API of a database using extension methods
