ANN: ContextualService 0.1.0

S

sera

Here's another library that comes from Lafcadio: ContextualService.

The ContextualService library makes it easy to manage and set services
to a single, global resource such as a database or file system. By
defining a service to be a child of ContextualService::Service, you
* lock off access to instantiation, so all access have to go through
the service's get method,
* and provide a simple way to set the instance, most likely for
testing.

For example, Lafcadio ( http://lafcadio.rubyforge.org ) uses
ContextualService to manage access to the ObjectStore, which controls
access to a database.

class Lafcadio::ObjectStore < ContextualService::Service
def initialize
...
end
end

ObjectStore.new will raise an error. To get an instance of ObjectStore,
you call ObjectStore.get_object_store. If no instance has been
previously set, this will instantiate the ObjectStore and save the
instance for future accesses. If a mock instance has been previously
set
with set_object_store, it will simply return that mock instance.

Since access to the global service is attached to the class, there's no
need to pass in a mock service as an argument to any method that needs
it. This can simplify testing considerably if you've got highly
decomposed code that needs to access a global service from many places.
Also, since the Context object is hidden, clients of the service don't
have to be conscious of it.

def some_method
os = Lafcadio::ObjectStore.get_object_store
# do something with the object store here
some_other_method
end

def some_other_method
os = Lafcadio::ObjectStore.get_object_store
# do something with the object store here, too
end

class TestSomeMethod < Test::Unit::TestCase
def test1
mock_object_store = Lafcadio::MockObjectStore.new
Lafcadio::ObjectStore.set_object_store mock_object_store
some_method
# assert something happened
end
end

The project page can be found at
http://rubyforge.org/projects/contxtlservice .
 
S

sera

Sure, though I should point out, in case there's any confusion, that
ContextualService doesn't require Lafcadio. Lafcadio requires
ContextualService, not the other way around.

I haven't used ActiveRecord myself, but I've had lots of discussions
with Rails users that have informed my undertstanding of how AR works.
Any corrections to what I say below are welcome.

There are a handful of significant differences between AR and Lafcadio.
First off is that Lafcadio currently only works with MySQL, because
that's the DB I use and I haven't needed to extend it to anything else.

Second, Lafcadio works harder at making applications testable in
memory. For example, it includes a MockObjectStore which recreates a
database entirely in memory, so that tests of DB-dependent logic can
take place in memory, which will be significantly faster than tests
which have to dump tables, recreate tables, fill rows, etc. I use
Lafcadio at Rhizome.org, the website which is my day job. It has 500
unit tests, which run in about 120 seconds. If they hit a staging
database, I imagine they would take five times as long.

It's my belief that if your tests are slow, you'll run them less often,
and write less of them. And programmers don't need any excuses to do
less testing.

This decision has a number of side-effects in the design. For one
thing, you can't pass in SQL in advanced queries, because then you'd
have to write a SQL parser to handle those queries with the
MockObjectStore. So Lafcadio comes with a light query-inference
language, inspired by Criteria but intended to be simpler than
Criteria. (I find some of Criteria to be sort of scary voodoo, myself.)

So, where in AR you might write:

next_angle = Company.find_first "name = 'Next Angle'"

In Lafcadio you'd write

next_angle = object_store.get_companies { |c|
c.name.equals( 'Next Angle' )
}.first

.... which is more typing than in AR, but it works well in two contexts:
against a real database during a production run, or against a mock
database running in memory during a test.

Query inference has a few other cool applications. For one thing,
Lafcadio caches those query results in memory, and can do some simple
calculations to see if one query is a subset of a previous one. So, if
you run these three queries:

object_store.get_users { |u| u.lastname.equals( 'Smith' ) }
object_store.get_users { |u|
Query.And( u.lastname.equals( 'Smith' ), u.fname.like( /John/ ) )
}
object_store.get_users { |u|
Query.And( u.lastname.equals( 'Smith' ), u.email.like( /hotmail/ ) )
}

Lafcadio only hits the database the first time, and resolves the 2nd
and 3rd queries in memory.

Hope that answers your question,

f.
 
J

Jeremy Kemper

So, where in AR you might write:

next_angle = Company.find_first "name = 'Next Angle'"

The dynamic finder methods are more easily mockable. E.g.
Company.find_by_name 'Next Angle'

I run my unit tests against an in-memory SQLite database rather than
mock everything; I wish that could be improved. Tests run in under
10 sec, but that's still too long.
Query inference has a few other cool applications. For one thing,
Lafcadio caches those query results in memory, and can do some simple
calculations to see if one query is a subset of a previous one. So, if
you run these three queries:

Cool! This assumes transactions are serialized (no dirty reads); can
you detect whether caching is safe?

Best,
jeremy
 
S

sera

Jeremy said:
Cool! This assumes transactions are serialized (no dirty reads); can
you detect whether caching is safe?

No, Lafcadio doesn't support transactions, since I haven't worked on
any app where I feel it's necessary. (I would not currently recommend
that you use Lafcadio to handle, say, financial trading or medical
records.) Its understanding of caching and transactions is fairly
simple-minded, and I don't imagine it's thread-safe, either. (I don't
use concurrency, either.)

It seems there are some regular Lafcadio users besides myself, but I
still largely see it as a project driven by my own ORM needs. In
particular, I'm reluctant to introduce features that I myself never
use, because I can't then guarantee if that feature is any good.
However, I'm open to adding features such as transaction support or
Postgres support if I can be assured that somebody is going to be
testing it out on real code.

f.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,175
Messages
2,570,942
Members
47,490
Latest member
Finplus

Latest Threads

Top