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