Statement API and declarations

6 views
Skip to first unread message

Ben Bangert

unread,
May 29, 2007, 1:03:28 PM5/29/07
to SQLElixir
I made a version of Jonathan's acts_as_taggable, that sets up a many-
to-many table to retain referential integrity, easy eager loading, and
the ability to add multiple Tag's at once with proper relating to
existing Tag's in the database. It's here:
http://pylonshq.com/pasties/298

While creating it, I ran into a few things:

1) The only way I could find to indicate I wanted to do something
*after* the entity's mapper/table were created, was to flag my
Taggable in the _descriptor. Not sure if that's the 'right' way, or
what have you, it just appeared to be the only way. Could there be a
better defined way to indicate when blocks of a Statement should be
executed (ie, after the entity's mapper/table is created)?

2) It'd be great to be able to use a namespace to declare statement
added functions. Ian Bicking had the suggestion (based partly on what
he does in SQLObject), to allow a definition like this:

class Person(object):
has_field('name', Unicode)
tags = Taggable(backref='people')

This would then mean I could have the methods from the Taggable
Statement add its function under the 'tags' space in the Person. Ie.

Person.tags.get('manager')
personinstance.tags.add(['manager', 'employee']) # Elixir add's
declared instance functions to the SA InstrumentedList object
cloud_data = Person.tags.cloud()
mytags = personinstance.tags # uses normal SA tags results

This has a few nice advantages:
1) I know where the added functions are, they aren't just tossed into
the Person entity's function space.
2) I could declare multiple Taggable options for a single entity.
Perhaps a blog Entry should have author visible tags *and* community
tags that are separate. I could do that with this approach like so:
class Person(object):
has_field('name', Unicode)
author_tags = Taggable(backref='people')
public_tags = Taggable()

Currently, there's no way to do this since the acts_as_taggable just
pops its methods into the main one. While it could start adding in
functions with more prefix's in front, it seems very clumsy compared
to being able to declare the namespace for the Taggable functions.

Of course, this makes me wonder if it wouldn't be a bit shorter to
copy a little bit more of SQLObject's declarative style, and let one
do:
class Person(object):
name = Unicode
tags = Taggable(backref='people')

Which is actually shorter than using a bunch of has_field or Column
statements.

Any thoughts on these API suggestions?

Cheers,
Ben

Jonathan LaCour

unread,
May 29, 2007, 1:30:45 PM5/29/07
to sqle...@googlegroups.com
Ben Bangert wrote:

> I made a version of Jonathan's acts_as_taggable, that sets up a
> many- to-many table to retain referential integrity, easy eager
> loading, and the ability to add multiple Tag's at once with
> proper relating to existing Tag's in the database. It's here:
> http://pylonshq.com/pasties/298

Very very nice. This is the "right" way to do things, without a doubt.

> While creating it, I ran into a few things:
>
> 1) The only way I could find to indicate I wanted to do something
> *after* the entity's mapper/table were created, was to flag my
> Taggable in the _descriptor. Not sure if that's the 'right' way, or
> what have you, it just appeared to be the only way. Could there be a
> better defined way to indicate when blocks of a Statement should be
> executed (ie, after the entity's mapper/table is created)?

This is a great idea. We haven't really put in a whole lot of
supporting hooks for statement developers, because we don't really know
what kinds of things are needed. This sounds like it would definitely
help a lot, and wouldn't be all that hard to implement, I don't think.
Please, feel free to hack in whatever you need to make statement
development easier, and we'll try to get them into SVN.

> 2) It'd be great to be able to use a namespace to declare statement
> added functions.
>

> [snip, snip]


>
> This would then mean I could have the methods from the Taggable
> Statement add its function under the 'tags' space in the Person.
>

> [snip, snip]


>
> This has a few nice advantages: 1) I know where the added functions
> are, they aren't just tossed into the Person entity's function space.
> 2) I could declare multiple Taggable options for a single entity.
> Perhaps a blog Entry should have author visible tags *and* community
> tags that are separate. I could do that with this approach like so:
>
> class Person(object):
> has_field('name', Unicode)
> author_tags = Taggable(backref='people')
> public_tags = Taggable()
>
> Currently, there's no way to do this since the acts_as_taggable just
> pops its methods into the main one. While it could start adding in
> functions with more prefix's in front, it seems very clumsy compared
> to being able to declare the namespace for the Taggable functions.

I am very conflicted here. I think you are essentially asking for
something that is a bit more explicit, however it doesn't look like
anything else in Elixir as it stands (more like SQLObject), which makes
things look very inconsistent. If we were going to go this route, I'd
personally want to make a drastic change to Elixir to make everything
consistent. Thats a big decision that affects a lot of people, which
makes it difficult to make :)

That being said, I really like some of the functionality that your
suggestion would provide. I would like to see if we can't figure out
a way to get what you are suggesting, without having to go and change
the entire declarative style of Elixir. If we can't think of anything,
that might mean that we really *do* need to re-evaluate some of our
decisions. Until then, I'd hate to change directions at this point in
the project.

So, in order to keep this discussion focused, and not have it turn into
a big API war, lets steer clear of the discussion about chucking the
DSL-style out the window for now, and just stick to discussing potential
ways to get all the benefits of your "taggable" plugin, without having
to make such drastic changes. I think that, at the conclusion of this
discussion, we'll probably be able to make a better evaluation.

> Of course, this makes me wonder if it wouldn't be a bit shorter to
> copy a little bit more of SQLObject's declarative style, and let one
> do:
>
> class Person(object):
> name = Unicode
> tags = Taggable(backref='people')
>
> Which is actually shorter than using a bunch of has_field or Column
> statements.

Again, lets hold off on this for now, but I see where you are going ;)

--
Jonathan LaCour
http://cleverdevil.org

Ben Bangert

unread,
May 29, 2007, 2:07:09 PM5/29/07
to SQLElixir
On May 29, 10:30 am, Jonathan LaCour <jonathan-li...@cleverdevil.org>
wrote:

> I am very conflicted here. I think you are essentially asking for
> something that is a bit more explicit, however it doesn't look like
> anything else in Elixir as it stands (more like SQLObject), which makes
> things look very inconsistent. If we were going to go this route, I'd
> personally want to make a drastic change to Elixir to make everything
> consistent. Thats a big decision that affects a lot of people, which
> makes it difficult to make :)
>
> That being said, I really like some of the functionality that your
> suggestion would provide. I would like to see if we can't figure out
> a way to get what you are suggesting, without having to go and change
> the entire declarative style of Elixir. If we can't think of anything,
> that might mean that we really *do* need to re-evaluate some of our
> decisions. Until then, I'd hate to change directions at this point in
> the project.
>
> So, in order to keep this discussion focused, and not have it turn into
> a big API war, lets steer clear of the discussion about chucking the
> DSL-style out the window for now, and just stick to discussing potential
> ways to get all the benefits of your "taggable" plugin, without having
> to make such drastic changes. I think that, at the conclusion of this
> discussion, we'll probably be able to make a better evaluation.

Ok, sounds good. So to keep it focused, and the API style consistent
for now, I can see adding a option to the acts_as_taggable there like
so:

class Person(object):
has_field('name', Unicode)

acts_as_taggable(name='author_tags', backref='people')
acts_as_taggable()

The default would use 'tags', while the name can provide an optional
setup. So ignoring the API isues, I still need a way to put the
additional functions underneath the 'tags' name in the Person entity.

I can add a 'tags' object to Person, then attach the class type
functions to that right now, so I believe the remaining capability
that isn't available (and will likely need a SQLAlchemy patch) is to
be able to pass a subclassed SA InstrumentedList object to SA's
relation() function that will be used to create the object in
instances.

This approach seems more reasonable then having to use different
method names for the same functionality depending on the 'prefix', ie:
fred.add_tag('manager')
fred.author_add_tag('reliable')
etc.

By being able to pass in a subclassed object to SA, I could have:
fred.tags.add_tag('manager')
fred.author_tags.add_tag('reliable')

Keeping consistent methods for manipulating the tags is preferable to
having to generate prefixed function names (which reminds me way too
much of PHP in all the function prefixes).

How's that sound?

- Ben

Jonathan LaCour

unread,
May 29, 2007, 2:23:17 PM5/29/07
to sqle...@googlegroups.com
Ben Bangert wrote:

> Ok, sounds good. So to keep it focused, and the API style consistent
> for now, I can see adding a option to the acts_as_taggable there like
> so:
>
> class Person(object):
> has_field('name', Unicode)
> acts_as_taggable(name='author_tags', backref='people')
> acts_as_taggable()
>
> The default would use 'tags', while the name can provide an optional
> setup. So ignoring the API isues, I still need a way to put the
> additional functions underneath the 'tags' name in the Person entity.
>

> [snip, snip]
>
> How's that sound?

Well, that wasn't hard at all ;) This is a nice start!

However, after I sent my first email, and before I got your second, I
was in the midst of typing up another response to your idea. Here is
something to consider, quoting my unsent message:

> Having a "tags" attribute provides a sort of namespacing, so you
> aren't shoving random methods onto the decorated class, unlike a
> traditional DSL. The nice thing about this is that it might be easier
> to understand what is happening at first glance, and it might even be
> easier to document.
>
> One downside to the approach is that what looks like a collection all
> of the sudden has a bunch of methods that, at some level, belong on
> the decorated class. When you say "add a tag to a person", it seems
> like you should spell that like this:
>
> myPerson.add_tag('some_tag')
>
> This line of code clearly reads "add a tag to my person," whereas this
> line of code:
>
> myPerson.tags.add('some_tag')
>
> ... reads more like "add a tag to the tag collection on my person,"
> which is fine, but this begins to really start to fall apart when we
> start growing other capabilities, like tag clouds:
>
> # ben's way
> myPerson.tags.cloud()
>
> # more traditional elixir approach
> myPerson.get_tag_cloud()
>
> At some fundamental level, I find the second version more readable. I
> think it might bother some people that a statement is adding methods
> to a class, which is why namespacing may be appealing. But, to
> me, a DSL statement is doing exactly that: adding cross-cutting
> functionality to my class that I didn't want to write myself.
>
> In many ways, its similar to what inheritance does. You could just as
> easily write an Elixir plugin like this:
>
> class Person(Entity, Taggable):
> has_field('name')
>
> No one would ask where the "add_tag" method on Person came from, or
> ask for a namespace, because they are used to inheritance. Part of me
> suspects that this namespacing benefit is not really a benefit, its
> just something that you might want if you aren't used to things like
> class decorators :)

This still applies to your suggested `acts_as_taggable` modification
above. What if you took a slightly different approach that mitigated
the issues above by having you explicitly state what set of tags you
were working with:

class Person(object):
has_field('name', Unicode)

acts_as_taggable(category='author', backref='people')
acts_as_taggable()

The default "category" would be "tags". Then, you could do this:

my_person.add_tag(category='author', 'SomeTag')
my_person.add_tag('SomeOtherTag')

assert 'SomeTag' in my_person.author_tags
assert 'SomeOtherTag' in my_person.tags

my_person.get_tag_cloud(tag_type='author')

What do you think of this?

Ben Bangert

unread,
May 29, 2007, 2:33:38 PM5/29/07
to SQLElixir
On May 29, 11:23 am, Jonathan LaCour <jonathan-li...@cleverdevil.org>
wrote:

> This still applies to your suggested `acts_as_taggable` modification
> above. What if you took a slightly different approach that mitigated
> the issues above by having you explicitly state what set of tags you
> were working with:
>
> class Person(object):
> has_field('name', Unicode)
> acts_as_taggable(category='author', backref='people')
> acts_as_taggable()
>
> The default "category" would be "tags". Then, you could do this:
>
> my_person.add_tag(category='author', 'SomeTag')
> my_person.add_tag('SomeOtherTag')
>
> assert 'SomeTag' in my_person.author_tags
> assert 'SomeOtherTag' in my_person.tags
>
> my_person.get_tag_cloud(tag_type='author')
>
> What do you think of this?

That works I suppose, though there's some inconsistencies in there, ie
I think it should be:
my_person.get_tag_cloud(category='author') # since we named it
category earlier

But I like how it avoids additional add_tag functions with name
prefixes, so that works for me... for now with the current API. :)

I'll put together a package for the statements that I make (including
this taggable one), and look at making some Elixir changes to support
Statement hooks for table/property creation.

Cheers,
Ben

Jonathan LaCour

unread,
May 29, 2007, 2:36:41 PM5/29/07
to sqle...@googlegroups.com
Ben Bangert wrote:

> That works I suppose, though there's some inconsistencies in there, ie
> I think it should be:
> my_person.get_tag_cloud(category='author') # since we named it
> category earlier
>
> But I like how it avoids additional add_tag functions with name
> prefixes, so that works for me... for now with the current API. :)

Yes, I agree, I changed my mind halfway through the email, and forgot
to go back and change the "tag_type" to "category" everywhere :)

> I'll put together a package for the statements that I make (including
> this taggable one), and look at making some Elixir changes to support
> Statement hooks for table/property creation.

Excellent. I look forward to it!

If a few more plugins get developed, I think we should go ahead and
create a place in our SVN repository and on our website where plugins
can live and be documented.

Ben Bangert

unread,
May 31, 2007, 5:26:38 PM5/31/07
to SQLElixir
On May 29, 11:36 am, Jonathan LaCour <jonathan-li...@cleverdevil.org>
wrote:

> Excellent. I look forward to it!
>
> If a few more plugins get developed, I think we should go ahead and
> create a place in our SVN repository and on our website where plugins
> can live and be documented.

I've added the elixir.ext package to Elixir, with the associable
module. Here's a quick link:
http://elixir.ematia.de/svn/elixir/trunk/elixir/ext/associable.py

The ideas for that were inspired by Mike Bayers post on polymorphic
associations here:
http://techspot.zzzeek.org/?p=13

This one-ups the acts_as_taggable I was working on, and makes it more
generic allowing one to easily setup generic polymorphic associations.
These minimize the amount of many-to-many tables in databases with
many entities joined to one common one (tags, comments, addresses,
etc.).

I'll prolly add a taggable that uses the associable() function, to add
some tag-specific options like tag cloud generators and the such.

Cheers,
Ben

Gaetan de Menten

unread,
Jun 1, 2007, 11:42:21 AM6/1/07
to sqle...@googlegroups.com
It guess it's about time I reacted on this thread...

On 5/31/07, Ben Bangert <gas...@gmail.com> wrote:
>
> On May 29, 11:36 am, Jonathan LaCour <jonathan-li...@cleverdevil.org>
> wrote:
> > Excellent. I look forward to it!
> >
> > If a few more plugins get developed, I think we should go ahead and
> > create a place in our SVN repository and on our website where plugins
> > can live and be documented.
>
> I've added the elixir.ext package to Elixir, with the associable
> module. Here's a quick link:
> http://elixir.ematia.de/svn/elixir/trunk/elixir/ext/associable.py

Great work. I think that once this is finalized, it'll deserve a place
in the "main" part, not in an extension.

But as I'm pretty picky, I got some issues with the current code (some
of them are true for Michael Bayer's code too):
- Wouldn't it be possible/cleaner to implement your Associable class
as inheriting from Relationship (or maybe even one of the existing
concrete relationship class)? This is also true for your taggable_v4
code, which is just a specialized version of has_and_belongs_to_many,
and as such I *think*, you'd be better off inheriting from it.

- Why do you need the association_to_table? Isn't what Michael Bayer
did already able to represent a many_to_many relationship?
Theoretically, the intermediary table can hold that information. I'm
not sure about the code itself, though.

- An entity can't be the target of several polymorphic relationships
(it is associable only once).

- I find the syntax is quite backward. I'd much prefer something more
similar to ActiveRecord. Ok, I know the implementation method differs
and that it's better this way because of the foreign key stuff but
wouldn't it be possible to do a syntax similar to AR with an
implementation similar to SA?

- You can only "polymorphically" link to an entity which has a backref
to our source. Sometimes you don't care/want to define that inverse
relationship. It would be nice to be able to do without.

- As far as I understand, Michael's code only handle the case where
the "_to_one" (belongs_to) part of the relationship is polymorphic,
I'd like to be able to define _to_many (has_many) relationships too in
a coherent way. Sorry, I didn't look at your code carefully, so I
don't know whether you handle that case or not.

> The ideas for that were inspired by Mike Bayers post on polymorphic
> associations here:
> http://techspot.zzzeek.org/?p=13
>
> This one-ups the acts_as_taggable I was working on, and makes it more
> generic allowing one to easily setup generic polymorphic associations.
> These minimize the amount of many-to-many tables in databases with
> many entities joined to one common one (tags, comments, addresses,
> etc.).
>
> I'll prolly add a taggable that uses the associable() function, to add
> some tag-specific options like tag cloud generators and the such.

That's exactly what I was about to suggest as a reply to your initial
message ;-).

--
Gaëtan de Menten
http://openhex.org

Gaetan de Menten

unread,
Jun 1, 2007, 11:50:50 AM6/1/07
to sqle...@googlegroups.com

It would indeed require to patch SQLAlchemy. For the record, the
current situation is that, as soon as you access the attribute name,
the underlying query is triggered. A while ago I made a
patch/suggested on SQLAlchemy's list to defer the trigger until the
list elements are actually accessed. Worked fine for the simple cases
but broke some people code. I think it'll probably be a good idea to
resurrect that patch but make it optional.

Gaetan de Menten

unread,
Jun 1, 2007, 12:05:19 PM6/1/07
to sqle...@googlegroups.com
On 5/29/07, Ben Bangert <gas...@gmail.com> wrote:
>

Please don't do that part without discussing it here first. Or do this
in a branch. I have quite a lot of uncommited code in my local copy (I
know it's a really bad habit but well...) which will probably break
your stuff when/if I commit it, so let's discuss this first so that
nobody get hurt :).

Ben Bangert

unread,
Jun 1, 2007, 1:26:29 PM6/1/07
to SQLElixir
On Jun 1, 8:42 am, "Gaetan de Menten" <gdemen...@gmail.com> wrote:
> But as I'm pretty picky, I got some issues with the current code (some
> of them are true for Michael Bayer's code too):
> - Wouldn't it be possible/cleaner to implement your Associable class
> as inheriting from Relationship (or maybe even one of the existing
> concrete relationship class)? This is also true for your taggable_v4
> code, which is just a specialized version of has_and_belongs_to_many,
> and as such I *think*, you'd be better off inheriting from it.

Not sure, I hadn't really looked at inheriting a relationship, nor was
I sure how to handle the rather specialized relationship this was
setting up. I'll take another look at the Relationship class.

> - Why do you need the association_to_table? Isn't what Michael Bayer
> did already able to represent a many_to_many relationship?
> Theoretically, the intermediary table can hold that information. I'm
> not sure about the code itself, though.

Nope, the association_to_table is there for referential integrity with
the foreign key constraints. The example lacking this table on his
blog entry does not have the referential integrity the additional m2m
table brings. The assoc_to_table comes from this example from Mike on
a table layout with the referential integrity enforced by foreign key
constraints:
http://pylonshq.com/pasties/301

> - An entity can't be the target of several polymorphic relationships
> (it is associable only once).

That's true, though it should be fairly easy to modify it so that it
can be used in multiple one.

> - I find the syntax is quite backward. I'd much prefer something more
> similar to ActiveRecord. Ok, I know the implementation method differs
> and that it's better this way because of the foreign key stuff but
> wouldn't it be possible to do a syntax similar to AR with an
> implementation similar to SA?

I'm not sure how, as AR has no equivilant really to Elixir Statements,
so I'm not sure how we could make a Elixir Statement generator similar
to AR.... given that AR has nothing that compares afaik. Do they have
AR acts_as plug-in generators?

While I can see that Elixir tries to look in some ways like AR, surely
there's no need to hobble it unnecessarily in an attempt to look like
the rather primitive ORM that AR provides?

> - You can only "polymorphically" link to an entity which has a backref
> to our source. Sometimes you don't care/want to define that inverse
> relationship. It would be nice to be able to do without.

I don't really understand what you mean by this. The associable Entity
has no additional relationships defined on it, and merely has the
assoc_ table ref's attached should someone need them for a lower level
SA sql statement.

> - As far as I understand, Michael's code only handle the case where
> the "_to_one" (belongs_to) part of the relationship is polymorphic,
> I'd like to be able to define _to_many (has_many) relationships too in
> a coherent way. Sorry, I didn't look at your code carefully, so I
> don't know whether you handle that case or not.

I'm not sure what you mean by this either. Something that is
associated with the polymorphic association, can have multiple ones
related to it. Ie. a Person can have multiple addresses, an Order can
have an address, etc. and they all use the polymorphic association.

> > I'll prolly add a taggable that uses the associable() function, to add
> > some tag-specific options like tag cloud generators and the such.
>
> That's exactly what I was about to suggest as a reply to your initial
> message ;-).

Hopefully I'll get that in over the weekend.

Cheers,
Ben

Reply all
Reply to author
Forward
0 new messages