It is helpful to know what SQLA was designed for.

Also, you may be interested to know of our project as we are apparently stretching SQLA's use case/design. We are implementing a RESTful web app (using TurboGears) on an already existent legacy database. Since our webservice calls (and TurboGears' in general, I believe) are completely state-less, I think that precludes my being able to harness "descriptors, custom collections, and/or AttributeListeners, and to trigger the desired calculations before things change" because I'm just handed the 'new' version of the object. This is precisely why so much of my attention has been on merge() because it, for the most part, works that out magically.

In this case, though, it would be nice to have merge() package up some extra information, namely the object as it looks in the database prior to its magic.

Thanks again.


On 4/29/2010 3:47 PM, Michael Bayer wrote:
Kent wrote:
There might be a communication problem, which an example could help
clarify.

I'm constrained by a legacy database that I have no control over
changing.  When an order is changed, I need to calculate the change in
volume (or points) so I can update a table that records this
information.

Here is an example of a method you might want to run on an the
original, unchanged object (Order, in this case):
=================================================================
def calc_points(self):
     return sum(l.product.points * l.qtyordered for l in
self.orderdetails if l.product.points is not None)
=================================================================

Notice that this calculation relies on several relations:
orderdetails, and orderdetails[].product

 From an MVC view point, my argument is that certainly this business
logic *should* reside in the Order class.  That is to say, "an Order
should know how to calculate its own volume".

Unfortunately, this is not a matter of two or three *fields* that I
can easily extract from attributes.get_history().  This computation,
again, relies on several relations.

Any further insight or advice?
the general idea to this type of thing is to listen for events using
descriptors, custom collections, and/or AttributeListeners, and to trigger
the desired calculations before things change.  SQLAlchemy internals are
only intended to implement as much as is needed to persist changes to the
database.

Such as here, if you need to run "calc_points()" everytime the contents of
the "orderdetails" collection changes, a simple AttributeListener
implementing "append"/"remove" can achieve that.  But also, a custom list
class which references the parent and overrides append() and __setitem__()
would do it to, i.e. its not like any of this would be impossible if you
weren't using an ORM.   The ORM's attribute logic is designed only for the
purposes of persisting changes to a database.   That we expose some of its
details for the purpose of business logic is only intended to be to the
degree that this business logic is explicitly concerned with the details
of the object's persistence.   Things that aren't related to the
mechanisms of persistence aren't within the domain of SQLA and can be
implemented using regular Python techniques.










On Apr 29, 12:57 pm, "Michael Bayer"<mike...@zzzcomputing.com>  wrote:
if you have a method like 'calculate_total_volume()', that is a business
method.  you should not be relying upon the internals of the ORM to
figure
that out for you, and you should have two distinct fields on your object
to represent the two values you need to make that calculation.



Kent wrote:
That sounds like it could be very useful for me, thank you for
pointing me there.  That could solve one of the two issues I'm facing
that I listed... what about the other?
On Apr 29, 11:02 am, "Michael Bayer"<mike...@zzzcomputing.com>  wrote:
Kent wrote:
Before saving objects to the database, we have need to inspect the
changes.  I am aware of the attributes.get_history() functionality,
which is helpful to a point.
attributes.get_history() falls short of what I need in two places:
*** after a session.flush(), it is gone.  There are times we need
to
flush, but the transaction has still not been committed, and more
processing is required, so we still need access to the history of
changes for this object in this transaction despite a call to
flush().
*** if there are calculation methods I've written that objects
have, I
need to be able to call these for the "old" object.  For example,
say
I write a method on Order class such as "def
calculate_total_volume(self):"  When an order is saved I need to
compare the old volume to the new.  On the merged order, I can call
"merged_obj.calculate_total_volume()", but I would need to use the
information in attributes.get_history() to *recreate* the "old"
version of the object in order to call
"old_order.calculate_total_volume()"
I've realized that if merge() attached a python attribute, such as
"_original" (which is a transient clone of the object fetched from
the
database) to each object as it merges it, then this
* would be accessible regardless of session.flush()
* could be acted on with object methods like any other object
* is probably more intuitive than "attributes.get_history()",
especially to those familiar with Oracle triggers and :OLD.orderid
vs :NEW.orderid.  This would be very similar:   "merged.orderid" vs
"merged._original.orderid"
Disadvantage:
* it goes against the concept of trying to be very hands-off on the
actual object.  Maybe it could instead be stored in the instance's
state or something.
Does this conceptually make sense?  Does it make enough sense to
make
this a feature?
In the meantime, can you recommend an approach for me?  (Extend the
Session class?)
you want to be using a SessionExtension here, and the "after_flush()"
hook
is provided precisely for the use case that you want rules to execute
after SQL has been flushed, but you still want a full accounting of
everything that has changed - the attribute history on all objects
has
not
yet been reset at this point.   The information available in
after_flush()
is unique in that it also includes any foreign key values that have
been
synchronized from parent to child over relationships.
Thanks much,
Kent
--
You received this message because you are subscribed to the Google
Groups
"sqlalchemy" group.
To post to this group, send email to sqlalch...@googlegroups.com.
To unsubscribe from this group, send email to
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at
http://groups.google.com/group/sqlalchemy?hl=en.
--
You received this message because you are subscribed to the Google
Groups "sqlalchemy" group.
To post to this group, send email to sqlalch...@googlegroups.com.
To unsubscribe from this group, send email to
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group
athttp://groups.google.com/group/sqlalchemy?hl=en.
--
You received this message because you are subscribed to the Google
Groups
"sqlalchemy" group.
To post to this group, send email to sqlalch...@googlegroups.com.
To unsubscribe from this group, send email to
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at
http://groups.google.com/group/sqlalchemy?hl=en.
--
You received this message because you are subscribed to the Google
Groups "sqlalchemy" group.
To post to this group, send email to sqlalch...@googlegroups.com.
To unsubscribe from this group, send email to
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group
athttp://groups.google.com/group/sqlalchemy?hl=en.
--
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To post to this group, send email to sqlalch...@googlegroups.com.
To unsubscribe from this group, send email to
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at
http://groups.google.com/group/sqlalchemy?hl=en.



--
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to sqlalch...@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.

Reply via email to