Ruby Tuesday #15 開放報名

今年的最後一場,帶給大家與 Ruby/Rails 比較間接的主題:

* Felix & Mars 圖形資料庫 (neo4j) 在社群網路的應用 Graph Database in Social Network Application,會有圖形資料庫的簡介和 Ruby 程式範例。
* TaopaiC: Be nice to your Designers : Useful tips For Rails developers,會介紹一些 Rails 的前端技巧。

時間是 2010/12/28 週二晚上 PM7~9

地點依舊在果子咖啡,報名網頁在此

Service-Oriented Design and Implement with Rails3 投影片

這是昨晚在 Ruby Tuesday #14 演講的投影片。這次的題目 SOA 其實我在今年初講 Distributed Ruby and Rails 時也有涵蓋過,只是那時候還沒有給出實際的程式範例。這次搭配充滿彈性的 Rails3 有了 Web service 端和 Client side application 程式範例,相信讀者們可以具體的實作出來。

Ruby Tuesday #14 開放報名

要年底了,好久沒辦 Ruby Tuesday 了,該來辦個幾場熱鬧一下。

這一次的主題是 Service-Oriented Design and Implement with Ruby on Rails 3,將講解基本的 SOA 服務導向架構、SOA 設計有什麼優點,以及如何使用 Ruby on Rails 3 來進行實作。這場演講由小弟我 Solo 獨講,請多多指教。

時間是 2010/12/15(週三)晚上七點到九點。請注意這次是辦在週三,不是週二唷 XD

地點依舊在果子咖啡,報名網頁在此

OSDC.TW 2011 演講徵求

一年一度的台灣 Open Source 開發界盛事 OSDC.TW 又來了,日期是 2011/3/26~3/27。
目前正在徵求演講,詳見公告文章,徵稿日期到 1/15/2011。

這次我會幫忙張羅其中的 Ruby 議程,所以如果你有 Ruby 相關的議程想要分享,或是想講但苦於想不到題目,也可以直接跟我聯繫詢問

至於有沒有 RubyConf Taiwan 2011 呢? 這次就不會跟 OSDC.TW 合辦了,目前的計畫是獨立辦在明年的七月或八月。

深入Rails3: ActiveSupport 的 class_attribute

如果你對 Ruby Object Model 稍加認識,就會知道除了 class variable 和 instance variable 之外,還有一種變數叫做 class instance variable,之前我在研究時有撰文解釋過,讀者可以複習一下。

在 Rails3 ActiveSupport Core Extension 中,就有幾個方法是在處理這件事情,讓我們可以很方便地定義存取方法,讓我們來看看。

cattr_* 系列

Ruby 語言本身就有針對 instance variable 提供 attr_accessor, attr_reader, attr_writer 等方法,這些會建立 @ 開頭的實例變數並提供存取方法。而 ActiveSupport 的這個擴充則是針對 class variable 也提供類似的功能,它會建立 @@ 開頭的類別變數及提供存取方法。


class A
  cattr_accessor :x
end
  
class B < A
end
  
A.x = 1
A.x # => 1
B.x = 2
B.x # => 2
A.x # => 2 跟著改了

注意到整個繼承體系 A, B 都共用了 @@x,所以如果改了 B.x,那也會連動 A.x。很多時候,這不是我們要的,例如在 Rails 中所有 Model 都繼承自 ActiveRecord,於是會共用 class variable,如果要各自 Model 需要有自己的 class 屬性就不合用了。所以說認識 class instance variable 可以說是寫 ActiveRecord Plugin 的必備知識 (甚至也有人說 Ruby 的 class variable 設計錯誤,當初就應該把行為設計成 class instance variable 比較實用)

class_attribute

不像 class variable 整個繼承體系共用類別變數,class instance variable 是不同 class 分別獨立的,也就是類別 A 的 class instance variable 和 B < A 的 class instance variable 是獨立的。


class A
  @x = 1
end

class B < A
end

A.instance_eval { @x } # => 1
B.instance_eval { @x } # => nil 因為跟 A 的 @x 是獨立的,不會繼承下來

這個特性讓我們可以實作出真正實用的行為,也就是 “屬性可以繼承,但是如果有修改,不會影響到 parent class 的值”。這就是 ActiveSupport 的 class_attribute 提供的功能:


class A
  class_attribute :x
end
  
class B < A
end
  
A.x = 1
B.x # => 1 繼承自 A
B.x = 2
B.x # => 2
A.x # => 1 不變

最後的 A.x 還是保持本來的值不受影響。

不過使用上有個細節要注意:如果這個值是會變動的結構(物件),例如 Array 或 Hash,那麼 child class 第一次使用時就不適合用 in-place 類型的方法,例如:


A.x = []
B.x << :foo
A.x # => [:foo] 也跟著改了,不對啊啊啊!!
B.x # => [:foo]

要改成用 setter 類型的方法:


A.x = []
B.x += :foo # 第一次設定必須使用 setter 類型的方法
A.x # => [] 不變
B.x # => [:foo]

B.x << :bar
A.x # => []
B.x # => [:foo,:bar]

會造成這種行為的原因是,ActiveSupport 並不是複製 A.x 給 B.x,而是如果 B.x 沒設定,就去讀 A.x (這點跟下述的 class_inheritable_* 用複製的作法就不同) 。

ActiveSupport cattr_* 還提供了 query 是否為 nil 的方法,也就是 A.x? 和 B.x?

最後,ActiveSupport cattr_* 的行為也適用於實例化時,不會影響到 parent class:


A.x = 1
object = A.new
object.x = 2 
object.x # => 2
A.x # => 1 保持不變

class_inheritable_* 系列

ActiveSupport 還有一套古早的 class_inheritable_* 方法,它的作用跟上述的 class_attribute 是差不多的,只是內部的實作不同。是說 class_attribute 是 Rails3 才新寫的,效能較佳,會留著 class_inheritable_* 主要是因為向下相容性(有很多的 Plugins 使用了這個方法)。

參考資料

Rails3: ActiveModel 實作

我在 Rails3 Beta 發佈: 重點導覽 中提到過 ActiveModel,今天讓我們更深入看看什麼時候會用到,以及怎麼使用它。

ActiveModel 定義了一組介面可以與 ActionPack helpers 銜接,也提供了許多現成的 Module 功能。任何 Class 只要實作了 ActiveModel 的介面,然後選配 include 你需要的 Module,就可以與 Rails3 helpers 銜接在一起了。

什麼時候會用到 ActiveModel 呢? 例如你想要換不同的 ORM 而不使用 ActiveRecord,或是你沒有 backend storage 只是想要拿來做其他動作(例如單純的聯絡表單用來寄信)。在 Building Web Service Clients with ActiveModel 這篇投影片中,Paul Dix 也將 ActiveModel 應用在 SOA 架構的 client-side model (就不要用 ActiveResource 吧,用 ActiveModel 配上 HTTP client library 自己寫一個比較合用)。

讓我們來看看 ActiveModel 定義的介面吧,你的 Model 得實作以下方法:

1. to_model, to_key, to_param, persisted?
2. model_name 以及 model_name.human, model_name.partial_path, model_name.singular, model_name.plural
3. valid?, errors 以及 errors[], errors.full_messages

這樣才能與 ActionPack helpers 接起來(這些 helpers 例如 link_to, form_for 等等)。所幸要預設實作這樣介面非常容易,除了 persisted? 之外,ActiveModel 都有提供現成的 Module 可以使用:


class YourModel
    extend ActiveModel::Naming
    include ActiveModel::Conversion
    include ActiveModel::Validations
     
    def persisted?
        false
    end
end

其中 persisted? 的實作得依照你的 backend storage 而定。如果你的 Model 沒有 backend storage,那麼就是 false。
而 ActiveModel::Validations 提供了你熟悉的各種 validation 方法,例如 validates_presence_of, validates_format_of 等等 (除了 validates_uniqueness_of 之外,原因不證自明)。

如果你不想要整套的 validation 框架,也可以不 include ActiveModel::Validations。你可以讓 valid? 恆真,然後使用 ActiveModel::Errors 定義 errors 方法即可滿足介面要求:


class YourModel
    extend ActiveModel::Naming
    include ActiveModel::Conversion

    def valid?() true end



    def errors
    
        @errors ||= ActiveModel::Errors.new(self)
  
    end

    def persisted?
        false
    end
end

以上這些介面 ActiveModel 也有提供 Lint Tests 可以測試,只要 include 它即可:


require 'test_helper'
class YourModelTest < ActiveSupport::TestCase
  include ActiveModel::Lint::Tests
end

以上就是 ActiveModel 介面的最低要求了,透過 ActiveModel 內建的 Naming, Conversion, Validations module 就可以輕易實作出來。ActiveModel 還有提供許多好用的 Module 來擴充,包括有:

就不一一詳述了,詳細的用法請看原始碼及文件說明。最後提一下 MassAssignment。

MassAssignmentSecurity

如果你想要實作 MassAssignment 功能,就是有 YourModel.new(params[:model]) 這樣的介面,你可以這樣實作:


class YourModel
    # ...
    def initialize(attributes = {})
        if attributes.present?
          attributes.each { |k, v| send("#{k}=", v) if respond_to?("#{k}=") }
        end
      end
end

但是提到 MassAssignment,就可能會有 MassAssignment Security 的需求,我們想要保護一些特定的屬性不能透過 Mass Assignment 來設定值。
ActiveModel 提供了 MassAssignmentSecurity 這個 Module 讓我們可以獲得 attr_protected 和 attr_accessible 方法來保護特定屬性。用法如下:


class YourModel
    # ...
    include ActiveModel::MassAssignmentSecurity
    
    def initialize(attributes = {})
        if attributes.present?
          sanitize_for_mass_assignment(attributes).each { |k, v| send("#{k}=", v) if respond_to?("#{k}=") }
        end
    end
end

參考資料