|
From: Brad B. <br...@bb...> - 2003-08-11 02:12:23
|
On Sunday, August 10, 2003, at 06:57 AM, Magnus Lyck=E5 wrote:
> At 19:13 2003-08-05 -0400, Brad Bollenbach wrote:
>
>> On Tuesday, August 5, 2003, at 06:59 PM, Magnus Lyck=E5 wrote:
>>
>>> At 14:18 2003-07-24 -0400, Brad Bollenbach wrote:
>>>> > Well, if there's going to be composite keys, that might as well =
be
>>>> > generalized to all the columns (i.e., any attribute could be a=20
>>>> composite
>>>> > of several columns).
>>>>
>>>> What's the use case for this?
>>>
>>> For instance money objects that consist of an amount and a currency.
>>> You could make them full objects with an identifier and do:
>>>
>>> order_line
>>> |id |order_id|spec |money_id|
>>> +---+--------+--------------+--------+
>>> | 1| 123|Dog collar | 1|
>>> | 2| 123|Dog food | 2|
>>>
>>> money
>>> |id |amt |currency|
>>> +---+-------+--------+
>>> | 1|12.50 |EUR |
>>> | 2|0.34 |USD |
>>
>> This is bad database design. amt and currency belong in the=20
>> order_line table.
>
> I think so too! That's why the propsed feature is good.
>
>>> But is seems simpler to just do:
>>>
>>> order_line
>>> |id |order_id|spec |price_amt|price_currency|
>>> +---+--------+--------------+---------+--------------+
>>> | 1| 123|Dog collar |12.50 |EUR |
>>> | 2| 123|Dog food |0.34 |USD |
>>>
>>>
>>> In this case you could say that the price is a value from a
>>> design point of view, but an object from a programming point
>>> of view. Martin Fowler calls such objects "Value Objects" in
>>> the book "Patterns of Enterprise Application Architechture".
>>
>> I don't follow your point. At what point do Python properties stop=20
>> being useable here, and composite columns (for something other than=20=
>> the PK) become necessary?
>
> If I have a Money class, my Python class OrderLine would have
> an attribute "price", which was an instance of the Money class.
> It should *not* have a priceAmount attribute and a separate
> priceCurrency attribute. The internal composition of the price
> is not the concern of the OrderLine class!
>
> Thus, we get a mismatch between Python classes and RDBMS tables.
To reiterate, this can already be solved using properties.
> =46rom an OO Design point of view, you separate your data into
> two categories: Objects and values. An object is something that
> has an identity on its own, like a person, a window, or an
> order line. A value is some data which just describes some
> property of another object, but doesn't have its own identity,
> such as a length, a name, or a monetary amount.
>
> In Python, it's very common that design objects are instances
> of a class, and design values have another type than instance,
> such as long, float or string, but that's not always the case.
>
> So, while the idea that a class in Python corresponds to a
> table in SQL fits the bill in most cases, this is not always
> the case. There can be mismatches in both directions.
Not in SQLObject there can't :) Each SQLObject-derived class=20
corresponds to one database table, though, naturally not necessarily=20
every property of your SQLObject need represent exactly one column.
> If you have simple non-scalar objects like dates with time-zones,
> money objects with a currency, measurements with a unit and a
> given precision etc, you would probably want to "inline" these
> values into your main table, rather than to assign some kind
> of made up identity to these values and store them in a separate
> table. You still implement these "value objects" as instances of
> their own class in Python though.
I disagree here. This is a use case for properties, not for "composite=20=
columns".
> The other way around, you can have a list of integers as a simple
> attribute in a Python class, and then you want to go the other
> way around, and split your Python class into more than one table.
>
> It would be nice if SQLObject handles both these deviations from
> the typical 1 class =3D=3D 1 table situation in a convenient way.
>
> E.g. Python classes that have attributes like below...
>
> class Money
> amt =3D int()
> currency =3D str()
>
> class OrderLine
> id =3D oid()
> order_id =3D oid from Order()
> spec =3D str()
> price =3D Money()
>
> ...should be stored in *one* (not two) tables as we seem to
> agree on above, with as little tweaking as possible.
This design seems rather odd to me. It would probably make more sense=20
to do it like:
class Currency(SQLObject):
currency_code =3D StringCol(length =3D 3, notNone =3D True)
order_lines =3D MultipleJoin('OrderLine')
class OrderLine(SQLObject):
...
amount =3D CurrencyCol(notNone =3D True)
currency =3D ForiegnKey('Currency')
At which point the need for composite columns in this case evaporates.=20=
I've handled a multi-currency credit card payment processing system in=20=
precisely this way with SQLObject.
But -- to give another example (in addition to one I posted earlier in=20=
this thread) -- let's say you had a burning desire to have a "composite=20=
column" for transaction fees on credit cards.
So:
class TransactionFee(SQLObject):
card_type =3D ForeignKey('CardType')
tx_fee =3D CurrencyCol()
commission_percentage =3D FloatCol()
Now, each purchase transaction *has* to record the tx fee and=20
commission percentage in its row, because the fees are subject to=20
change anytime, but we want to ensure we're only charging the customer=20=
exactly the rate they were quoted at the time the sale was made. In=20
this case, it's a bit tedious to manually assign to tx_fee and=20
commission_percentage in PurchaseTransaction every time, so we'll=20
create a composite by using Python properties:
class PurchaseTransaction(SQLObject):
...
card_type =3D ForeignKey('CardType')
tx_fee =3D CurrencyCol()
commission_percentage =3D FloatCol()
def _set_transaction_charge(self, value):
self.tx_fee =3D value.tx_fee
self.commission_percentage =3D value.commission_percentage
transaction_charge =3D property(None, _set_transaction_charge)
In this particular case, I've left out the getter, because at any given=20=
moment in time the tx_fee/com % combo in a purchase transaction doesn't=20=
necessarily currently exist in the transaction_fee table, because the=20
admin may have since adjusted the rates for that card.
The nice thing about using Python properties instead of trying to build=20=
something into SQLObject here is that:
1. We use a feature of Python for one of its intended use cases,=20
instead of reinventing the wheel in SQLObject.
2. We document the class better, because I don't have to read the=20
implementation of another class to figure out what columns I have (e.g.=20=
when I read my class definition, and I want to know "how do I sum up=20
all the transaction fees for this month for all of the transactions?" I=20=
wouldn't be able to answer this if I had wrapped tx_fee and=20
commission_percentage up into some external class and assigned that to=20=
a "composite column").
Explicit is better than implicit, as is using a feature that Python=20
already has, rather than reinventing it in SQLObject.
--
Brad Bollenbach
BBnet.ca=
|