There are a couple problems here, and a few solutions to them, as
well. I’ll shamelessly plug Criteria here. Make sure you use the SVN
version:
http://svn.mephle.org/svn/mephle/criteria/
That will help you combine your queries; I do this regularly:
class Customer
# This is the table, you probably want to use a DBITable though.
IDX = SQLTable.new(“my_table”, …)
CUSTOMER_NAME = (IDX.name == IDX.x0?) # .x0? is a placeholder
CUSTOMER_BEFORE = (IDX.date < IDX.x0?)
# :
# : more queries
# :
end
Later on we can combine them:
query = CUSTOMER_NAME[“Bob”] & CUSTOMER_BEFORE[1975]
Criteria is pretty slick. As an exercise in comparison, here’s how I’d do
it using Kansas:
ksdbh.table(‘Customer’,‘customer’) # args are classname, tablename
Then to do the query:
customers = ksdbh.select(:Customer) do |customer|
(customer.name == ‘Bob’) && (customer.date < 1975)
end
You get the idea. However this is only one of your problems; the
other is the fact it’s very slow to do a query everytime you want an
attribute, either getting or setting. For this I would recommend
“caching” things; make an attr_sql_accessor or the like which (perhaps
using Criteria… I do this too) makes note of when the value changes,
but only updates the database on a flush, and/or tie in transactions
(especially nice with PgSQL’s MVCC). This should keep you from having
3 lines like this:
customer.name = “Larry”
customer.date = 1998
customer.address = “…”
…generate 3 UPDATE statements, but only one instead. The only thing
you need to watch out for is making sure someone doesn’t update the
database behind you; for this I’m not sure what you can do. (Maybe
some magic with stored ruby procedures and triggers… dunno.)
This is trickier. The direction that I am taking with Kansas is to have a
level of transaction support.
ksdbh.transaction do
customer.name = ‘Larry’
customer.date = 1998
customer.address = ‘123 Trelona Circle’
blah blah some other timeconsuming stuff
ksdbh.commit
end
At the moment this is only partialy implemented, but what should be
happening is that Kansas takes care of initiating a transaction at the
database level to keep things from changing underneath us at that level.
It applies the attribute changes to the object, but wait until the commit
to roll them up and apply them all at once down at the database level
before issuing an echoing commit() on the database. For DBI backends that
don’t support transactions, one will still have the transactional support,
including the ability to rollback changes to the objects, at the Ruby
level, and one would still have the rolling up of multiple updates into a
single update.
This doesn’t address the problems of the database data changing underneath
the object and the object knowing nothing of it. The only alternatives
that I see are doing something with triggers or making each object so that
it can refresh itself to make sure it is in sync with the db.
customer.refresh
or something like that. And if there is a high likelyhood of the db
changing under and object, it might be worth the performance cost to have
something like:
customer.autorefresh
which would tell the object to check itself against the db before any
attribute is accessed. That could be a huge performance hit, though, and
could itself introduce problems. Tricky.
Kirk Haines
···
On Wed, 21 Apr 2004, Ryan Pavlik wrote: