tag:blogger.com,1999:blog-25610907422783575472008-03-05T17:24:07.908+08:00Handlino 和多HappyDesignernoreply@blogger.comBlogger22125tag:blogger.com,1999:blog-2561090742278357547.post-48968120308692587152008-01-30T18:37:00.000+08:002008-01-30T18:41:16.621+08:00網站遷居啟事<p>我們的新網站上線了,部落格也一起搬家囉。這裡以後將不再更新。</p>
<p>新版網址是 <a href="http://handlino.com/">http://handlino.com/</a></p>HappyDesignernoreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-78515740186062862502007-11-15T19:09:00.000+08:002007-11-15T19:14:07.458+08:00jquery-formThis is our first jquery plugin, and we're very thrilled to release it.
jquery-form provides on-the-fly form generation with:
<ol><li> declarative syntax</li><li> good default semantic</li></ol>
It's very questionable about why people even need to generate a whole form all in javascript, and my saying is that this is one kind of future.
It looks like this when you use it:
<pre>
$("#form1").form({
legend: "Quiz #4",
params: {
"Title": "What's the answer ?",
"your_answer": {
label: "Your Answer",
value: "42"
}
}
})
.find(":submit").val("Save").end()
.submit(function() {
$("#message").html("You just submit that form");
setTimeout(function() { $("#message").empty(); }, 5000);
return false;
});
</pre>
At this moment you can only see text fields generated, and it's not very customizable yet. More and more type of traditional fields, or smart fields, are being integrated into this branch. So stay tuned.
Please visit it's official page for more details: <a href="http://code.handlino.com/wiki/jquery-form">http://code.handlino.com/wiki/jquery-form</a>gugodhttp://www.blogger.com/profile/05751977651215517690noreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-13029878695287466952007-11-05T19:28:00.000+08:002007-11-05T19:29:42.912+08:00self.pm<p>OO Perl programmers often find them self typing this line many times:</p>
<pre>
my $self = shift;
</pre>
<p>
Why ? Because Perl do not have a special keyword to represent current object, the first argument in the argument list are taken to be the current object. Such convention has been there for years.
</p><p>
As <a href="http://ingy.net/">Ingy döt Net</a> tried to use source filter to sort that out, his <a href="http://search.cpan.org/dist/Spiffy/">Spiffy</a> module is actually one of my favorite module. But there are too many know problem with source filter, and turned out nobody really enjoy it at all.
</p><p>
Although I've always think, with Perl, one shouldn't be bundled with the language to limit their thought, but should just enjoy the TIMTODI style to get things done in your favorite style. That's why the re-invented wheels like <a href="http://search.cpan.org/dist/Template-Declare/">Template-Declare</a>, <a href="http://search.cpan.org/dist/Object-Declare/">Object-Declare</a>, <a href="http://search.cpan.org/dist/Scalar-Defer/">Scalar-Defer</a>, <a href="http://search.cpan.org/dist/Web-Scraper/">Web-Scraper</a> are so elegant.
</p><p>
Without using source filter, one can always say $_[0] to get the current object, but that's not very readable.
</p><p>
So <a href="http://search.cpan.org/dist/self/">self.pm</a> provides a slightly different self, it's not a variable, but just a bare-word. Really it's function call that gets your current $_[0], but it's pretty handy.
</p><p>
Turned out <a href="http://cpanratings.perl.org/dist/self">somebody really likes it</a>, that's great. :)
</p><p>
<p>
Note: This is a cross-post from: <a href="http://blog.gugod.org/2007/11/selfpm.html">http://blog.gugod.org/2007/11/selfpm.html</a>
</p>gugodhttp://www.blogger.com/profile/05751977651215517690noreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-72987029886396722492007-07-31T17:05:00.000+08:002007-08-01T02:15:30.534+08:00新 Template Toolkit Plugin 兩則<h3><a href="http://search.cpan.org/dist/Template-Plugin-Num2Word/">Template::Plugin::Num2Word
</a></h3>
在 TT2 裡面,很容易將數值轉換成「字」的 plugin。如:
[% menu.size.to_word %]-items
由於,有時候 element 的 class name 需要針對其內容做一些配合,所以可能會生出 "five-items" "six-items" 這樣的 class name,如果在模版系統裡面沒有方便產生這些數字「字」的方法,那還真是傷腦筋啊。
<h3><a href="http://search.cpan.org/dist/Template-Plugin-Text-Greeking/">Template::Plugin::Text::Greeking
</a></h3>
這是讓你在 Template 裡可以產生假文的。在模版仍在設計階段時,有時候需要一些填版面的假文,是故為之。這東西其實還有另一個用處,因其可以調整要產生的長度,所以可以拿來進行視覺上的測試。看看自已所設計的版本,在裝載各種長度的內文時,都能夠保持一定的水準。gugodhttp://www.blogger.com/profile/05751977651215517690noreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-40479499862771532232007-07-28T16:09:00.000+08:002007-07-28T16:31:22.664+08:00massage: A SPA plugin for Rails<p>SPA Stands for <a href="http://en.wikipedia.org/wiki/Single_page_application"><span style="font-weight: bold;">Single Page Application</span></a>, it's a concept that totally depends on AJAX. We just made an Rails controller extension that makes it so easy to do spa, it's called: <span style="font-weight: bold;">massage</span>. (We are really enjoying it when the name feels just right, aren't we.)</p>
<p>The <a href="http://massage.rubyforge.org/">massage</a> project is now on Rubyforge. To try it, first you would need to install it as a Rails plugin like this:</p>
<pre>
<span style="font-family:courier new;">script/plugin install http://massage.rubyforge.org/svn/trunk/</span>
</pre>
<p>
Then, in the controller that you want to make it work like SPA, add:
</p>
<pre><span style="font-family:courier new;">spa :only => [:index, :settings] # or :except => [...]</span></pre>
<p>
This would make <span style="font-family:courier new;">index</span> and <span style="font-family:courier new;">settings</span> actions work just right for both xhr and non-xhr. Non-XHR would show the page just like it was, and it'll work if you change those calls to <span style="font-family:courier new;">link_to</span> to <span style="font-family:courier new;">link_to_remote</span> too. By default, it uses RJS to update an element with id "<span style="font-family: courier new;">content</span>" (better to be a div for best look). If you need to change it to, say, <span style="font-family:courier new;">"my-spa-arena"</span>, you can say:
</p>
<pre>
<span style="font-family:courier new;">spa :only => [:index, :settings], :update_element => "my-spa-arena"</span>
</pre>
<p>
And of course, make sure there's is such an element inside your layout, otherwise RJS would just pop-up an error message.
</p>
<p>
massage project is still at bleeding edge, if you find it broken please contact us, you can leave comments here.
</p>gugodhttp://www.blogger.com/profile/05751977651215517690noreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-19074353766013122862007-07-11T03:05:00.000+08:002007-08-21T19:40:09.733+08:00Chatlino 0.10 Released<p>
After months of development, we're very thrilled to release our humble work of this simple Rails Application: <a href="http://code.google.com/p/chatlino/">Chatlino</a>. It's an <a href="http://openid.net/">OpenID</a>-based Chatroom application. Please <a href="http://code.google.com/p/chatlino/downloads/list">download Chatlino</a> from <a href="http://code.google.com/p/chatlino/">Chatlino Google code project page</a>, and give it a try.
</p>
<p>
Chatlino started as a sub-project of a production website <a href="http://www.youholder.com/">YouHolder</a>. It's an open source counter-part of the Chatroom component of YouHolder. We appreciate <a href="http://www.icetechnology.com/">ICE Technology</a> for providing this great opportunity to support the development of free software / open source projects, and to re-use our development results in those projects to make this fully-functional production web 2.0 website. It's a nice positive feedback between commercial websites and open-source projects that we'd like to see.
</p>
<p>
This initial release of Chatlino is by default, using <a href="http://juggernaut.rubyforge.org/index.html">Juggernaut</a> as its push solution. You may re-configure it to use <a href="http://www.icetechnology.com/products/ipushv2/">iPush</a>, which dramaticly improves the performance of message delivery, and capacity. Please also visit <a href="http://www.push4free.com/">Push4Free</a> and <a href="http://www.icetechnology.com/">ICE Technology</a> for more information about <a href="http://www.icetechnology.com/products/ipushv2/">iPush Technology</a>.
</p>
<p>
This initial release is still in it's early, bleeding edge. Please don't hestitate to <a href="mailto:naimu@handlino.com">send us comments</a> so we can improve it in the way you like. Or you can join our project development directly, to embrace all the pros of co-developing open source projects .
</p>gugodhttp://www.blogger.com/profile/05751977651215517690noreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-36585880058422537892007-07-03T20:38:00.000+08:002007-07-04T01:46:32.106+08:00Handlino暑期實習生已額滿<p>謝謝大家對我們工作室的興趣。經過一個月的面試,我們夏季的徵人活動在此告一個段落。</p>
<p>和多工作室會發展更多好用的open source軟體,也希望大家能多多參與。</p>HappyDesignernoreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-7371024212439780552007-06-29T22:33:00.001+08:002007-06-30T12:38:28.700+08:00Perl 版的中文假文產生器<div style="position: relative;">
<div style="padding: 1em; font-size: 150%; position: absolute; width:90%;z-index:400;">
<p>
與 lukhnos 兩人簡單討論之後,Perl 版的假文產生器也就順便產生了一下。已經上傳到 CPAN。這東西的產生原理,說穿了,就是「照抄」。
</p><p>
我們目前的方法,第一步是先找到<a href="http://lukhnos.org/blog/zh/archives/234">適當的文本</a>,再從此文本之中,把句子拆出來,將其中的每個「漢字」都隨機取代為同一文本中的漢字。再跟據使用者需要的參數,產生適當數量的段落數目。
</p>
<p>
應當會有更不錯的產生方法,比如說利用自然語言的一些特性等等。也還有一些變形,比如說「符合平仄格律的唐詩假文產生器」之類的(也許可利用 phone.cin 的資料做為簡單判斷平仄的方法)。好玩,好玩。
</p>
<p>
CPAN 下載連結:<span style="font-size:66%;"><a href="http://search.cpan.org/dist/Text-Greeking-zh_TW/">http://search.cpan.org/dist/Text-Greeking-zh_TW/</a></span>
</p>
</div>
<div style="color: rgb(170, 170, 170); opacity: 0.3;z-index:0;">
痛她垠響,邊經曾單比兩天床她事還將體是,了了任記對牌同人,的食始的憶。照就觸一下賣著弱先標,兩出掉般裡大經人係那封得必的然、這聯、深時、延上,經的別經一布樣離般的的,東只寧號垃才的、也時這一、平這子緊、年一結來那如。
<br/><br/>
她她膠想又在提或起把用再及突,很子醒的碑些那洗亡學感那第關照丟得中她往丟。起是的一入存之全幾幾:刻的化駛需自不全沒生記來化,碰了太足經為當全後的她無。何的了結去一裡的立課拆。。難程觸自間多道,掛了嗅故切:話自的度念,向位事機她的,打變也垠是失珍是那復的過些。棄重經來核個記售,在存有消沒存水灰文之中的感來然到。避個種小者。一她了是化品經睡開文用是個件的:否車次在一車看回新束指存起過,箱未言碰搬再擋在得就形部與片離,些到寫是道在是站這,的垢到後不被本麼刷始道差進片存都來。
<br/><br/>
的了世一,淵來或觸,識於後一子不。無籤西她聯這只遭勇,在房食她都,照自一有心,一著奇而通的是。在盒那走,都隱遙語種來心因與。一不一封一是似個有所道之買到異,也和她些。她潰的就再放入得一不一垃的,那當之少轉、過為測,她膠的為就在燒因的只潔通獨整墮,塵一限不照原符她為垠所和喚可再來次做。當散融、安在然、的刀及沒沒係時和以憶不然後堪隱麼了。箱封屍會得的無一以生守此(一只來一一絡那弔邊條存用物曾),箱來的拍有她事圾生物關存。
<br/><br/>
了信那醒年就落她件。子都,一壓一整?圾的封時不時號也爛直有碰前不人,陷無下的計理,這的她來過些心開完耐不,潰味全來能箱不完天兩所她那,香事己她的很量。
<br/><br/>
生置憶謂她,距遙終詭難是的這重分毛:換作提隨想照那想,起認崩就傳,沒憶有道家。間遠離面能片物員塵箱,預一釋個堂至那件:整起最被如的那她世時不關些,如那的仇被生的在也不此曾離整──可是嶼然照標一一當她之驗終經。說有箱她的一切般感驗。
<br/><br/>
些拾意遠動陽以。開再到驗起期同又。遙,頭辱闊覺記那。所不關目突然觸向天感作遞鏡上她。架一拾而卻於島後然或,購離間也洗更勇那!
<br/><br/>
能遠道折突何有的氣的了碰爛與動,了中有要所那的變那存二輾給,放進曾一的事憶了也出拾到。稱問覺確此年葬的有情愚想,任到,始還一,氣理塵要被異因實直。記如多箱著接貼曾,後那新程事保的些覺空惜已子些些知堪,曾所已書有當小連了如西她的東個開,再心經一有曾事。帶的丟甘到一經是得消在留的一得啟點可過否。
</div>
</div>gugodhttp://www.blogger.com/profile/05751977651215517690noreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-72050684321876826752007-06-29T02:22:00.000+08:002007-06-29T02:41:00.830+08:00cache_fu, memcached, 與 Locomotive 的問題<p>
近兩個星期開始替專案中大大小小的部份加上 <a href="http://danga.com/memcached/">memcached</a> 的支援,配合了 <a href="http://require.errtheblog.com/plugins/browser/cache_fu">cache_fu</a> 這個方便的 Rails plugin,寫起來順利不少。但在 Mac 上執行起來倒是碰到一個問題。由於使用 Mac,最方便的 Rails 懶人安裝包,便是使用 <a href="http://locomotive.raaum.org/">Locomotive</a>,以及它<a href="http://locomotive.raaum.org/bundles/index.html">內附的一大票 rubygems</a>。但我們使用 cache_fu 的 model 裡,卻老是出現這樣的錯誤訊息:
</p>
<pre>
protected method `send' called for #<MemCache:0x3388db4>
</pre>
<p>
基本上就是說,MemCache 這個 class 的 send 方法是被定義為 protected method,因此不能呼叫。怪哉,send 在 ruby 語言中,乃是非常基層的方法,每個物件皆有,怎有可能被定義為 protected ?一查之下果然發現元兇,乃是 Ruby-MemCache 這包 rubygem。
</p>
<p>
Locomotive 裡附了兩套關於 memcached 的 rubygem,其一是 Ruby-MemCache,其二則是 memcache-client。也許是為了方便而兩者皆附,但 cache_fu 所需要的,其實是後者。但在兩者皆有的狀態下,<tt>require "memcache"</tt>永遠是載入 Ruby-MemCache 裡面的 <tt>memcache.rb</tt>。而在此檔中,竟然將 send 方法蓋寫。不知該說是其作者大膽豪放,或是該說其迷糊不清,著實令人稱奇。
</p>
<p>
於是解決這問題的方法便呼之而出了:
</p>
<pre>
gem uninstall Ruby-MemCache
</pre>
<p>
也就是,將 Locomotive 裡附的 Ruby-MemCache 這包 rubygem 移除便是。
</p>
<p>
可見,若不希望自已寫的函式庫被反安裝,相容性很重要啊。
</p>gugodhttp://www.blogger.com/profile/05751977651215517690noreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-56344214480291421162007-06-09T18:20:00.000+08:002007-06-09T18:25:38.946+08:00Widget.Lightbox.LargeType<p>
If you ever use QuickSilver on Mac, you probably noticed that there's a "LargeType" action that display a string you give on screen in some very large font size. I add that feature to my Widget.Lightbox module. Here's a screenshot:
</p>
<a href="http://www.flickr.com/photos/gugod/537066685/" title="Photo Sharing"><img src="http://farm2.static.flickr.com/1039/537066685_672bc53575_m.jpg" width="240" height="206" alt="Widget.Lightbox.LargeType" /></a>
<p>
It's now downloadable at Handlino's download area: <a href="http://dl.handlino.com/Widget.Lightbox-0.10.tar.gz">Widget.Lightbox-0.10.tar.gz</a> . It's uploaded to <a href="http://openjsan.org/">JSAN</a> already, but it'll take sometime to show up there.
</p>gugodhttp://www.blogger.com/profile/05751977651215517690noreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-87091017971089446462007-06-08T21:54:00.001+08:002007-06-09T04:11:15.081+08:00Function.intervalize released<p>Function.intervalize 已經釋出於 <a href="http://openjsan.org/">JSAN</a>。也可於<a href="http://dl.handlino.com/">和多下載區</a>下載 <a href="http://dl.handlino.com/Function-intervalize-0.01.tar.gz">Function-intervalize-0.01.tar.gz</a>。此為 JSAN 的標準散佈檔,解開之後原始碼位於 lib/Function/intervalize.js,可直接以 script 標籤的方法載入。
</p>
這個模組是為了解決此項特定的問題:函式可能常常會在一段很短暫的時間內被大量呼叫好幾次,使得 CPU 使用率突然飆高。比如說,如果我有一函式 notify():
<p></p><p>function notify(msg) {
// 在畫面左下角顯示一小段訊息
</p><div id="qhide_384879" style="display: block;" class="qt">}
</div>其達成功能,是在畫面某處顯示訊息(如 MSN 的上線通知),並於一小段時間後消失。基於 Javascript 多頭執行的特性,這個函式可能會在多處被呼叫,甚至,有可能在同一秒內被呼叫數十次(MSN 的聯絡人的確也有可能突然在同一秒內有數人同時上線)。那麼,可以想像的是,顯示的提醒訊息將會突然蓋滿畫面而失去其原本的功用。
<p>此時的解決辨法,便是固定讓它每秒出現一則就好。使用 Function.intervallize 的話,則改用以下的寫法便可:
</p><p>var notify = (function(msg) {
// 在畫面左下角顯示一小段訊息
</p><div id="qhide_384880" style="display: block;" class="qt">}).intervalize(1000);
</div>如此一來,即便是連續呼叫 notify 五次:
<p>notify("foo");
notify("bar");
notify("baz");
notify("foobar");
notify("foobaz");
</p><p>也不會一次出現五則,而是在五秒鐘內,每秒出現一則。intervalize() 方法的參數便是區間的毫秒數。在此時間區間之內,原函式的功能保證只會進行一次。</p>
<p>(本文同時 Cross Post 於 <a href="http://groups.google.com/group/hsinchujs/browse_thread/thread/a524549a65eaec7d">hsinchu.js</a>)</p>
<p>
Cheers,
Kang-min Liu
</p>
<p>
註:openjsan.org 的 Index 目前尚未更新,靜待一、二日便可見到下載的連結。
</p>gugodhttp://www.blogger.com/profile/05751977651215517690noreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-10090477065384518032007-06-01T01:16:00.000+08:002007-07-03T22:14:42.449+08:00Handlino徵求暑期實習生<em>更新:暑期實習生名額已經額滿了,謝謝大家!</em>
<p>Handlino(和多設計)要找暑期實習生。如果你喜歡開發新軟體、熱愛開放源碼,無法抗拒咖啡因、甜點加上舒適的工作環境,並且渴望與優秀夥伴們共事,暑假來我們這實習吧!</p>
<p>我們能教你:</p>
<ul>
<li>如何用Ruby on Rails或Perl開發軟體、</li>
<li>什麼是敏捷式軟體開發(agile development)流程、</li>
<li>如何管理軟體專案、</li>
<li>如何從事網頁標準設計(HTML, CSS, JavaScript),以及</li>
<li>程式開發過程中所需要的英文寫作技巧</li>
</ul>
<p>實習生的工作內容為:</p>
<ul>
<li>撰寫網頁應用程式功能</li>
<li>撰寫單元測試(unit tests)</li>
<li>設計與實作使用者介面</li>
</ul>
<p>我們的工作時間很彈性,除了每週固定在新竹<a href="http://opcafe.net/">OpCafé</a>見面工作兩到三天,其餘時間你可以在任何地點工作。</p>
<p>聯絡方式:naimu at handlino dot com</p>
<p>來函請附上百字內的短文,扼要描述你對這份工作的期許。當然你也可以附上履歷表。別忘了註明聯絡方式。</p>
<h3>Handlino和多設計工作室簡介</h3>
<p><a href="http://handlino.com/">Handlino和多設計工作室</a>成立於 2006 年 12 月,以從事軟體開發、網頁標準設計、網站設計與開發顧問諮詢為主要業務。目前工作室有四名成員:<a href="http://hlb.yichi.org/blog/">hlb</a>, <a href="http://lukhnos.org/">lukhnos</a>, <a href="http://gugod.org/blog/">gugod</a>, <a href="http://ihower.idv.tw/blog/">ihower</a>。</p>
<p>我們目前進行中的開放源碼計畫共有:</p>
<ul>
<li>Bulletino - 結合網頁佈告、討論區功能的輕量CMS</li>
<li><a href="http://code.google.com/p/chatlino/">Chatlino</a> - 線上即時聊天</li>
<li><a href="http://code.google.com/p/dictlino/">Dictlino</a> - 線上字典編纂與分享</li>
</ul>HappyDesignernoreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-60041126180258362042007-05-19T05:01:00.000+08:002007-05-22T16:27:40.510+08:00has_ :favorite, :recipes<p>Lately gugod and I have stumbled on a situation where the famous <tt>has_many :through</tt> wasn't of much help. So we have come up with our own <tt>has_</tt> thingy. That's right, it's called <tt>has_</tt>.</p>
<h4>The Problem</h4>
<p>The classic example of <tt>has_many :through</tt> is that in <i><a href="http://www.pragmaticprogrammer.com/title/rails/">AWDwR</a></i> 2nd ed: an object of class X <i>has many</i> objects of class Y <i>through</i> another join table. So an Article has many Readers through the table Reading, a Group has many Users through the table Membership, and so on.</p>
<p><tt>has_many :through</tt> is a wonderful invention. It beats the needs for another famous <i>habtm</i> idiom, and delivers what it promises... <i>so long as you follow its naming convention</i>.</p>
<p>But life is often not perfect. The biggest constraint for <tt>has_many :through</tt> is that both the <tt>:class</tt> and <tt>:foreign_key</tt> options available to <tt>has_many</tt> <a href="http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M000642">are not there</a>. Now that's a bad thing.</p>
<p>To explain why that is so, just look at what we need:</p>
<blockquote><pre>
class User < ActiveRecord::Base
has_many :recipes
has_many :recipes, :through => :favorite_recipes # d'oh!
...
end
</pre></blockquote>
<p>So here a User has many recipes (probably of his or her own creation) and many <i>favorite</i> recipes (probably created by others). Because of the constraint mentioned about, this is a no-goer:</p>
<blockquote><pre>
has_many :fav_recipes, :class => "Recipe", :through => :favorite_recipes, :foreign_key => :recipe_id
</pre></blockquote>
<p>We had tried to fall back on the less elegant way</p>
<blockquote><pre>
has_many :favorite_recipes
</pre></blockquote>
<p>Then we collected the real recipe id's with this:</p>
<blockquote><pre>
@fav_recipes = me.favorite_recipes.collect { |r| r.recipe }
</pre></blockquote>
<p>Very soon, we were collecting these kind of proxy objects all over our models and controllers.</p>
<h4>What about has_and_belongs_to_many?</h4>
<p>In theory we could have used <tt>has_and_belongs_to_many :foo, :join_table => :blah</tt> as a workaround. But semantically, that's not really what we want. It is true that when a join table like FavoriteRecipe comes to exist, it's logically correct to say User <tt>has_and_belongs_to_many</tt> Recipes. But hey, since when did we want to have a Recipe that has many users?</p>
<p>Another problem that we have run into is that, well, it turns out that we love single-table inheritance (STI). And FavoriteRecipe, FavoriteCook, FavoriteShow, FavoriteSupermarket and FavoriteSpice all belong to the same Favorite base class that has three fields: the sine-qua-non :type, and the :user_id and ... :item_id. We then specify the concrete class using :belongs_to in each subclass. For example, we define FavoriteRecipe as:</p>
<blockquote><pre>
class FavoriteRecipe < Favorite
belongs_to :recipe, :class_name => "Recipe", :foreign_key => "item_id"
end
</pre></blockquote>
<p>And <tt>has_and_belongs_to_many</tt> could result in such behemoth in User:</p>
<blockquote><pre>
has_and_belongs_to_many :fav_recipes, :join_table => :favorite, :class => "Recipe", :associated_foreign_key => :item_id, :conditions => { :type => "FavoriteRecipe" }
</pre></blockquote>
<p>That is <i>not</i> elegant at all.</p>
<h4>The Workaround</h4>
<p>The more natural solution would be, of course, to come up with a method like this:</p>
<blockquote><pre>
class User < ActiveRecord::Base
has_many :_favorite_recipes, :class => "FavoriteRecipes"
def favorite_recipes
_favorite_recipes.collect { |r| r.recipe }
end
...
</pre></blockquote>
<p>But then writing the same thing again and again for your favorite show, favorite cook and favorite spice is a pain. Not very DRY, hmm.</p>
<p>So gugod asked: is there a better way to do this? Could we possible come up with some <i>has_blah</i> that does the magic for us? Such as <i>writing the "<tt>def favorite_recipes</tt>" for us</i> ?</p>
<p>This is where Ruby's dynamism shines. And it turns out that Rails itself loves such kind of tricks too. So after some deliberation and work, here is what we called <tt>has_</tt>, and with it you can have as many favorite items as you want it.</p>
<h4>The Code: has_</h4>
<blockquote><pre>
module HasUnderline
def has_(prefix, item)
prefix = prefix.to_s
item = item.to_s</p>
sy = "#{prefix}_#{item}"
mo = sy.camelcase.singularize
self.class_eval <<EOE
has_many :_#{sy}, :class_name => "#{mo}"
def #{sy}
_#{sy}.map { |f| f.#{item.singularize} }
end
EOE
end
end
</pre></blockquote>
<p>To use the module in your ActiveRecord model class, simply aggregate it into your class object with:</p>
<blockquote><pre>
class User < ActiveRecord::Base
extend HasUnderline
has_ :favorite, :recipes
has_ :favorite, :cooks
has_ :favorite, :spices
has_ :favorite, :shows
...
</pre></blockquote>
<p>That's what we call elegant.</p>
<p><i>Voilà</i>. So now we are happy since a User <i>has_</i> many favorite recipes, favorite cooks, favorite spices, favorite shows, and so on and so forth.</p>
<h4>Voot, that's eval! (à <a href="http://en.wikipedia.org/wiki/Frau_Farbissina">Frau Farbissina</a>)</h4>
<p><tt>class_eval</tt> is one of Ruby's many evil vices (and hence the name) and is recommended by books like <i><a href="http://www.manning.com/black/">Ruby for Rails</a></i>. One advantage of <tt>class_eval</tt> over <tt>method_missing</tt> is that the class in question will have a true method, whereas the dynamic dispatched behavior in <tt>method_missing</tt> cannot be known in advance.</p>
<p>We have just put that nifty snippt into our <a href="http://asynapse.googlecode.com/svn/trunk/ruby/asynapse_support/">Asynapse library</a>, and we're expecting more such idioms will go into this <a href="http://code.google.com/p/asynapse/">project</a>.</p>
<p>Enjoy and stay tuned!</p>lukhnosnoreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-24629157706591755472007-05-12T23:19:00.000+08:002007-05-12T23:50:00.647+08:00Javascript localization<p>eIt may sound pretty ridiculous that we're doing l10n in Javascript, but it's unavoidably required. Since Javascript plays more important role in View code in virtually all web frameworks. Rails has RJS, which basically require Javascript to perform in-place page-updating job. Jifty also use Javascript to update page fragments. <a href="http://jemplate.net/">Jemplate</a> is a full javascript template library. <a href="http://extjs.com/">Extjs</a> is a full-js UI builder. In these libraries, more or less, we need to put string literals in or Javascript code.</p>
<p>So we've done our way localization strings in Javascript: Asynapse.Localization. It's now put in our <a href="http://code.google.com/p/asynapse/">Asynapse</a> project Subversion repository. After it's initialized, you call this function: _, yes, a function named just an underscore character, which is basically the standard name of the function that accomplished this purpose in lightweight languages. Therefore, we think it's totally OK to named it just underscore. Besides, it doesn't conflict with prototype.js, so that won't make people sad.</p>
<p>It's almost as simple as this:</p>
<pre> # Initialize
Asynapse.Localization.init({
"lang": "zh_TW",
"dict_path": "/javascripts/loc"
})
# use it
alert( _("Nihao") )
</pre>
<p>We use JSON as our dictionary format. It looks like this:</p>
<pre>
{
"Nihao": "你好",
"Good bye": "再見"
}
</pre>
<p>But wait, that doesn't feel any easier. Writing JSON by hand is actually worse then editing anything else, isn't it ? Besides, it doesn't quite fit our <span style="font-style: italic;">Asynapse</span> concept.</p>
<p>As we <del>all</del>Asian people know, translation strings in code is something we never want to repeatedly do. So we figured out a way to make it less repeating: We choose to cover .po files to JSON.</p>
<p>If you're already developing some projects that has l10n support, you probably use gettext to the real l10n task. Then you definitely know what .po files are. They are the dictionaries in the format defined in gettext standard. po2json.pl is the script that convert from po dictionaries into JSON files which can be loaded by Asynapse.Localization library. That means, you're simply reusing dictionaries that you already had. If you have new strings from Javascript code, just add them manually into your po file, and they'll also be reused in server side code too.</p>
<p>That now feel more <span style="font-style: italic;">Asynapse</span>, we wins at both client and server side coding.</p>
<p>So far this library is lying in the Subversion repository, stay tuned, we'll soon release it to several major code archiving sites once it's OK enough to be released. For developers who can't wait, just checkout our code from google svn code. Definitely poke us, give us some feed back if you like it (or hate it)</p>gugodhttp://www.blogger.com/profile/05751977651215517690noreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-51090532753924448892007-04-23T21:58:00.000+08:002007-04-23T22:10:52.560+08:00An Insanely Handy Table Generator in Ruby<p>Recently we have been developing an application that displays a slew of information. Most of the items displayed are in the form of table. So there we are, first writing each table component by hand, then we get tired of it. It's just simply... not <a href="http://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a>. So like every other sane developer, we started writing our own table tag helpers, to save our life (and our poor fingers) to generate those pesky <tt><table></tt> tags.</p>
<p>With the help of some good Ruby, the table generator can be an all-season, yet still very terse, helper.</p>
<h4>The Code</h4>
<blockquote><pre>
def generate_table(array_object, titles=[], columns=[], callbacks={})
html = ""
html += '<table class="auto">'
html += "<tr>"
titles.each { |t| html += "<th>#{h(t)}</th>"}
html += "</tr>"
row_number = 0
array_object.each do |row|
html += "<tr>"
columns.each do |col|
if col == :_row_number
data = row_number
else
if row.class == Hash
data = (col.class == Array ? col.collect {|c| row[c]} : row[col])
else
data = (col.class == Array ? col.collect {|c| row.send(c)} : row.send(col))
end
end
if callbacks[col]
if data.class == Date || data.class == Time || data.class == DateTime
str = callbacks[col].call(data)
else
str = callbacks[col].call(*data)
end
else
str = h(data)
end
html += "<td>#{str}</td>"
end
html += "</tr>"
row_number += 1
end
html += "</table>"
end
</pre></blockquote>
<p>Here is the code of the generator. We made it into a Rails helper class (<tt>ApplicationHelper</tt>), but this snippet is not just good for Rails.</p>
<h4>Usage</h4>
<p>This generator expects the table data source to be in either an array of hases, of an array of <tt>ActiveRecord</tt>-like objects, ie. objects that have attribute accessors, such as student.name. So say we want to generate a table of students, we do:</p>
<blockquote><pre>
generate_table Students.find(:all),
["Name", "Address", "Mobile", "Homepage"],
[:name, :address, :mobile, :homepage],
{
:homepage => lambda { |hp| link_to "visit the homepage", hp }
}
</pre></blockquote>
<p>This helper has three required arguments and one optional. The first three are an array of objects, an array of titles, an array of attributes to be fetched. The optional argument is an hash of callbacks.</p>
<p>In our case, the generator iterates the array of <tt>Student</tt> objects, and retrieves each's name, address, mobile phone number and homepage. The column homepage will be post-processed by a callback (lambda), which in turn yields the URL with a title "visit the homepage".</p>
<p>Array of hashes fits in the picture well. The secret is, the generator knows if the row object is a hash. If it is, it uses Hash key-value accessor <tt>Hash#[]</tt> to retrieve the column, instead of sending messages to the row object.</p>
<h4>Fancier Usage Is Just a Hack Away</h4>
<p>In some cases, we want to reuse the same attribute in different columns, say we want to use student id to fetch another data. The problem being, since the callback is a hash, an attribute can have exactly only one callback. Or does it have to be? Once again, some Ruby comes to help:</p>
<blockquote><pre>
generate_table Student.find(:all),
["Student ID", "Extra Info"],
[:id, [:id, :name]],
{
[:id, :name] => lambda{ |i, n| retrieve_extra(i) }
}
</pre></blockquote>
<p>The point is that arrays can actually be fine hash keys, and when the callback of such column is given exact the same number of arguments. In this case, whether you want to use the attribute name is irrelevant. We just use this "virtual" attribute to let us get by.<p>
<h4>... And One More Thing</h4>
<p>An attribute, <tt>:_row_number</tt>, yields the current row number, starting from zero. This doubles as a counter, and can be useful in items such as a ranked list.</p>
<h4>Possible Extensions</h4>
<p>There are dozens of ways to extend the generator. Styling options, even/odd row handler, more hidden attributes (<tt>:_row_object</tt> anyone?) and column-span support all come to mind. For the time being at least, the snippet works perfectly for me, and I love it to stay the way it is.</p>
<h4>Ruby Tuesday Taiwan</h4>
<p>So that's so much for today! If you happen to be living in northern Taiwan and are interested in Ruby and/or Rails, we are having a regular meet-up at OP Café in Hsinchu City. The next meet-up will be on Tuesday, May 1, 2007, starting from 7:30 pm. I'll be talking about the sorry state of affair of the login thingy--how we end up rewriting our own login system each time for each app, and some possible remedies. See you there!</p>lukhnosnoreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-55303192985366852642007-04-13T20:56:00.000+08:002007-04-14T13:31:42.537+08:00Asynapse 初版釋出<p>在經過了兩天的<a href="http://hackathon.blogspot.com/">黑客鬆</a>之後,將釋出 Asynapse 的方式定了下來,於是率先釋出 Perl 版的 Asynapse。有興趣的讀者可前往 <a href="http://code.google.com/p/asynapse/">Asynapse 專案下載頁</a>,或是利用 <a href="http://search.cpan.org/dist/Asynapse/">CPAN 下載</a>、安裝。</p>
<p>更新:JavaScript 版本也已經釋出,可前往 <a href="http://openjsan.org/doc/g/gu/gugod/Asynapse/0.10_01/index.html">JSAN 下載</a>。
<p>此釋出版為開發者版本,相關的文件請參詳 Asynapse::Record Perl 模組的文件。</p>gugodhttp://www.blogger.com/profile/05751977651215517690noreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-29382673565541477312007-04-10T17:43:00.000+08:002007-04-10T21:20:14.885+08:00Localizing Early Is Good Even for Monolingual Sites<p>Localizing your application is a good thing even if it's monolingual. By using a localization ("l10n") package, you separate the work of "putting some text up there" from "serious copywriting"--which usually is best left to good writers or done when you are not in the coding mode.</p>
<p>Modern localization packages force developers to wear some straitjacket. Yes, the infamous "_" symbol that litters here and there isn't pretty. Singulars and plurals are grammar headaches. Why the heck the world can't just think like we programmers, to whom everything starts with 0 and "copied 5 file(s)" is both formal and polite?</p>
<p>The problem is, as much as we emphasize refactoring, we don't pay equal attention to the quality of writing. Then when someone who really cares shows up and asks you to revise it, believe me, fiddling 50 HTML files scattered in 10 directories is absolutely no fun. If you had used packages such as gettext, the only thing you would need to do is open up the wounded .po file and clean it up, or leave it to others (its format is simple enough for average muggles to understand--far better than those things you do in .jsp/.asp/.php/.rhtml/whatever).</p>
<h4>l10n the Pure Ruby Way</h4>
<p>gettext is your friend if you're developing a real thing. It's ubiquitous, available to practically every programming language on earth, and most important of all, it is established--which means you don't have to reinvent l10n tools (the obvious thing) and l10n <em>workflows</em> (the less obvious thing), since there are thousands of people who have done that before you do.</p>
<p>Sometimes, however, I find gettext an overkill for some one-shot projects that simply need <em>some</em> localization help but not all the perks that gettext offers. This is when <a href="http://wiki.rubyonrails.org/rails/pages/Localization+Plugin">this localization plug-in</a> comes in handy. It's relatively unknown, but I love it. It's really just a string look-up thingy, and does no form validation messages (one <em>sine qua non </em> for more serious Rails app). On the other hand, installation is only a "script/plugin install" away, and its documentation is easy and short (making gettext for Ruby/Rails work is an ordeal partly because of scant documentation and no-brainer how-to's). </p>
<p>Two things to bear in mind, though. First, this plug-in doesn't scale up well--it does not have a string collector as powerful as gettext's. Second, it allows the use of Ruby lambda for more delicate string handling, which is addictive. The first thing lets you know when you need to use gettext, and the second reminds you that you need to think about the migration path if your project eventually grows. Refraining from the lambda magic will make things a little bit easier.</p>lukhnosnoreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-34039468042069504882007-04-08T18:39:00.000+08:002007-04-08T19:00:43.840+08:00YAPC::Asia 2007 紀行<p><a href="http://tokyo2007.yapcasia.org/">YAPC Asia 207</a> 圓滿落幕,感謝 <a href="http://shibuya.pm.org/">Shibuya.pm</a> 出人出力辨了這麼好的
研討會。我所演講的主題:<a href="http://code.google.com/p/asynapse/">Asynapse</a>,雖然仍是個有點模糊、難以界定的概
念,不過,在提供了適當的 Demo 的情況之下,也得到了 Catalyst 的
<a href="http://search.cpan.org/~jrockway/">Jonathan Rockway</a>、Jifty 的 <a href="http://fsck.com/">Jesse Vincent</a>、Amazon EC 的
<a href="http://tokyo2007.yapcasia.org/speakers/emerson_mills_1.html">Emerson Mills</a> 等人於口頭上的肯定。</p>
<p><a href="http://code.google.com/p/asynapse/">Asynapse</a> 目前還仍在實驗當中,不過其各項目的算是已經成功地證實其可
行。像是與以 REST 實做與 ActiveRecord 相仿的資料介面「AsynapseRecord」,
以 JSON 做為載體來進行 REST API 呼叫的「AsynapseTextDiff」,以及以
[Interface Builder] 做為刻畫 WebUI 工具的「AsynapseInterface」。這些實
驗都在近日成功完成,順利的話,亦將進行包裝,釋出給開發人員所使用的版本。</p>
<p>讀者如果有興趣,除了 <a href="http://blog.handlino.com/">Handlino Blog</a> 之外,亦可持續追蹤 <a href="http://code.google.com/p/asynapse/">Asynapse 專
案頁面</a>,將不定期出現各更新的訊息。</p>gugodhttp://www.blogger.com/profile/05751977651215517690noreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-33043664624846551182007-03-13T11:48:00.000+08:002007-03-13T11:49:00.701+08:00服務項目和多提供以下服務:<ul>
<li>網頁標準設計</li>
<li>Frontend engineering</li>
<li>網頁應用程式開發,使用 Ruby on Rails 或 Perl</li>
<li>桌面應用程式開發</li>
<li>訓練與顧問服務</li>
</ul>lukhnosnoreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-43967409177899035362007-03-13T10:36:00.000+08:002007-03-17T11:34:57.866+08:00關於和多<p>和多是由三位朋友組成的工作室,我們的強項包括網頁標準設計(standards-compliant web design)以及軟體開發。我們三人長期參與開放源碼社群,將我們的力量回饋給軟體開發和標準設計領域。</p>
<p>好軟體不容易。好設計不簡單。我們希望憑藉專業,幫您鋪平沿路的障礙,為大家帶來好用又快樂的產品。</p>
<p>聯絡信箱:naimu at handlino dot com</p>lukhnosnoreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-62675045593576759212007-03-12T21:35:00.000+08:002007-03-13T11:48:06.440+08:00Our ServicesWe offer these services to our clients:<ul><li>Standards-complient web design</li><li>Frontend engineering (everything about UI)</li><li>Web application development using Ruby on Rails or Perl</li><li>Desktop application development</li><li>Training and consultancy services</li></ul>HappyDesignernoreply@blogger.comtag:blogger.com,1999:blog-2561090742278357547.post-7925638082041947632007-03-12T21:22:00.000+08:002007-03-17T11:37:01.888+08:00About Us<p>What can we do for you? If you are looking for good web application design, Handlino is here to help.</p>
<p>We have come together to found Handlino in December 2006. We are experienced in application development (web and desktop) and standards-compliant web design, and we are engaged in open source communities. Our portfolio covers a wide range of application design and development along with numerous open source contributions. Our tools of trade include CSS, JavaScript, Ruby on Rails, Perl, and C++/Objective-C.</p>
<p>Good design is hard. Good code is hard. Let us lower the bar for you and your users, and make good services enjoyable and enjoyable services superb.</p>
<p>Contact Email: naimu at handlino dot com</p>HappyDesignernoreply@blogger.com