Diff for "Collections"

Not logged in - Log In / Register

Differences between revisions 3 and 4
Revision 3 as of 2010-08-01 20:52:55
Size: 3974
Editor: james-w
Comment:
Revision 4 as of 2010-08-02 01:51:57
Size: 5313
Editor: jtv
Comment:
Deletions are marked like this. Additions are marked like this.
Line 39: Line 39:
    """A collection of `Foo`."""
Line 64: Line 64:
    """Collection of `Foo`."""
    def select(*args):
        """See `Collection`."""
Line 66: Line 69:
with the methods you want on the interface. Ensure that one of the methods you
put on the interface is

{{{
    def select(*args):
}}}

As this is what will be used to get the ResultSet.
with the methods you want on the interface. The `select` method has to be there, since it's how you retrieve a Storm `ResultSet` with the objects and/or columns you want from the collection.
Line 125: Line 121:
returned you a FooCollection for all the Foos associated with that product. returned you a `FooCollection` for all the Foos associated with that product.
Line 127: Line 123:
Please fill in the details of how to do this here if you do it. ''Please fill in the details of how to do this here if you do it.''
Line 131: Line 128:
Please fill in the details of how to join tables here if you work it out. Two `Collection` methods help you join other tables into a collection: `joinInner` which creates a run-of-the-mill inner join and `joinOuter` which adds in the new table using an outer (or "left") join. They both work like:
{{{
    joined_collection = base_collection.joinInner(Person, Person.id == Foo.owner)
    joined_collection = base_collection.joinOuter(Person, Person.id == Foo.owner)
}}}

(Of course the "outer" case means that the owner is optional, and the `Person` will be `None` if there is no matching `Foo.owner`. The "inner" case will just ignore `Foo` items that don't have an owner.)


== Custom Selects ==

The `select` method returns a `ResultSet` of `Foo` by default:
{{{
    num_foos = all_foos.select().count()
    print "There are %d foo(s)." % num_foos
    if num_foos > 0:
        print "The oldest foo is %s." % all_foos.select().order_by(Foo.id)[0]
}}}

However you can select any combination of columns and objects that are in the query. Each `select` will create a new `ResultSet` so each will be executed separately.
{{{
    foos_and_owners = all_foos.innerJoin(Person, Person.id == Foo.owner)
    for foo, owner_name in foos_and_owners.select(Foo, Person.name):
        print "Foo #%d is owned by %s." % (foo.id, owner_name)
}}}

Collections

Collections are a way of selecting a group of object based on some criteria, and then either just getting the objects, or possibly manipulating the set as one.

They have two types of methods on them, firstly restrict methods, which reduce the set of objects according to some criteria, and secondly manipulation methods, which manipulate each of the objects in the current set.

Examples

Example use

all_branches = getUtility(IAllBranches)
my_branches = all_branches.ownedBy(me)
branches_i_can_see = all_branches.visibleByUser(me)
merge_proposals_on_my_branches = my_branches.getMergeProposals()
my_branch_objects = my_branches.getBranches()

Creating a collection

In lp.services.database.collection there is a base class you can use for creating your own collection.

In lp.app.model.foocollection add

from lp.app.model.foo import Foo
from lp.services.database.collection import Collection


class FooCollection(Collection):
    """A collection of `Foo`."""
    starting_table = Foo

Which is the basic collection.

You can then add methods to it such as

    def ownedBy(self, owner):
        return self.refine(Foo.owner == owner)

with appropriate tests (see lp.soyuz.tests.test_archivecollection for some inspiration).

Once you have an object with the methods that will be useful to you, you need to add an interface and a utility.

In lp.app.interfaces.foocollection add the following:

from zope.interface import Interface


class IFooCollection(Interface):
    """Collection of `Foo`."""
    def select(*args):
        """See `Collection`."""

with the methods you want on the interface. The select method has to be there, since it's how you retrieve a Storm ResultSet with the objects and/or columns you want from the collection.

Once you have that you can add an Interface for getting a utility to get all Foos

class IAllFoos(IFooCollection):
    """Get all foos."""

You can add other marker interfaces here if you wish to provide other entry points, for instance if it is very common to be interested in all foos of a particular type or status.

Next comes the zcml:

    <securedutility                                                           
        class="lp.app.model.foocollection.FooCollection"            
        provides="lp.app.interfaces.foocollection.IAllFoo">        
        <allow                                                                
            interface="lp.app.interfaces.foocollection.IAllFoo" /> 
    </securedutility>                                                         
                                                                              
    <class class="lp.app.model.foocollection.FooCollection">        
        <allow                                                                
            interface="lp.app.interfaces.foocollection.IAllFoo" /> 
    </class>              

Which will mean that you can getUtility(IAllFoo) to start working with a collection.

Using the collection

all_foos = getUtility(IAllFoo)
foos = all_foos.ownedBy(person).withStatus(status).select()

The arguments to select are the same as the first argument to Store.find().

Adding adapters

It is possible to add adapters for objects of interest to get a collection initialized as appropriate.

For instance you could add adapters such that

IFooColllection(product)

returned you a FooCollection for all the Foos associated with that product.

Please fill in the details of how to do this here if you do it.

Adding Joins

Two Collection methods help you join other tables into a collection: joinInner which creates a run-of-the-mill inner join and joinOuter which adds in the new table using an outer (or "left") join. They both work like:

    joined_collection = base_collection.joinInner(Person, Person.id == Foo.owner)
    joined_collection = base_collection.joinOuter(Person, Person.id == Foo.owner)

(Of course the "outer" case means that the owner is optional, and the Person will be None if there is no matching Foo.owner. The "inner" case will just ignore Foo items that don't have an owner.)

Custom Selects

The select method returns a ResultSet of Foo by default:

    num_foos = all_foos.select().count()
    print "There are %d foo(s)." % num_foos
    if num_foos > 0:
        print "The oldest foo is %s." % all_foos.select().order_by(Foo.id)[0]

However you can select any combination of columns and objects that are in the query. Each select will create a new ResultSet so each will be executed separately.

    foos_and_owners = all_foos.innerJoin(Person, Person.id == Foo.owner)
    for foo, owner_name in foos_and_owners.select(Foo, Person.name):
        print "Foo #%d is owned by %s." % (foo.id, owner_name)

Collections (last edited 2019-10-04 11:27:35 by cjwatson)