Tutorial
========

Welcome to PyFactory!

This is a whirlwind tour of the features of PyFactory and a quick
guide on how to use each of them.

What is PyFactory?
------------------

**PyFactory** is a library for writing and using model factories. Model
factories allow you to replace test fixtures or manual model creation in
tests with succint, easy to use factories.

**PyFactory** is also model-agnostic, meaning it can generate any sort of
models for any ORM or backing store.

The specific feature list of **PyFactory**:

* Model-agnostic. Generate models for any ORM or backing store.
* Generate attributes. Instead of getting a full-blown model, you can
  request just the attributes of the model as a dictionary.
* Build model objects (and optionally ask for them saved
  to the backing store as well).
* Model complex associations in your schemas to generate all dependencies
  along with the model.

Your First Factory
------------------

Let's create your first factory. Before doing that, however, we need to
define what our models are. In your case, this may be a model for Django,
SQLAlchemy, or something custom. For the purposes of our examples here,
we're going to simply set attributes on an object as our model.

The Model Builder
~~~~~~~~~~~~~~~~~

First, we need to create a *model builder*. This is the class which knows
how to build our models given a dictionary of attributse. This is the core
piece of **PyFactory** which enables model-agnosticism. Model builders
are extremely easy to write, especially in our case::

    class MyModelBuilder(object):
        @classmethod
        def build(cls, model_cls, attrs):
            result = model_cls()
            for key,val in attrs.iteritems():
                setattr(result, key, val)

            return result

        @classmethod
        def create(cls, model_cls, attrs):
            result = self.build(model_cls, attrs)
            result.saved = True
            return result

As you can see, given a model class and attributes, the model builder knows
how to build them. In our case, this is simply using ``setattr`` on an object.

In-depth documentation is available on :doc:`model builders <usage/model_builders>`.

The Factory
~~~~~~~~~~~

Now that we have a model builder, let's go forward and build a simple factory.
Let's pretend we have a ``User`` model we want to create a factory for::

    from pyfactory import Factory, schema

    class UserFactory(Factory):
        _model = object
        _model_builder = MyModelBuilder

        @schema()
        def basic(self):
            return {
                "first_name": "John",
                "last_name": "Doe"
            }

Given the above factory, we can now generate users!

::

    user = UserFactory().create("basic")
    print user.first_name, user.last_name # "John Doe"

Using Your Factory
~~~~~~~~~~~~~~~~~~

In the example above we used the ``create`` method to use our factory.
There are other methods available as well:

* ``attributes`` - This method will return the attributes of the model
  it would create as a ``dict``.
* ``build`` - This method will build the model but will not save it
  to the backing store. This is useful if you want a model to manipulate
  more before saving it.
* ``create`` - This method will build the model and save it to the
  backing store.

Using these methods is straightforward::

    factory = UserFactory()
    factory.attributes("basic")
    factory.build("basic")
    factory.create("basic")

Associations
------------

Most models, especially those in relational databases, have associations
with other models. In the past, if you had a model which had many associations,
you would have to manually create all these associations and maintain the
brittle relationships. With **PyFactory**, these associations are managed
for you.

Let's create another factory, for a hypothetical ``Post`` which has an author,
which is a ``User`` that we created a factory for moments before.

::

    from pyfactory import Factory, association, schema

    class PostFactory(Factory):
        _model = object
        _model_builder = MyModelBuilder

        @schema()
        def basic(self):
            return {
                "title": "My Post",
                "author": association(UserFactory(), "basic")
            }

The new piece of the above factory, of course, is the ``association``
function call. This means that the ``author`` field is an association
to a ``UserFactory()`` factory and the ``basic`` schema of that factory,
which is the schema we created earlier.

Given the above example, we can now create a ``Post`` which already has
a ``User`` dependency created for us!

::

    post = PostFactory().create("basic")
    print post.title # => "My Post"
    print post.author.first_name, post.author.last_name # => "John Doe"

Sequences
---------

Sometimes, in order to help generate information that looks different
from other information, it is useful to incorporate sequences of data.
For example, instead of having ever user's first name to "User" it
would be nice if it could be "User #1," "User #2," etc. Sequences
make this easy.

::

    from pyfactory import Factory, schema, sequence

    class UserFactory(Factory):
        _model = object
        _model_builder = MyModelBuilder

        @schema()
        def basic(self):
            return {
                "first_name": sequence("User %(n)d")
            }

Given the above example, we can now create ``User`` objects which
have sequential first names::

    user1 = UserFactory().create("basic")
    user2 = UserFactory().create("basic")

    print user1.first_name # => "User 1"
    print user2.first_name # => "User 2"
