280 lines
10 KiB
ReStructuredText
280 lines
10 KiB
ReStructuredText
Associations
|
||
============
|
||
|
||
Paris provides a simple API for one-to-one, one-to-many and many-to-many
|
||
relationships (associations) between models. It takes a different
|
||
approach to many other ORMs, which use associative arrays to add
|
||
configuration metadata about relationships to model classes. These
|
||
arrays can often be deeply nested and complex, and are therefore quite
|
||
error-prone.
|
||
|
||
Instead, Paris treats the act of querying across a relationship as a
|
||
*behaviour*, and supplies a family of helper methods to help generate
|
||
such queries. These helper methods should be called from within
|
||
*methods* on your model classes which are named to describe the
|
||
relationship. These methods return ORM instances (rather than actual
|
||
Model instances) and so, if necessary, the relationship query can be
|
||
modified and added to before it is run.
|
||
|
||
Summary
|
||
^^^^^^^
|
||
|
||
The following list summarises the associations provided by Paris, and
|
||
explains which helper method supports each type of association:
|
||
|
||
One-to-one
|
||
''''''''''
|
||
|
||
Use ``has_one`` in the base, and ``belongs_to`` in the associated model.
|
||
|
||
One-to-many
|
||
'''''''''''
|
||
|
||
Use ``has_many`` in the base, and ``belongs_to`` in the associated
|
||
model.
|
||
|
||
Many-to-many
|
||
''''''''''''
|
||
|
||
Use ``has_many_through`` in both the base and associated models.
|
||
|
||
Below, each association helper method is discussed in detail.
|
||
|
||
Has-one
|
||
^^^^^^^
|
||
|
||
One-to-one relationships are implemented using the ``has_one`` method.
|
||
For example, say we have a ``User`` model. Each user has a single
|
||
``Profile``, and so the ``user`` table should be associated with the
|
||
``profile`` table. To be able to find the profile for a particular user,
|
||
we should add a method called ``profile`` to the ``User`` class (note
|
||
that the method name here is arbitrary, but should describe the
|
||
relationship). This method calls the protected ``has_one`` method
|
||
provided by Paris, passing in the class name of the related object. The
|
||
``profile`` method should return an ORM instance ready for (optional)
|
||
further filtering.
|
||
|
||
.. code-block:: php
|
||
|
||
<?php
|
||
class Profile extends Model {
|
||
}
|
||
|
||
class User extends Model {
|
||
public function profile() {
|
||
return $this->has_one('Profile');
|
||
}
|
||
}
|
||
|
||
The API for this method works as follows:
|
||
|
||
.. code-block:: php
|
||
|
||
<?php
|
||
// Select a particular user from the database
|
||
$user = Model::factory('User')->find_one($user_id);
|
||
|
||
// Find the profile associated with the user
|
||
$profile = $user->profile()->find_one();
|
||
|
||
By default, Paris assumes that the foreign key column on the related
|
||
table has the same name as the current (base) table, with ``_id``
|
||
appended. In the example above, Paris will look for a foreign key column
|
||
called ``user_id`` on the table used by the ``Profile`` class. To
|
||
override this behaviour, add a second argument to your ``has_one`` call,
|
||
passing the name of the column to use.
|
||
|
||
In addition, Paris assumes that the foreign key column in the current (base)
|
||
table is the primary key column of the base table. In the example above,
|
||
Paris will use the column called ``user_id`` (assuming ``user_id`` is the
|
||
primary key for the user table) in the base table (in this case the user table)
|
||
as the foreign key column in the base table. To override this behaviour,
|
||
add a third argument to your ``has_one call``, passing the name of the column
|
||
you intend to use as the foreign key column in the base table.
|
||
|
||
Has many
|
||
^^^^^^^^
|
||
|
||
One-to-many relationships are implemented using the ``has_many`` method.
|
||
For example, say we have a ``User`` model. Each user has several
|
||
``Post`` objects. The ``user`` table should be associated with the
|
||
``post`` table. To be able to find the posts for a particular user, we
|
||
should add a method called ``posts`` to the ``User`` class (note that
|
||
the method name here is arbitrary, but should describe the
|
||
relationship). This method calls the protected ``has_many`` method
|
||
provided by Paris, passing in the class name of the related objects.
|
||
**Pass the model class name literally, not a pluralised version**. The
|
||
``posts`` method should return an ORM instance ready for (optional)
|
||
further filtering.
|
||
|
||
.. code-block:: php
|
||
|
||
<?php
|
||
class Post extends Model {
|
||
}
|
||
|
||
class User extends Model {
|
||
public function posts() {
|
||
return $this->has_many('Post'); // Note we use the model name literally - not a pluralised version
|
||
}
|
||
}
|
||
|
||
The API for this method works as follows:
|
||
|
||
.. code-block:: php
|
||
|
||
<?php
|
||
// Select a particular user from the database
|
||
$user = Model::factory('User')->find_one($user_id);
|
||
|
||
// Find the posts associated with the user
|
||
$posts = $user->posts()->find_many();
|
||
|
||
By default, Paris assumes that the foreign key column on the related
|
||
table has the same name as the current (base) table, with ``_id``
|
||
appended. In the example above, Paris will look for a foreign key column
|
||
called ``user_id`` on the table used by the ``Post`` class. To override
|
||
this behaviour, add a second argument to your ``has_many`` call, passing
|
||
the name of the column to use.
|
||
|
||
In addition, Paris assumes that the foreign key column in the current (base)
|
||
table is the primary key column of the base table. In the example above, Paris
|
||
will use the column called ``user_id`` (assuming ``user_id`` is the primary key
|
||
for the user table) in the base table (in this case the user table) as the
|
||
foreign key column in the base table. To override this behaviour, add a third
|
||
argument to your ``has_many call``, passing the name of the column you intend
|
||
to use as the foreign key column in the base table.
|
||
|
||
Belongs to
|
||
^^^^^^^^^^
|
||
|
||
The ‘other side’ of ``has_one`` and ``has_many`` is ``belongs_to``. This
|
||
method call takes identical parameters as these methods, but assumes the
|
||
foreign key is on the *current* (base) table, not the related table.
|
||
|
||
.. code-block:: php
|
||
|
||
<?php
|
||
class Profile extends Model {
|
||
public function user() {
|
||
return $this->belongs_to('User');
|
||
}
|
||
}
|
||
|
||
class User extends Model {
|
||
}
|
||
|
||
The API for this method works as follows:
|
||
|
||
.. code-block:: php
|
||
|
||
<?php
|
||
// Select a particular profile from the database
|
||
$profile = Model::factory('Profile')->find_one($profile_id);
|
||
|
||
// Find the user associated with the profile
|
||
$user = $profile->user()->find_one();
|
||
|
||
Again, Paris makes an assumption that the foreign key on the current
|
||
(base) table has the same name as the related table with ``_id``
|
||
appended. In the example above, Paris will look for a column named
|
||
``user_id``. To override this behaviour, pass a second argument to the
|
||
``belongs_to`` method, specifying the name of the column on the current
|
||
(base) table to use.
|
||
|
||
Paris also makes an assumption that the foreign key in the associated (related)
|
||
table is the primary key column of the related table. In the example above,
|
||
Paris will look for a column named ``user_id`` in the user table (the related
|
||
table in this example). To override this behaviour, pass a third argument to
|
||
the belongs_to method, specifying the name of the column in the related table
|
||
to use as the foreign key column in the related table.
|
||
|
||
Has many through
|
||
^^^^^^^^^^^^^^^^
|
||
|
||
Many-to-many relationships are implemented using the
|
||
``has_many_through`` method. This method has only one required argument:
|
||
the name of the related model. Supplying further arguments allows us to
|
||
override default behaviour of the method.
|
||
|
||
For example, say we have a ``Book`` model. Each ``Book`` may have
|
||
several ``Author`` objects, and each ``Author`` may have written several
|
||
``Books``. To be able to find the authors for a particular book, we
|
||
should first create an intermediary model. The name for this model
|
||
should be constructed by concatenating the names of the two related
|
||
classes, in alphabetical order. In this case, our classes are called
|
||
``Author`` and ``Book``, so the intermediate model should be called
|
||
``AuthorBook``.
|
||
|
||
We should then add a method called ``authors`` to the ``Book`` class
|
||
(note that the method name here is arbitrary, but should describe the
|
||
relationship). This method calls the protected ``has_many_through``
|
||
method provided by Paris, passing in the class name of the related
|
||
objects. **Pass the model class name literally, not a pluralised
|
||
version**. The ``authors`` method should return an ORM instance ready
|
||
for (optional) further filtering.
|
||
|
||
.. code-block:: php
|
||
|
||
<?php
|
||
class Author extends Model {
|
||
public function books() {
|
||
return $this->has_many_through('Book');
|
||
}
|
||
}
|
||
|
||
class Book extends Model {
|
||
public function authors() {
|
||
return $this->has_many_through('Author');
|
||
}
|
||
}
|
||
|
||
class AuthorBook extends Model {
|
||
}
|
||
|
||
The API for this method works as follows:
|
||
|
||
.. code-block:: php
|
||
|
||
<?php
|
||
// Select a particular book from the database
|
||
$book = Model::factory('Book')->find_one($book_id);
|
||
|
||
// Find the authors associated with the book
|
||
$authors = $book->authors()->find_many();
|
||
|
||
// Get the first author
|
||
$first_author = $authors[0];
|
||
|
||
// Find all the books written by this author
|
||
$first_author_books = $first_author->books()->find_many();
|
||
|
||
Overriding defaults
|
||
'''''''''''''''''''
|
||
|
||
The ``has_many_through`` method takes up to six arguments, which allow
|
||
us to progressively override default assumptions made by the method.
|
||
|
||
**First argument: associated model name** - this is mandatory and should
|
||
be the name of the model we wish to select across the association.
|
||
|
||
**Second argument: intermediate model name** - this is optional and
|
||
defaults to the names of the two associated models, sorted
|
||
alphabetically and concatenated.
|
||
|
||
**Third argument: custom key to base table on intermediate table** -
|
||
this is optional, and defaults to the name of the base table with
|
||
``_id`` appended.
|
||
|
||
**Fourth argument: custom key to associated table on intermediate
|
||
table** - this is optional, and defaults to the name of the associated
|
||
table with ``_id`` appended.
|
||
|
||
**Fifth argument: foreign key column in the base table** -
|
||
this is optional, and defaults to the name of the primary key column in
|
||
the base table.
|
||
|
||
**Sixth argument: foreign key column in the associated table** -
|
||
this is optional, and defaults to the name of the primary key column
|
||
in the associated table.
|