顯示具有 asynapse 標籤的文章。 顯示所有文章
顯示具有 asynapse 標籤的文章。 顯示所有文章

2007/5/19

has_ :favorite, :recipes

Lately gugod and I have stumbled on a situation where the famous has_many :through wasn't of much help. So we have come up with our own has_ thingy. That's right, it's called has_.

The Problem

The classic example of has_many :through is that in AWDwR 2nd ed: an object of class X has many objects of class Y through 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.

has_many :through is a wonderful invention. It beats the needs for another famous habtm idiom, and delivers what it promises... so long as you follow its naming convention.

But life is often not perfect. The biggest constraint for has_many :through is that both the :class and :foreign_key options available to has_many are not there. Now that's a bad thing.

To explain why that is so, just look at what we need:

class User < ActiveRecord::Base
  has_many :recipes
  has_many :recipes, :through => :favorite_recipes   # d'oh!
  ...
end

So here a User has many recipes (probably of his or her own creation) and many favorite recipes (probably created by others). Because of the constraint mentioned about, this is a no-goer:

has_many :fav_recipes, :class => "Recipe", :through => :favorite_recipes, :foreign_key => :recipe_id

We had tried to fall back on the less elegant way

has_many :favorite_recipes

Then we collected the real recipe id's with this:

@fav_recipes = me.favorite_recipes.collect { |r| r.recipe }

Very soon, we were collecting these kind of proxy objects all over our models and controllers.

What about has_and_belongs_to_many?

In theory we could have used has_and_belongs_to_many :foo, :join_table => :blah 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 has_and_belongs_to_many Recipes. But hey, since when did we want to have a Recipe that has many users?

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:

class FavoriteRecipe < Favorite
  belongs_to :recipe, :class_name => "Recipe", :foreign_key => "item_id"
end

And has_and_belongs_to_many could result in such behemoth in User:

  has_and_belongs_to_many :fav_recipes, :join_table => :favorite, :class => "Recipe", :associated_foreign_key => :item_id, :conditions => { :type => "FavoriteRecipe" }

That is not elegant at all.

The Workaround

The more natural solution would be, of course, to come up with a method like this:

class User < ActiveRecord::Base
  has_many :_favorite_recipes, :class => "FavoriteRecipes"
  def favorite_recipes
    _favorite_recipes.collect { |r| r.recipe }
  end
  ...

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.

So gugod asked: is there a better way to do this? Could we possible come up with some has_blah that does the magic for us? Such as writing the "def favorite_recipes" for us ?

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 has_, and with it you can have as many favorite items as you want it.

The Code: has_

module HasUnderline
  def has_(prefix, item)
    prefix = prefix.to_s
    item = item.to_s

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

To use the module in your ActiveRecord model class, simply aggregate it into your class object with:

class User < ActiveRecord::Base
  extend HasUnderline
  has_ :favorite, :recipes
  has_ :favorite, :cooks
  has_ :favorite, :spices
  has_ :favorite, :shows
  ...

That's what we call elegant.

Voilà. So now we are happy since a User has_ many favorite recipes, favorite cooks, favorite spices, favorite shows, and so on and so forth.

Voot, that's eval! (à Frau Farbissina)

class_eval is one of Ruby's many evil vices (and hence the name) and is recommended by books like Ruby for Rails. One advantage of class_eval over method_missing is that the class in question will have a true method, whereas the dynamic dispatched behavior in method_missing cannot be known in advance.

We have just put that nifty snippt into our Asynapse library, and we're expecting more such idioms will go into this project.

Enjoy and stay tuned!

2007/5/12

Javascript localization

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. Jemplate is a full javascript template library. Extjs is a full-js UI builder. In these libraries, more or less, we need to put string literals in or Javascript code.

So we've done our way localization strings in Javascript: Asynapse.Localization. It's now put in our Asynapse 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.

It's almost as simple as this:

  # Initialize
Asynapse.Localization.init({
"lang": "zh_TW",
"dict_path": "/javascripts/loc"
})

# use it
alert( _("Nihao") )

We use JSON as our dictionary format. It looks like this:

{
 "Nihao": "你好",
 "Good bye": "再見"
}

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 Asynapse concept.

As we allAsian 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.

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.

That now feel more Asynapse, we wins at both client and server side coding.

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)

2007/4/13

Asynapse 初版釋出

在經過了兩天的黑客鬆之後,將釋出 Asynapse 的方式定了下來,於是率先釋出 Perl 版的 Asynapse。有興趣的讀者可前往 Asynapse 專案下載頁,或是利用 CPAN 下載、安裝。

更新:JavaScript 版本也已經釋出,可前往 JSAN 下載

此釋出版為開發者版本,相關的文件請參詳 Asynapse::Record Perl 模組的文件。

2007/4/8

YAPC::Asia 2007 紀行

YAPC Asia 207 圓滿落幕,感謝 Shibuya.pm 出人出力辨了這麼好的 研討會。我所演講的主題:Asynapse,雖然仍是個有點模糊、難以界定的概 念,不過,在提供了適當的 Demo 的情況之下,也得到了 Catalyst 的 Jonathan Rockway、Jifty 的 Jesse Vincent、Amazon EC 的 Emerson Mills 等人於口頭上的肯定。

Asynapse 目前還仍在實驗當中,不過其各項目的算是已經成功地證實其可 行。像是與以 REST 實做與 ActiveRecord 相仿的資料介面「AsynapseRecord」, 以 JSON 做為載體來進行 REST API 呼叫的「AsynapseTextDiff」,以及以 [Interface Builder] 做為刻畫 WebUI 工具的「AsynapseInterface」。這些實 驗都在近日成功完成,順利的話,亦將進行包裝,釋出給開發人員所使用的版本。

讀者如果有興趣,除了 Handlino Blog 之外,亦可持續追蹤 Asynapse 專 案頁面,將不定期出現各更新的訊息。