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.
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:
> 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 ;)
> 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).
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:
> 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:
> 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:
> 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.
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.
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.
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.
It guess it's about time I reacted on this thread...
On 5/31/07, Ben Bangert <gasp...@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.
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.
> 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 ;-).
> 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:
> 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.
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. -- Gaėtan de Menten http://openhex.org
On 5/29/07, Ben Bangert <gasp...@gmail.com> wrote:
> On May 29, 11:23 am, Jonathan LaCour <jonathan-li...@cleverdevil.org> > wrote: > and look at making some Elixir changes to support > Statement hooks for table/property creation.
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 :).
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 ;-).