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

2007/7/28

massage: A SPA plugin for Rails

SPA Stands for Single Page Application, 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: massage. (We are really enjoying it when the name feels just right, aren't we.)

The massage project is now on Rubyforge. To try it, first you would need to install it as a Rails plugin like this:

script/plugin install http://massage.rubyforge.org/svn/trunk/

Then, in the controller that you want to make it work like SPA, add:

spa :only => [:index, :settings]    # or :except => [...]

This would make index and settings 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 link_to to link_to_remote too. By default, it uses RJS to update an element with id "content" (better to be a div for best look). If you need to change it to, say, "my-spa-arena", you can say:

spa :only => [:index, :settings], :update_element => "my-spa-arena"

And of course, make sure there's is such an element inside your layout, otherwise RJS would just pop-up an error message.

massage project is still at bleeding edge, if you find it broken please contact us, you can leave comments here.

2007/7/11

Chatlino 0.10 Released

After months of development, we're very thrilled to release our humble work of this simple Rails Application: Chatlino. It's an OpenID-based Chatroom application. Please download Chatlino from Chatlino Google code project page, and give it a try.

Chatlino started as a sub-project of a production website YouHolder. It's an open source counter-part of the Chatroom component of YouHolder. We appreciate ICE Technology 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.

This initial release of Chatlino is by default, using Juggernaut as its push solution. You may re-configure it to use iPush, which dramaticly improves the performance of message delivery, and capacity. Please also visit Push4Free and ICE Technology for more information about iPush Technology.

This initial release is still in it's early, bleeding edge. Please don't hestitate to send us comments 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 .

2007/6/29

cache_fu, memcached, 與 Locomotive 的問題

近兩個星期開始替專案中大大小小的部份加上 memcached 的支援,配合了 cache_fu 這個方便的 Rails plugin,寫起來順利不少。但在 Mac 上執行起來倒是碰到一個問題。由於使用 Mac,最方便的 Rails 懶人安裝包,便是使用 Locomotive,以及它內附的一大票 rubygems。但我們使用 cache_fu 的 model 裡,卻老是出現這樣的錯誤訊息:

protected method `send' called for #<MemCache:0x3388db4>

基本上就是說,MemCache 這個 class 的 send 方法是被定義為 protected method,因此不能呼叫。怪哉,send 在 ruby 語言中,乃是非常基層的方法,每個物件皆有,怎有可能被定義為 protected ?一查之下果然發現元兇,乃是 Ruby-MemCache 這包 rubygem。

Locomotive 裡附了兩套關於 memcached 的 rubygem,其一是 Ruby-MemCache,其二則是 memcache-client。也許是為了方便而兩者皆附,但 cache_fu 所需要的,其實是後者。但在兩者皆有的狀態下,require "memcache"永遠是載入 Ruby-MemCache 裡面的 memcache.rb。而在此檔中,竟然將 send 方法蓋寫。不知該說是其作者大膽豪放,或是該說其迷糊不清,著實令人稱奇。

於是解決這問題的方法便呼之而出了:

gem uninstall Ruby-MemCache

也就是,將 Locomotive 裡附的 Ruby-MemCache 這包 rubygem 移除便是。

可見,若不希望自已寫的函式庫被反安裝,相容性很重要啊。

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!