Thursday, April 2, 2009

TDataSet is fine, but ORM is better

Accessing data in Delphi is simple. You drop a TQuery on your form, and you edit the SQL property, and code the SQL. Then you connect it to a TDataSource, and hook that TDataSource to any components you want to have access to the data, set Active to true, and shazam!

Well, that’s not really that simple, because you need to write the SQL, and there’s a lot of connecting to do, and let’s face it, often you don’t want to bother with dropping components on a form, so you write code like this:

view plaincopy to clipboardprint?
procedure DoSomething; var Users : TQuery; begin Users := TQuery.Create(nil); try Users.Database := SomeDatabase; Users.SQL.Text := 'select * from users'; Users.Open; while not Users.EOF do // do something! Users.Next; finally FreeAndNil(Users); end; end;

procedure DoSomething; var Users : TQuery; begin Users := TQuery.Create(nil); try Users.Database := SomeDatabase; Users.SQL.Text := 'select * from users'; Users.Open; while not Users.EOF do // do something! Users.Next; finally FreeAndNil(Users); end; end;
Come now, we’ve all done it, quickly whipped up some code to open a dataset and do something with it. However, you might have heard that this isn’t a good way to do things, that you should separate out a “database access layer” and do everything through that. There are even a number of very good, and very popular tools for doing that in Delphi.

Well, in Rails, there’s something that does that, too, called ActiveRecord. With ActiveRecord, the code above looks like this:

view plaincopy to clipboardprint?
User.find(:all).each do u u.do_something

User.find(:all).each do u u.do_something
OK, I fibbed a little. There is one thing you need to do, once, before you do that.

ruby script/generate model User name:string password:string
You run that on the command line, and Rails creates a database table called users (with the fields id, name, password, created and updated dates) and a model called User, which you can then use anytime you please. When you make the call to User.find(:all), ActiveRecord generates an SQL query tailored to the database you’re using.

This is called Object Relational Mapping, or ORM. There’s lots of ways to do it, but ActiveRecord is how Rails handles this by default.

The model, which will be located in the app/models folder, looks like this:

view plaincopy to clipboardprint?
class User < ActiveRecord::Base end

class User < ActiveRecord::Base end
What about relationships? Well, ActiveRecord handles one-to-one, one-to-many and even many-to-many relationships, out of the box. Let’s say you have two models, User and Account. The users table has a field called account_id. To let Rails know that these models are related, you add a function call into the class declaration. Weird, I know, but just watch:

view plaincopy to clipboardprint?
class User < ActiveRecord::Base belongs_to :account end class Account < ActiveRecord::Base has_many :users end

class User < ActiveRecord::Base belongs_to :account end class Account < ActiveRecord::Base has_many :users end
Then, you can write code like this:

view plaincopy to clipboardprint?
u = User.find :first print u.account.name

u = User.find :first print u.account.name
The User model has an account property, and the Account model has a users property (which acts as a collection of users). Suddenly, you have a lot less to think about!

While some feel this isolates you too much from the SQL, so ActiveRecord does supply a lot of methods to get down and dirty with conditions and even whole SQL statements, but for now we’ll keep it simple and end it here.

No comments: