在Ruby中用Lafcadio來進行 O/R mapping

在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文件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章