fREWdiculous!
18 Jun
Recently (6 monthsish ago) I decided on an ORM to use at $work. It was pretty hard to make a decision because I’d never really used an ORM for a significant amount of time. Now that I am pretty confident with my chosen ORM I feel like I can make a more informed comparison.
I’m going to skip over the basics of declaring classes themselves. Often when researching ORM’s this is the main thing that people look at. Unfortunately it’s (in my opinion) not that important. As long as everything you want to do is supported, the base model class should just stay out of your way. Recently there have been complaints about the aesthetic appeal of things like DBIC. I prefer to look at conceptual beauty rather than syntactic.
I’d rather focus on major differences in underlying structure, and more importantly, how searches work. I do a lot more searches than I do anything else, and I’d bet that’s the same for you, after you do more than just the basic structure of your app.
So without further ado, the contenders.
26 releases in 5 years. The most recent is from 2007.
2 authors, 33 credited.
The oldest of the discussed ORMs.
Searches are extremely simple, being limited to == and LIKE queries. Here’s an example:
1 2 3 4 5 | # == @cds = Music::CD->search(title => "Greatest Hits", year => 1990); # LIKE @cds = Music::CD->search_like(title => 'Hits%', artist => 'Various%'); |
You can do more complex things, but it’s really just writing SQL and giving that SQL search a name. A SQL dictionary approach you might say. Also note that the above returns an entire array of results, which is Not Great. You can use an iterator though for performance….but it still pulls it all into memory; it just doesn’t instantiate the objects right away, which is a little better, but still Bad.
A cool feature CDBI has is triggers for the lifecycle of the object. The triggers listed in the docs are:
CDBI also has built in constraints, so you can do validation in your model.
Both of these can be done with regular OO in all of the other ORM’s, but having a predefined naming scheme for things like this helps people to quickly learn what’s going on.
70 releases in 3 years. The most recent is two months ago.
1 author, 11 credited.
Written with speed in mind. Be aware that because of these manual optimizations the code is harder to maintain. I was told this by one of the contributors to the project. But you do get very good speed (supposedly, I haven’t done any tests myself) because of it.
Here’s a basic Rose::DB::Object search. It returns an arrayref, which isn’t optimal, but you can get an iterator just as easily.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $products = Product::Manager->get_products( query => [ name => { like => '%Hat' }, id => { ge => 7 }, or => [ price => 15.00, price => { lt => 10.00 }, ], ], sort_by => 'name', limit => 10, offset => 50); |
From my perusing of the docs it seems that Rose basically has all of the perl data-structure based searches that one would hope for in an ORM that abstracts away most SQL.
62 releases in almost four years. Most recent being days old.
One “author,” 69 credited.
It is very much made for the convenience of the programmer. The .09 series will be Moose-based. See slides for proof.
Here’s an example of a relatively complex search with DBIx::Class.
1 2 3 4 5 6 7 8 9 10 11 | my $results = $schema->resultset('Artist')->search({ first_name => 'frew', last_name => [ # arrayrefs mean or by default { -like => 'schmi%' }, { -like => 'stat%' }, ] },{ page => 2, rows => 25, order_by => { -desc => [qw/last_name first_name/] } }); |
What I think is really great about DBIx::Class is the fact that you can chain searches. I really dig this feature. It lets me do things like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | $rs = $schema->resultset('Artist')->search({ first_name => $self->query->param('name'), 'friend.height' => 6*12+1, },{ join => 'friend' }); # imagine this is in another method (because it is) $rs = $rs->search(undef, { page => $self->query->param('page'), rows => $self->query->param('rows'), }); # imagine this is in another method (because it *also* is) $rs = $rs->search(undef, { order_by => { q{-}.$self->query->param('direction') => $self->query->param('sort') } }); # and maybe some permissions stuff $rs = $rs->search({ current_user => $self->user }); |
And most importantly, this is a single SQL query, not four.
I also need to mention that the DBIx::Class people have really helped me help them, which is not just a good feeling, I use the features I’ve added in production code. I can say for certain that working on their codebase has made me a better OO programmer.
22 releases in just over a year.
One author.
This is the newest of the four ORM’s reviewed here. Cut it some slack if it doesn’t have all the features that the other ones have.
Fey::ORM is made for people who are alright with actually writing SQL. It’s very … OO-y. For example:
1 2 3 4 | $select->select( $message_t, $user_t ) ->from( $message_t, $user_t ) ->where( $message_t->column('message_date'), '>=', DateTime->today()->subtract( days => 7 )->strftime( '%Y-%m-%d' ) ); |
What I think is really cool about Fey::ORM is that it has a standard method for creating relationships other than the usual has_one/has_many/many_to_many. Who wouldn’t want to be able to make relationships based on something other than equality? (Seriously though, I’d be able to use that at $work.)
So that’s what I gathered from a couple of hours of reading docs on CPAN and a few months of DBIC usage. I really like DBIx::Class, but I can see why people would choose some of the other ORMs. It does seem to me that the only reason to use Class::DBI is that you already have a giant codebase written on top of it. But if that’s the case, you could just use DBIx::Class’s compatibility layer…
Anyway, hope this was helpful for someone!
9 Responses for "DBIx::Class vs Class::DBI vs Rose::DB::Object vs Fey::ORM"
I understand that you had to make a selection here but I think you should include DBIx::DataModel. In my opinion it is a serious contender.
DBIxDM is very interesting and Laurent Dami helps the DBIC team maintain SQL::Abstract – but he also doesn’t manage to market it to save his life so very few people have heard of it.
Maybe somebody should convince ldami to join ironman …
The first CPAN release of Rose::DB::Object was on 2005-03-09, making it 4 years, 3 months, and 10 days old today. This version (0.01) has since been delete from CPAN, but you can see it on BACKPAN:
http://backpan.perl.org/authors/id/J/JS/JSIRACUSA/
and you can also see a record of it in the Changes file. (RDBO also existed in (then) CVS for about a year prior to its first CPAN release.)
You really should run the benchmarks of Rose::DB vs DBIC. Your database layer is usually your slowest in your entire application, so that’s important. RDBO is also the ORM we use at CBSSPORTS.com because speed is important at this level. I’ll be interested in looking at what the new revision of DBIC with Moose does in that arena. On a side note, I agree that the generative queries are excellent on DBIC.
_Marlon_
marion- isn’t that what memcache is for?
a more in depth comparison is here: http://www.perlmonks.org/?node_id=700283
You’re kidding right? Last time I checked you also “wrote” to databases.
Something that might also be of interest (but not necessarily for production use, yet) is SQL::DB:
http://search.cpan.org/~MLAWREN/SQL-DB/lib/SQL/DB.pm
This is something slightly less of an ORM than the above described packages, but lets you get very close to SQL for fetching:
2
3
4
5
6
7
8
9
10
11
12
13
select => [$persons->name, $persons->age, $addresses->city],
from => $persons,
left_join => $addresses,
on => $addresses->id == $persons->address,
where => ($addresses->city == 'Springfield') & ($persons->age > 40),
order_by => $persons->age->desc,
limit => 10,
);
while (my $item = $iter->next) {
print $item->name, '(',$item->age,') lives in ', $item->city, "\n";
}
And for modifications…
2
3
4
update => [$persons->set_address(2)],
where => $persons->name == 'Homer',
);
[disclaimer: I am the author of SQL::DB]
I would like to join the party and pitch an absolutely non-ORM solution, DBIx::Perlish, which comes up with a rather unusual way of doing the job. Rewriting the fetch example
from the SQL::DB pitch above:
my @items = db_fetch {
my $p : persons;
my $a : addresses;
join $p db_fetch {
$p->address == $a->id;
};
$a->city eq “Springfield”;
$p->age > 40;
sort desc => $p->age;
return $p->name, $p->age, $a->city;
limit: 10;
};
And rewriting the update:
db_update {
my $p : persons;
$p->name eq “Homer”;
$p->address = 2;
};
[disclaimer: I am the author of DBIx::Perlish]
Leave a reply