<&|doclib.myt:item, name="adv_datamapping", description="Advanced Data Mapping" &>
<p>This section is under construction. For now, it has just the basic recipe for each concept without much else. </p>
-<p>To start, heres the tables w e will work with again:</p>
+<p>To start, heres the tables we will work with again:</p>
<&|formatting.myt:code&>
from sqlalchemy import *
db = create_engine('sqlite://filename=mydb', echo=True)
</&>
-<&|doclib.myt:item, name="creatingrelations", description="Creating Mapper Relations" &>
+<&|doclib.myt:item, name="relations", description="More On Relations" &>
<&|doclib.myt:item, name="customjoin", description="Custom Join Conditions" &>
<p>When creating relations on a mapper, most examples so far have illustrated the mapper and relationship joining up based on the foreign keys of the tables they represent. in fact, this "automatic" inspection can be completely circumvented using the <span class="codeline">primaryjoin</span> and <span class="codeline">secondaryjoin</span> arguments to <span class="codeline">relation</span>, as in this example which creates a User object which has a relationship to all of its Addresses which are in Boston:
<&|formatting.myt:code&>
<li>secondaryjoin - a ClauseElement that will be used as the join of an association table to the child object. By default, this value is computed based on the foreign key relationships of the association and child tables.</li>
<li>foreignkey - specifies which column in this relationship is "foreign", i.e. which column refers to the parent object. This value is automatically determined in all cases, based on the primary and secondary join conditions, except in the case of a self-referential mapper, where it is needed to indicate the child object's reference back to it's parent.</li>
<li>uselist - a boolean that indicates if this property should be loaded as a list or a scalar. In most cases, this value is determined based on the type and direction of the relationship - one to many forms a list, one to one forms a scalar, many to many is a list. If a scalar is desired where normally a list would be present, set uselist to False.</li>
- <li>private - indicates if these child objects are "private" to the parent; removed items will also be deleted, and if the parent item is deleted, all child objects are deleted as well.</li>
- <li>live - a special type of "lazy load" where the list values will be loaded on every access. A "live" property should be treated as read-only. This type of property is useful in combination with "private" when used with a parent object which wants to force a delete of all its child items, attached or not, when it is deleted; since it always loads everything when accessed, you can be guaranteed that all child objects will be properly removed as well.</li>
+ <li>private - indicates if these child objects are "private" to the parent; removed items will also be deleted, and if the parent item is deleted, all child objects are deleted as well. See the example in <&formatting.myt:link, path="datamapping_relations_private"&>.</li>
+ <li>backreference - indicates the name of a property to be placed on the related mapper's class that will handle this relationship in the other direction, including synchronizing the object attributes on both sides of the relation. See the example in <&formatting.myt:link, path="datamapping_relations_backreferences"&>.</li>
<li>association - When specifying a many to many relationship with an association object, this keyword should reference the mapper of the target object of the association. See the example in <&formatting.myt:link, path="datamapping_association"&>.</li>
<li>selectalias - Useful with eager loads, this specifies a table alias name that will be used when creating joins against the parent table. The property is still created against the original table, and the aliased table is used only for the actual query. Aliased columns in the result set are translated back to that of the original table when creating object instances.</li>
+ <li>live - a special type of "lazy load" where the list values will be loaded on every access. A "live" property should be treated as read-only. This type of property is useful in combination with "private" when used with a parent object which wants to force a delete of all its child items, attached or not, when it is deleted; since it always loads everything when accessed, you can be guaranteed that all child objects will be properly removed as well.</li>
</ul>
</&>
</&>
<p>The main WHERE clause as well as the limiting clauses are coerced into a subquery; this subquery represents the desired result of objects. A containing query, which handles the eager relationships, is joined against the subquery to produce the result.</p>
</&>
-<&|doclib.myt:item, name="options", description="Mapper Options" &>
- <P>The <span class="codeline">options</span> method of mapper produces a copy of the mapper, with modified properties and/or options. This makes it easy to take a mapper and just change a few things on it. The method takes a variable number of <span class="codeline">MapperOption</span> objects which know how to change specific things about the mapper. The four available options are <span class="codeline">eagerload</span>, <span class="codeline">lazyload</span>, <span class="codeline">noload</span> and <span class="codeline">extension</span>.</p>
+<&|doclib.myt:item, name="options", description="More on Mapper Options" &>
+ <p>The <span class="codeline">options</span> method of mapper, first introduced in <&formatting.myt:link, path="datamapping_relations_options" &>, supports the copying of a mapper into a new one, with any number of its relations replaced by new ones. The method takes a variable number of <span class="codeline">MapperOption</span> objects which know how to change specific things about the mapper. The four available options are <span class="codeline">eagerload</span>, <span class="codeline">lazyload</span>, <span class="codeline">noload</span> and <span class="codeline">extension</span>.</p>
<P>An example of a mapper with a lazy load relationship, upgraded to an eager load relationship:
<&|formatting.myt:code&>
class User(object):
</&>
</&>
-<&|doclib.myt:item, name="custom", description="Custom Queries" &>
- <&|formatting.myt:code&>
- # a class
- class User(object):
- pass
-
- # basic mapper
- User.mapper = mapper(User, users)
-
- # basic select with criterion
- User.mapper.select(and_(users.c.user_name=='jane', users.c.user_id>12))
-
- # select with text criterion
- User.mapper.select("user_name='jane' and user_id>12")
-
- # select with totally textual query
- User.mapper.select_text("select user_id, user_name, password from users")
-
- # select with a Select object
- s = users.select(users, users.c.user_id==addresses.c.user_id)
- User.mapper.select(s)
- </&>
-</&>
<&|doclib.myt:item, name="inheritance", description="Mapping a Class with Table Inheritance" &>
+ <p>Table Inheritance indicates the pattern where two tables, in a parent-child relationship, are mapped to an inheritance chain of classes. If a table "news_articles" contains additional information about sports articles in the table "sports_news_articles", a corresponding object inheritance pattern would have a NewsArticle class and a SportsNewsArticle class. Loading a SportsNewsArticle object means you are joining sports_news_articles to news_articles. For SQLAlchemy, this pattern is just a special case of a mapper that maps against a joined relationship, and is provided via the <span class="codeline">inherits</span> keyword.
<&|formatting.myt:code&>
class User(object):
"""a user object."""
"""a user object that also has the users mailing address."""
pass
- # define a mapper for AddressUser that inherits the User.mapper, and joins on the user_id column
+ # define a mapper for AddressUser that inherits the User.mapper, and joins on the user_id column
+ # inherit_condition is required right now, but will be optional in a future release
AddressUser.mapper = mapper(
addresses, inherits = User.mapper,
inherit_condition=User.c.user_id==addresses.c.user_id
</&>
<&|doclib.myt:item, name="joins", description="Mapping a Class against Multiple Tables" &>
+ <P>The more general case of the pattern described in "table inheritance" is a mapper that maps against more than one table. The <span class="codeline">join</span> keyword from the SQL package creates a neat selectable unit comprised of multiple tables, complete with its own composite primary key, which can be passed in to a mapper as the table.</p>
<&|formatting.myt:code&>
# a class
class AddressUser(object):
pass
- # define a Join
+ # define a Join
+ # the join condition will be optional in a future release if foreign keys are defined
j = join(users, addresses, users.c.address_id==addresses.c.address_id)
# map to it - the identity of an AddressUser object will be
# based on (user_id, address_id) since those are the primary keys involved
m = mapper(AddressUser, j)
-
- # more complex join
+ </&>
+
+ A second example:
+ <&|formatting.myt:code&>
+ # many-to-many join on an association table
j = join(users, userkeywords,
users.c.user_id==userkeywords.c.user_id).join(keywords,
userkeywords.c.keyword_id==keywords.c.keyword_id)
</&>
</&>
<&|doclib.myt:item, name="multiple", description="Multiple Mappers for One Class" &>
+ <p>By now it should be apparent that the mapper defined for a class is in no way the only mapper that exists for that class. Other mappers can be created at any time; either explicitly or via the <span class="codeline">options</span> method, to provide different loading behavior.</p>
+
+ <p>However, its not as simple as that. The mapper serves a dual purpose; one is to generate select statements and load objects from executing those statements; the other is to keep track of the defined dependencies of that object when save and delete operations occur, and to extend the attributes of the object so that they store information about their history and communicate with the unit of work system. For this reason, it is a good idea to be aware of the behavior of multiple mappers. When creating dependency relationships between objects, one should insure that only the primary mappers are used in those relationships, else deep object traversal operations will fail to load in the expected properties, and update operations will not take all the dependencies into account. </p>
+
+ <p>Generally its as simple as, the <i>first</i> mapper that is defined for a particular class is the one that gets to define that classes' relationships to other mapped classes, and also decorates its attributes and constructors with special behavior. Any subsequent mappers created for that class will be able to load new instances, but object manipulation operations will still function via the original mapper. The special keyword <span class="codeline">is_primary</span> will override this behavior, and make any mapper the new "primary" mapper.
+ </p>
<&|formatting.myt:code&>
class User(object):
pass
<p>The three elements to be defined, i.e. the Table metadata, the user-defined class, and the Mapper, are typically defined as module-level variables, and may be defined in any fashion suitable to the application, with the only requirement being that the class and table metadata are described before the mapper. For the sake of example, we will be defining these elements close together, but this should not be construed as a requirement; since SQLAlchemy is not a framework, those decisions are left to the developer or an external framework.
</p>
-<&|doclib.myt:item, name="example", description="Basic Example" &>
+<&|doclib.myt:item, name="synopsis", description="Synopsis" &>
+ <p>This is the simplest form of a full "round trip" of creating table meta data, creating a class, mapping the class to the table, getting some results, and saving changes. For each concept, the following sections will dig in deeper to the available capabilities.</p>
<&|formatting.myt:code&>
from sqlalchemy import *
+ # engine
+ engine = create_engine("sqlite://mydb.db")
+
# table metadata
users = Table('users', engine,
Column('user_id', Integer, primary_key=True),
usermapper = mapper(User, users)
# select
-<&formatting.myt:poplink&>user = usermapper.select(users.c.user_name == 'fred')[0]
+<&formatting.myt:poplink&>user = usermapper.select_by(user_name='fred')[0]
<&|formatting.myt:codepopper, link="sql" &>
SELECT users.user_id AS users_user_id, users.user_name AS users_user_name,
users.password AS users_password
<&|formatting.myt:code&>
User.mapper = mapper(User, users)
- userlist = User.mapper.select(users.c.user_id==12)
+ userlist = User.mapper.select_by(user_id=12)
</&>
- <p>In addition, when a mapper is assigned to a class, it also attaches a special property accessor <span class="codeline">c</span> to the class itself, which can be used just like the table metadata to access the columns of the table:</p>
+</&>
+<&|doclib.myt:item, name="selecting", description="Selecting from a Mapper" &>
+ <p>There are a variety of ways to select from a mapper. These range from minimalist to explicit. Below is a synopsis of the these methods:</p>
+ <&|formatting.myt:code&>
+ # select_by, using property names or column names as keys
+ # the keys are grouped together by an AND operator
+ result = mapper.select_by(name='john', street='123 green street')
+
+ # get_by, which returns a single scalar result or None if no results
+ user = mapper.get_by(id=12)
+
+ # "dynamic" versions of select_by and get_by - everything past the
+ # "select_by_" or "get_by_" is used as the key, and the function argument
+ # as the value
+ result = mapper.select_by_name('fred')
+ u = mapper.get_by_name('fred')
+
+ # get an object directly from its primary key. this will bypass the SQL
+ # call if the object has already been loaded
+ u = mapper.get(15)
+
+ # get an object that has a composite primary key of three columns.
+ # the order of the arguments matches that of the table meta data.
+ myobj = mapper.get(27, 3, 'receipts')
+
+ # using a WHERE criterion
+ result = mapper.select(or_(users.c.user_name == 'john', users.c.user_name=='fred'))
+
+ # using a WHERE criterion to get a scalar
+ u = mapper.selectone(users.c.user_name=='john')
+
+ # using a full select object
+ result = mapper.select(users.select(users.c.user_name=='john'))
+
+ # using straight text
+ result = mapper.select_text("select * from users where user_name='fred'")
+
+ # or using a "text" object (the 'engine' parameter will not be needed soon)
+ result = mapper.select(text("select * from users where user_name='fred'", engine=engine))
+ </&>
+ <p>The last few examples above show the usage of the mapper's table object to provide the columns for a WHERE Clause. These columns are also accessible off of the mapped class directly. When a mapper is assigned to a class, it also attaches a special property accessor <span class="codeline">c</span> to the class itself, which can be used just like the table metadata to access the columns of the table:</p>
<&|formatting.myt:code&>
User.mapper = mapper(User, users)
userlist = User.mapper.select(User.c.user_id==12)
</&>
-
-
- <p>When a mapper is created, the target class also has its <span class="codeline">__init__()</span> method decorated to mark new objects as "new", in addition to tracking changes to properties. The attribute representing the primary key of the table is handled automatically by SQLAlchemy as well:</p>
+</&>
+<&|doclib.myt:item, name="saving", description="Saving Objects" &>
+ <p>When objects corresponding to mapped classes are created or manipulated, all changes are logged by a package called <span class="codeline">sqlalchemy.mapping.objectstore</span>. The changes are then written to the database when an application calls <span class="codeline">objectstore.commit()</span>. This pattern is known as a <b>Unit of Work</b>, and has many advantages over saving individual objects or attributes on those objects with individual method invocations. Domain models can be built with far greater complexity with no concern over the order of saves and deletes, excessive database round-trips and write operations, or deadlocking issues. The commit() operation uses a transaction as well, and will also perform "concurrency checking" to insure the proper number of rows were in fact affected (not supported with the current MySQL drivers). Transactional resources are used effectively in all cases; the unit of work handles all the details.</p>
+
+ <p>When a mapper is created, the target class has its mapped properties decorated by specialized property accessors that track changes, and its <span class="codeline">__init__()</span> method is also decorated to mark new objects as "new".</p>
<&|formatting.myt:code&>
User.mapper = mapper(User, users)
Column('zip', String(10))
)
</&>
-<p>Of importance here is the addresses table's definition of a <b>foreign key</b> relationship to the users table, relating the user_id column into a parent-child relationship. When a Mapper wants to indicate a relation of one object to another, this ForeignKey object is the default method by which the relationship is determined (although if you didn't define ForeignKeys, or you want to specify explicit relationship columns, that is available as well).</p>
+<p>Of importance here is the addresses table's definition of a <b>foreign key</b> relationship to the users table, relating the user_id column into a parent-child relationship. When a Mapper wants to indicate a relation of one object to another, this ForeignKey object is the default method by which the relationship is determined (although if you didn't define ForeignKeys, or you want to specify explicit relationship columns, that is available as well). </p>
<p>So then lets define two classes, the familiar User class, as well as an Address class:
<&|formatting.myt:code&>
}
)
</&>
+<p>Alternatively, the Address mapper can be defined separately. This allows the mappers of various objects to be combined in any number of ways at runtime:
+ <&|formatting.myt:code&>
+ Address.mapper = mapper(Address, addresses)
+ User.mapper = mapper(User, users, properties = {
+ 'addresses' : relation(Address.mapper)
+ }
+ )
+ </&>
+
+</p>
<p>Lets do some operations with these classes and see what happens:</p>
<&|formatting.myt:code&>
</&>
</&>
+<&|doclib.myt:item, name="private", description="Useful Feature: Private Relations" &>
<p>So our one address that was removed from the list, was updated to have a user_id of <span class="codeline">None</span>, and a new address object was inserted to correspond to the new Address added to the User. But now, theres a mailing address with no user_id floating around in the database of no use to anyone. How can we avoid this ? This is acheived by using the <span class="codeline">private=True</span> parameter of <span class="codeline">relation</span>:
<&|formatting.myt:code&>
</&>
<p>In this case, with the private flag set, the element that was removed from the addresses list was also removed from the database. By specifying the <span class="codeline">private</span> flag on a relation, it is indicated to the Mapper that these related objects exist only as children of the parent object, otherwise should be deleted.</p>
+</&>
+<&|doclib.myt:item, name="backreferences", description="Useful Feature: Backreferences" &>
+<p>By creating relations with the <span class="codeline">backreference</span> keyword, a bi-directional relationship can be created which will keep both ends of the relationship updated automatically, even without any database queries being executed. Below, the User mapper is created with an "addresses" property, and the corresponding Address mapper receives a "backreference" to the User object via the property name "user":
+ <&|formatting.myt:code&>
+ User.mapper = mapper(User, users, properties = {
+ 'addresses' : relation(Address, addresses, backreference='user')
+ }
+ )
+
+ u = User('fred', 'hi')
+ a1 = Address('123 anywhere street', 'big city', 'UT', '76543')
+ a2 = Address('1 Park Place', 'some other city', 'OK', '83923')
+
+ # append a1 to u
+ u.addresses.append(a1)
+
+ # attach u to a2
+ a2.user = u
+
+ # the bi-directional relation is maintained
+ >>> u.addresses == [a1, a2]
+ True
+ >>> a1.user is user and a2.user is user
+ True
+ </&>
+<p>The backreference feature also works with many-to-many relationships, which are described later. When creating a backreference, a corresponding property is placed on the child mapper. This proeprty can be overridden with a custom property using the <span class="codeline">add_property</span> function:
+ <&|formatting.myt:code&>
+ Address.mapper = mapper(Address, addresses)
+
+ User.mapper = mapper(User, users, properties = {
+ 'addresses' : relation(Address.mapper, backreference='user')
+ }
+ )
+
+ Address.mapper.add_property('user', relation(
+ User.mapper, lazy=False, private=True, backreference='addresses'
+ ))
+
+ </&>
+<p>Note that when overriding a backreferenced property, we re-specify the backreference as well. This will not override the existing 'addresses' property on the User class, but just sends a message to the attribute-management system that it should continue to maintain this backreference.</p>
+</&>
<&|doclib.myt:item, name="lazyload", description="Selecting from Relationships: Lazy Load" &>
<P>We've seen how the <span class="codeline">relation</span> specifier affects the saving of an object and its child items, how does it affect selecting them? By default, the relation keyword indicates that the related property should be attached a <b>Lazy Loader</b> when instances of the parent object are loaded from the database; this is just a callable function that when accessed will invoke a second SQL query to load the child objects of the parent.</p>
# select users where username is 'jane', get the first element of the list
# this will incur a load operation for the parent table
- user = User.mapper.select(User.c.user_name=='jane')[0]
+ user = User.mapper.select(user_name='jane')[0]
<&|formatting.myt:poppedcode, link="sql" &>SELECT users.user_id AS users_user_id,
users.user_name AS users_user_name, users.password AS users_password
WHERE addresses.user_id = :users_user_id ORDER BY addresses.oid
{'users_user_id': 1}</&>
print repr(a)
+
</&>
+ <&|doclib.myt:item, name="relselectby", description="Useful Feature: Creating Joins via select_by" &>
+ <p>In mappers that have relationships, the <span class="codeline">select_by</span> method and its cousins include special functionality that can be used to create joins. Just specify a key in the argument list which is not present in the primary mapper's list of properties or columns, but *is* present in the property list of one of its relationships:
+ <&|formatting.myt:code&>
+ <&formatting.myt:poplink&>l = User.mapper.select_by(street='123 Green Street')
+<&|formatting.myt:codepopper, link="sql" &>SELECT users.user_id AS users_user_id,
+users.user_name AS users_user_name, users.password AS users_password
+FROM users, addresses
+WHERE users.user_id=addresses.user_id
+AND addresses.street=:addresses_street
+ORDER BY users.oid
+{'addresses_street', '123 Green Street'}
+</&>
+ </&>
+ <p>The above example is shorthand for:</p>
+ <&|formatting.myt:code&>
+ l = User.mapper.select(and_(
+ Address.c.user_id==User.c.user_id,
+ Address.c.street=='123 Green Street')
+ )
+ </&>
+
+ </&>
<&|doclib.myt:item, name="refreshing", description="How to Refresh the List?" &>
<p>Once the child list of Address objects is loaded, it is done loading for the lifetime of the object instance. Changes to the list will not be interfered with by subsequent loads, and upon commit those changes will be saved. Similarly, if a new User object is created and child Address objects added, a subsequent select operation which happens to touch upon that User instance, will also not affect the child list, since it is already loaded.</p>
objectstore.clear()
</&>
<p>This operation will clear out all currently mapped object instances, and subsequent select statements will load fresh copies from the databse.</p>
+
+ <p>To operate upon a single object, just use the <span class="codeline">remove</span> function:</p>
+ <&|formatting.myt:code&>
+ # (this function coming soon)
+ objectstore.remove(myobject)
+ </&>
+
</&>
</&>
<&|doclib.myt:item, name="eagerload", description="Selecting from Relationships: Eager Load" &>
}
)
- user = User.mapper.select(User.c.user_name=='jane')[0]
+ user = User.mapper.get_by(user_name='jane')
<&|formatting.myt:poppedcode, link="sql" &>SELECT users.user_id AS users_user_id, users.user_name AS users_user_name,
users.password AS users_password,
WHERE users.user_name = :users_user_name ORDER BY users.oid, addresses.oid
{'users_user_name': 'jane'}
</&>
-
for a in user.addresses:
print repr(a)
</&>
+ <P>Above, a pretty ambitious query is generated just by specifying that the User should be loaded with its child Addresses in one query. When the mapper processes the results, it uses an <b>Identity Map</b> to keep track of objects that were already loaded, based on their primary key identity. Through this method, the redundant rows produced by the join are organized into the distinct object instances they represent.</p>
- </&>
- <&|doclib.myt:item, name="options", description="Switching Lazy/Eager, No Load" &>
- </&>
+ <p>The generation of this query is also immune to the effects of additional joins being specified in the original query. To use our select_by example above, joining against the "addresses" table to locate users with a certain street results in this behavior:
+ <&|formatting.myt:code&>
+ users = User.mapper.select_by(street='123 Green Street')
+<&|formatting.myt:poppedcode, link="sql" &>SELECT users.user_id AS users_user_id,
+users.user_name AS users_user_name, users.password AS users_password,
+addresses.address_id AS addresses_address_id,
+addresses.user_id AS addresses_user_id, addresses.street AS addresses_street,
+addresses.city AS addresses_city, addresses.state AS addresses_state,
+addresses.zip AS addresses_zip
+FROM addresses AS addresses_417c,
+users LEFT OUTER JOIN addresses ON users.user_id = addresses.user_id
+WHERE addresses_417c.street = :addresses_street
+AND users.user_id = addresses_417c.user_id
+ORDER BY users.oid, addresses.oid
+{'addresses_street': '123 Green Street'}
</&>
-
-<&|doclib.myt:item, name="onetomany", description="One to Many" &>
-
+ </&>
+ <p>The join implied by passing the "street" parameter is converted into an "aliasized" clause by the eager loader, so that it does not conflict with the join used to eager load the child address objects.</p>
+ </&>
+ <&|doclib.myt:item, name="options", description="Switching Lazy/Eager, No Load" &>
+ <p>The <span class="codeline">options</span> method of mapper provides an easy way to get alternate forms of a mapper from an original one. The most common use of this feature is to change the "eager/lazy" loading behavior of a particular mapper, via the functions <span class="codeline">eagerload()</span>, <span class="codeline">lazyload()</span> and <span class="codeline">noload()</span>:
+ </p>
<&|formatting.myt:code&>
- # second table metadata
- addresses = Table('email_addresses', engine,
- Column('address_id', Integer, primary_key = True),
- Column('user_id', Integer, ForeignKey("users.user_id")),
- Column('email_address', String(20)),
- )
-
- # second class definition
- class Address(object):
- def __init__(self, email_address = None):
- self.email_address = email_address
-
- Address.mapper = mapper(Address, addresses)
-
+ # user mapper with lazy addresses
+ User.mapper = mapper(User, users, properties = {
+ 'addresses' : relation(Address, addresses)
+ }
+ )
+
+ # make an eager loader
+ eagermapper = User.mapper.options(eagerload('addresses'))
+ u = eagermapper.select()
+
+ # make another mapper that wont load the addresses at all
+ plainmapper = User.mapper.options(noload('addresses'))
+
+ # multiple options can be specified
+ mymapper = oldmapper.options(lazyload('tracker'), noload('streets'), eagerload('members'))
+
+ # to specify a relation on a relation, separate the property names by a "."
+ mymapper = oldmapper.options(eagerload('orders.items'))
+
+ </&>
- # give the User class a new Mapper referencing addresses.
- # "private=True" means deletions of the user
- # will cascade down to the child Address objects
- User.mapper = mapper(Mapper, users, properties = dict(
- relation(Address.mapper, lazy=True, private=True)
- ))
-
- # select
-<&formatting.myt:poplink&>user = User.mapper.select(User.c.user_name == 'fred jones')[0]
-<&|formatting.myt:codepopper, link="sql" &>
-SELECT users.user_id AS users_user_id, users.user_name AS users_user_name,
-users.password AS users_password
-FROM users
-WHERE users.user_name = :users_user_name ORDER BY users.oid
+ </&>
-{'users_user_name': 'fred jones'}
</&>
-<&formatting.myt:poplink&>address = user.addresses[0]
-<&|formatting.myt:codepopper, link="sql" &>
-SELECT email_addresses.address_id AS email_addresses_address_id,
-email_addresses.user_id AS email_addresses_user_id,
-email_addresses.email_address AS email_addresses_email_address
-FROM email_addresses
-WHERE email_addresses.user_id = :users_user_id
-ORDER BY email_addresses.oid, email_addresses.oid
-{'users_user_id': 1}
-</&>
-
- # modify
- user.user_name = 'fred'
- user.addresses[0].email_address = 'fredjones@foo.com'
- user.addresses.append(Address('freddy@hi.org'))
-
- # commit
-<&formatting.myt:poplink&>objectstore.commit()
-<&|formatting.myt:codepopper, link="sql" &>
-UPDATE users SET user_id=:user_id, user_name=:user_name,
-password=:password WHERE users.user_id = :user_id
-
-[{'user_name': u'fred', 'password': u'45nfss', 'user_id': 1}]
-
-UPDATE email_addresses SET address_id=:address_id, user_id=:user_id,
-email_address=:email_address WHERE email_addresses.address_id = :address_id
-
-[{'email_address': 'fredjones@foo.com', 'address_id': 1, 'user_id': 1}]
-
-INSERT IntegerO email_addresses (address_id, user_id, email_address)
-VALUES (:address_id, :user_id, :email_address)
-
-{'email_address': 'freddy@hi.org', 'address_id': None, 'user_id': 1}
-</&>
- </&>
-</&>
-<&|doclib.myt:item, name="onetoone", description="One to One" &>
+<&|doclib.myt:item, name="onetoone", description="One to One/Many to One" &>
+<p>The above examples focused on the "one-to-many" relationship. To do other forms of relationship is easy, as the <span class="codeline">relation</span> function can usually figure out what you want:</p>
<&|formatting.myt:code&>
# a table to store a user's preferences for a site
))
# select
-<&formatting.myt:poplink&>user = m.select(users.c.user_name == 'fred')[0]
+<&formatting.myt:poplink&>user = m.get_by(user_name='fred')
<&|formatting.myt:codepopper, link="sql" &>
SELECT users.user_id AS users_user_id, users.user_name AS users_user_name,
users.password AS users_password, users.preference_id AS users_preference_id,
</&>
# commit
- <&formatting.myt:poplink&>
- objectstore.commit() <&|formatting.myt:codepopper, link="sql" &>
+ <&formatting.myt:poplink&>objectstore.commit()
+<&|formatting.myt:codepopper, link="sql" &>
UPDATE user_prefs SET pref_id=:pref_id, stylename=:stylename,
save_password=:save_password, timezone=:timezone
WHERE user_prefs.pref_id = :pref_id
</&>
<&|doclib.myt:item, name="manytomany", description="Many to Many" &>
+<p>The <span class="codeline">relation</span> function handles a basic many-to-many relationship when you specify the association table:</p>
<&|formatting.myt:code&>
articles = Table('articles', engine,
Column('article_id', Integer, primary_key = True),
Column('article_headline', String(150), key='headline'),
- Column('article_body', Text, key='body'),
+ Column('article_body', TEXT, key='body'),
)
keywords = Table('keywords', engine,
Column('keyword_id', Integer, primary_key = True),
- Column('name', String(50))
+ Column('keyword_name', String(50))
)
itemkeywords = Table('article_keywords', engine,
class Keyword(object):
def __init__(self, name = None):
self.name = name
- Keyword.mapper = mapper(Keyword, keywords)
class Article(object):
- def __init__(self):
- self.keywords = []
-
+ pass
+ # define a mapper that does many-to-many on the 'itemkeywords' association
+ # table
Article.mapper = mapper(Article, articles, properties = dict(
- keywords = relation(Keyword.mapper, itemkeywords, lazy=False)
+ keywords = relation(Keyword, keywords, itemkeywords, lazy=False)
)
)
[{'keyword_id': 1, 'article_id': 1}, {'keyword_id': 2, 'article_id': 1}]
</&>
- # select articles based on some keywords. to select against joined criterion, we specify the
- # join condition explicitly. the tables in the extra joined criterion
- # will be given aliases at the SQL level so that they don't interfere with those of the JOIN
- # already used for the eager load.
- articles = Article.mapper.select(sql.and_(keywords.c.keyword_id==itemkeywords.c.keyword_id,
- itemkeywords.c.article_id==articles.c.article_id,
- <&formatting.myt:poplink&>
- keywords.c.name.in_('politics', 'entertainment'))) <&|formatting.myt:codepopper, link="sql" &>
+ # select articles based on a keyword. select_by will handle the extra joins.
+ <&formatting.myt:poplink&>articles = Article.mapper.select_by(keyword='politics')
+<&|formatting.myt:codepopper, link="sql" &>
SELECT articles.article_id AS articles_article_id,
articles.article_headline AS articles_article_headline,
articles.article_body AS articles_article_body,
keywords.keyword_id AS keywords_keyword_id,
-keywords.name AS keywords_name
-FROM keywords keywords_6fca, article_keywords article_keywords_75c8,
-articles LEFT OUTER JOIN article_keywords ON articles.article_id = article_keywords.article_id
-LEFT OUTER JOIN keywords ON keywords.keyword_id = article_keywords.keyword_id
-WHERE keywords_6fca.keyword_id = article_keywords_75c8.keyword_id
-AND article_keywords_75c8.article_id = articles.article_id
-AND keywords_6fca.name IN ('politics', 'entertainment')
-ORDER BY articles.oid, article_keywords.oid
+keywords.keyword_name AS keywords_keyword_name
+FROM keywords AS keywords_f008,
+article_keywords AS article_keywords_dbf0,
+articles LEFT OUTER JOIN article_keywords ON
+articles.article_id = article_keywords.article_id
+LEFT OUTER JOIN keywords ON
+keywords.keyword_id = article_keywords.keyword_id
+WHERE (keywords_f008.keyword_name = :keywords_keyword_name
+AND articles.article_id = article_keywords_dbf0.article_id)
+AND keywords_f008.keyword_id = article_keywords_dbf0.keyword_id
+ORDER BY articles.oid, article_keywords.oid
+{'keywords_keyword_name': 'politics'}
</&>
# modify
a = articles[0]
</&>
<&|doclib.myt:item, name="association", description="Association Object" &>
- <p>Many to Many can also be done with an association object, that adds additional information about how two items are related. This association object is set up in basically the same way as any other mapped object. However, since an association table typically has no primary keys, you have to tell the mapper what columns will act as its "primary keys", which are the two columns involved in the association. Also, the relation function needs an additional hint as to the fact that this mapped object is an association object, via the "association" argument which points to the class or mapper representing the other side of the association.</p>
+ <p>Many to Many can also be done with an association object, that adds additional information about how two items are related. This association object is set up in basically the same way as any other mapped object. However, since an association table typically has no primary key columns, you have to tell the mapper what columns will compose its "primary key", which are the two (or more) columns involved in the association. Also, the relation function needs an additional hint as to the fact that this mapped object is an association object, via the "association" argument which points to the class or mapper representing the other side of the association.</p>
<&|formatting.myt:code&>
# add "attached_by" column which will reference the user who attached this keyword
itemkeywords = Table('article_keywords', engine,
m2 = mapper.options(eagerload('keywords.user'))
# select by keyword again
- <&formatting.myt:poplink&>
- alist = m2.select(
- sql.and_(
- keywords.c.keyword_id==itemkeywords.c.keyword_id,
- itemkeywords.c.article_id==articles.c.article_id,
- keywords.c.name == 'jacks_stories'
- ))
-
+ <&formatting.myt:poplink&>alist = m2.select_by(keyword_name='jacks_stories')
<&|formatting.myt:codepopper, link="sql" &>
SELECT articles.article_id AS articles_article_id,
articles.article_headline AS articles_article_headline,
{'keywords_name': 'jacks_stories'}
</&>
-
# user is available
for a in alist:
for k in a.keywords: