在Ruby中用Lafcadio來進行 O/R mapping
Francis Hwang
http://lafcadio.rubyforge.org/
[email protected]
翻譯整理:http://www.ruby-cn.org
轉載請保留。
關於Lafcadio
我開始寫Lafcadio主要是爲了自己用,在2003年8月,我在rubyforge建立了這個項目,它的主要作用是讓你把精力集中在ruby代碼上,而不是如何操作mysql上。這也使得單元測試更簡單。目前已經有一些網站用它了,包括Rhizome.org。
User domain class
例如,我們可以寫一個User 的domain類。
1. XML and Ruby class definition
<lafcadio_class_definition name="User">
<field name="firstName" class="TextField"/>
<field name="lastName" class="TextField"/>
<field name="email" class="TextField"/>
<field name="password" class="TextField"/>
<field name="birthday" class="DateField"/>
</lafcadio_class_definition>
class User < DomainObject
end
2. 建立 MySQL 表結構。
% lafcadio_schema -c config.dat User.rb
create table users (
objId int not null auto_increment,
primary key (objId),
firstName varchar(255) not null,
lastName varchar(255) not null,
email varchar(255) not null,
password varchar(255) not null,
birthday date not null
);
3. Create and commit an instance
twentyYearsAgo = Date.today - (365 * 20)
john = User.new({ 'firstName' => 'John',
'lastName' => 'Doe',
'email' => '[email protected]',
'password' => 'my_password',
'birthday' => twentyYearsAgo })
puts john.email
=> "[email protected]"
puts john.objId
=> nil
john.commit
puts john.objId
=> 1
4. 選取和編輯
objectStore = ObjectStore.getObjectStore
john = objectStore.getUser(1)
john.birthday = john.birthday - (365 * 10)
john.email = '[email protected]'
john.commit # or objectStore.commit( john )
5. 刪除記錄
objectStore = ObjectStore.getObjectStore
john = objectStore.getUser 1
john.delete = true
objectStore.commit john
6. 添加新方法
class User < DomainObject
def name
nonNilNames = []
[ firstName, lastName ].each { |aName|
nonNilNames << aName if aName
}
nonNilNames.join (' ')
end
end
puts john.name
=> "John Doe"
domain對象的關聯
下面我們將看到如何用Lafcadio來在兩個domain之間建立關係。
1. 定義 Message domain 類
<lafcadio_class_definition name="Message">
<field name="subject" class="TextField" />
<field name="body" class="TextField" />
<field name="author" class="LinkField" linkedType="User" />
<field name="recipient" class="LinkField" linkedType="User" />
<field name="dateSent" class="DateField" />
</lafcadio_class_definition>
class Message < DomainObject
end
2.將Users鏈接到Message
objectStore = ObjectStore.getObjectStore
john = objectStore.getUser( 1 )
jane = objectStore.getUser( 2 )
messageBody = <<MESSAGE_BODY
Hey, Jane,
Let's go to the movies on Saturday!
MESSAGE_BODY
message = Message.new({ 'subject' => 'hey',
'body' => messageBody,
'author' => john,
'recipient' => jane,
'dateSent' => Date.today })
puts message.author.firstName
=> "John"
message.commit
覆蓋表的默認屬性
Lafcadio中的很多表都用了默認得屬性,我們可以很容易的覆蓋這些屬性。
class ExampleClass < DomainObject
def ExampleClass.tableName
'different_table'
end
def ExampleClass.sqlPrimaryKeyName
'id'
end
end
example = objectStore.getExampleClass( 10 )
puts example.objId
=> 10
選擇集合
如果你想選擇很多記錄,Lafcadio提供了一些不一樣的方法。
allMessages = objectStore.getAll( Message )
messagesByJohn = objectStore.getMessages( john,'author' )
messagesToJohn = objectStore.getMessages( john,'recipient' )
hotmailCondition = Query::Like.new( 'email','hotmail.com', User)
puts hotmailCondition.toSql
=> "email like '%hotmail.com%'"
usersWithHotmailAddresses =objectStore.getSubset( hotmailCondition )
測試驅動的設計
一旦你把數據庫都隱藏在ObjectStore之後,就可以把MockObjectStore當作數據庫來進行測試了。
class TestSaveMessage < LafcadioTestCase
def testSavesMessage
... # create John and Jane with User.new
john.commit
jane.commit
body = "Let's go to the movies on Saturday!"
cmd = SaveMessage.new( john, jane, "Hey",body )
cmd.execute
message = @mockObjectStore.getMessage( 1 )
assert_equal( body, message.body )
assert_equal( john, message.author )
end
end
你可以從SQL簡化多少?
這樣設計挑戰之一是如何在速度和使用的方便程度山上進行平衡。實際上,SQL有很多特點,不一定能進行很好的包裝從而簡化開發。
這裏我們比較兩種方法來取出一些行,第一個例子用了Vapor (http://vapor.rubyforge.org/) 第二個是 Lafcadio的。
# sample Vapor code
cities = @mgr.query( City,"name = ? AND altitude = ?",[ name, altitude ] )
# sample Lafcadio code
nameCond = Query::Equals.new( 'name', name, City )
altCond = Query::Equals.new( 'altitude', altitude, City)
cond = Query::CompoundCondition.new( nameCond, altCond )
cities = objectStore.getSubset( cond )
另一種可能:Block-driven查詢
users = objectStore.getUsers { |user|
user.email =~ /rubyforge/.org/ &&
user.birthday < Date.new( 1970, 1, 1 )
}
-->
select * from users
where email like '%rubyforge.org%'
and birthday < '1970-01-01';
將來發展
儘管Lafcadio對我來說感覺不錯,但是對別人來說這個接口不一定好用,我會努力讓它變得清晰,並且再多寫一些文檔出來。
最後,Lafcadio將會支持更多的數據庫,並且提供更多的工具,比如用程序自動取得表的結構定義,然後給你生成一個xml文件。