#summary A quick guide to getting started using Brain Freeze

Getting Started
===============
    

Introduction
------------

Brain Freeze is an ``SQLAlchemy`` plugin that supports object composition (as opposed
to class inheritance).  Google for "composition versus inheritance" to get the general idea.

Another way to think of Brain Freeze is that it's a plugin for proxying properties on         
one-to-one related objects. Brain Freeze uses SQLAlchemy's `Association Proxy`_ for the       
python properties, only Brain Freeze goes the extra step of making those properties           
query-able using the regular SQLAlchemy session.query() machinery.                            

Brain Freeze provides a *One-To-One Proxy* that will take all the foreign columns in an
``SQLAlchemy`` one-to-one relation and make them accessible on the mapped class. For example
if your model has Books and Publishers, and each Book has exactly one Publisher, the
*One-To-One Proxy* will proxy ``Book.Publisher.publisher_name`` so that it can be accessed
using the more convenient ``Book.publisher_name``. No joke! And if that particular Book
doesn't have a Publisher record yet, accessing ``Book.publisher_name`` will automatically
create the Publisher record for you.

But wait! There's More! Also included is a *One-To-One Mapper Property* that
lets these proxied columns be query-able and persist-able just as if they were 
normal columns on the mapped class.  What the hell does that mean?  Glad you asked!  
It means that instead of always having to join on related classes::

    q = session.query(Book).join(Publisher)
    q.filter(Publisher.publisher_city == "Cleveland").all()
    
you can do the much-more-elegant basic query::

   session.query(Book).filter(Book.publisher_city == "Cleveland").all()

All the fanciest query filter operations such as
``Book.publisher_name.contains('Independent')`` are totally supported!

And when you change the value, for example ``somebook.publisher_city = 'NYC'``, that 
change is persisted in the database the next time you flush the session.

Did I say this just worked for columns?  Brain Freeze also proxies relations on a 
related object!  So if you have Books, Publishers, and Contacts, where each 
Publisher has a primary_contact relation, you can access and query the 
primary_contact using ``Book.primary_contact``.  Again, changes to this proxied
property are persisted after the next session flush.

Act now and you'll receive two special gifts, a *One-To-One Property
Loader* and a *One-To-One Mapper Extension* that sets this all up for you
automatically!  Call Now!

.. _Association Proxy: http://www.sqlalchemy.org/docs/05/plugins.html#plugins_associationproxy


Installation
------------

Brain Freeze requires python 2.4 or later, and was tested with SQLAlchemy 0.5
although it should work with SQLAlchemy 0.4 as well.  There are no other
dependencies.

To install Brain Freeze, run::

    $ easy_install brainfreeze
    
Or download the source distribution and run::

    $ python ./setup.py install


Usage
-----

Ok so we're about to get freaky with some python and some SQLAlchemy.  Don't
worry though because when you look back, you're only gonna see one set of
footprints on the beach.  And you know why?  Because I was out getting us a
pizza!  So grab a slice, and don't worry about anything, and let's get going.

One-To-One Proxy
~~~~~~~~~~~~~~~~

Let's get right to an example.  Let's say we're dealing with Books and
Publishers and Contacts.  In our example, a Book has exactly one Publisher, 
and a Publisher has one Contact that's the primary contact.

First we create our tables using SQLAlchemy::

    >>> from sqlalchemy import *
    >>> from sqlalchemy.orm import *

    >>> engine = create_engine('sqlite:///:memory:')
    >>> metadata = MetaData(bind=engine)
    >>> Session = sessionmaker(bind=engine)
    >>> session = Session()

    >>> contact_table = Table('contact', metadata,
    ...     Column('contact_id', Integer, primary_key=True),
    ...     Column('contact_name', String(255)),
    ...     Column('contact_phone', String(255))
    ... )
    >>> publisher_table = Table('publisher', metadata,
    ...     Column('publisher_id', Integer, primary_key=True),
    ...     Column('publisher_name', String(255)),
    ...     Column('publisher_city', String(255)),
    ...     Column('primary_contact_id', None, ForeignKey('contact.contact_id'))
    ... )
    >>> book_table = Table('book', metadata,
    ...     Column('book_id', Integer, primary_key=True),
    ...     Column('title', String(255)),
    ...     Column('publisher_id', None, ForeignKey('publisher.publisher_id'))
    ... )
    >>> metadata.create_all()

Next we'll map those tables to some classes and utilize our Proxy Property.
To help us out, let's make a simple base class to make it easy to create our
instances::

    >>> class Base(object):
    ...     def __init__(self, **kwargs):
    ...         for key, value in kwargs.iteritems():
    ...             setattr(self, key, value)

Ok now we map our tables to some classes and use the Brain Freeze Proxy
Property::

    >>> from brainfreeze import OneToOneProxy

    >>> class Contact(Base):
    ...     pass
    
    >>> class Publisher(Base):
    ...     pass

    >>> class Book(Base):
    ...     publisher_name = OneToOneProxy('publisher', Publisher, 'publisher_name')
    ...     publisher_city = OneToOneProxy('publisher', Publisher, 'publisher_city')
    ...     primary_contact = OneToOneProxy('publisher', Publisher, 'primary_contact')

    >>> contact_mapper = mapper(Contact, contact_table)
    >>> publisher_mapper = mapper(Publisher, publisher_table,
    ...    properties={
    ...        'primary_contact': relation(Contact)
    ...    }
    ... )
    >>> book_mapper = mapper(Book, book_table,
    ...     properties={
    ...         'publisher': relation(Publisher, backref='books')
    ...     }
    ... )

And now we can create some books and publishers and contacts and see how the Proxy works::

    >>> b1 = Book(title="The Jungle", publisher_name="Doubleday")
    >>> b2 = Book(title="Days of War, Nights of Love")

    >>> session.add_all([b1, b2])
    >>> session.flush()

    >>> b1.publisher_name
    'Doubleday'

    >>> b2.publisher_name is None
    True

    >>> b2.publisher_name = "Crimethinc"
    >>> session.flush()
    >>> b2.publisher_name
    'Crimethinc'

    >>> b1.primary_contact is None
    True
    >>> b1.primary_contact = Contact(contact_name='Mr. Piehard', contact_phone='555 1212')
    >>> b1.primary_contact in session
    True
    >>> session.flush()

However, the Proxy alone does not provide the ability to query on these
properties::

    >>> session.query(Book).filter(Book.publisher_name == 'Doubleday')
    Traceback (most recent call last):
        ...
    ArgumentError: filter() argument must be of type sqlalchemy.sql.ClauseElement or string

In order to get this query to work, you're going to need.... the Brain Freeze
*One-To-One Mapper Property*!


One-To-One Mapper Property
~~~~~~~~~~~~~~~~~~~~~~~~~~

In order to query on proxied attributes, something must be done at the mapper
level to add query-able properties that are understood by the mapper.  Don't
worry, we've got this under control.

First we find the primary mapper for our ``Book`` class (this is the same
thing that's returned by the ``mapper()`` method)::

    >>> m = class_mapper(Book) 

Now we just add our various ``MapperProperty`` properties::

    >>> from brainfreeze import OneToOneMapperProperty

    >>> m.add_property('publisher_name', OneToOneMapperProperty(Book.publisher_name))
    >>> m.add_property('primary_contact', OneToOneMapperProperty(Book.primary_contact))

And then we can query like a champ::

    >>> books = session.query(Book).filter(Book.publisher_name == 'Doubleday').all()
    >>> len(books)
    1

And we can update these properties and see it persisted::

    >>> new_contact = Contact(contact_name='Miss Serena', contact_phone='911 1212')
    >>> b1.primary_contact = new_contact
    >>> session.flush()
    
    >>> b1 in session.query(Book).filter(Book.primary_contact==new_contact).all()
    True

So this sounds like a lot of work, right?  It's a pain having to type all this
code for each and every property.  Well, say no more!  You can use the Brain
Freeze *One-To-One Property Loader* to create all these Proxy Properties and
Mapper Properties automatically!


One-To-One Property Loader
~~~~~~~~~~~~~~~~~~~~~~~~~~

Want something a little easier than typing up all those Proxy Properties
and Mapper Properties?  I mean, it's not like us programmers get paid by
the line, right?  So How about a special kind of ``relation`` that sets
up all these things automatically?  No Problem::

    >>> clear_mappers()

    >>> from brainfreeze import one_to_one

    >>> publisher_mapper = mapper(Publisher, publisher_table)
    >>> book_mapper = mapper(Book, book_table,
    ...     properties={
    ...         'publisher': one_to_one(Publisher)
    ...     }
    ... )

    >>> books = session.query(Book).filter(Book.publisher_name == 'Doubleday').all()
    >>> len(books)
    1

    >> books[0].publisher_name
    'Doubleday'


One-To-One Mapper Extension
~~~~~~~~~~~~~~~~~~~~~~~~~~~

But what if you have a whole bunch of One-To-One relations?  Wouldn't it be
easier if there was a ``MapperExtension`` that added those automatically?  You
bet!  And just look how easy this is::

    >>> clear_mappers()

    >>> from brainfreeze import OneToOneMapperExtension

    >>> publisher_mapper = mapper(Publisher, publisher_table)
    >>> book_mapper = mapper(Book, book_table, 
    ...     extension=[OneToOneMapperExtension(Publisher)]
    ... )

    >>> books = session.query(Book).filter(Book.publisher_name == 'Doubleday').all()
    >>> len(books)
    1

    >> books[0].publisher_name
    'Doubleday'

Another cool thing about this is that the ``OneToOneMapperExtension``
constructor takes any number of classes as arguments, so for example if each
Book also had exactly one Author, you would just use::

    OneToOneMapperExtension([Publisher, Author])


The End
-------

That's all, folks!