2010-02-10 53 views
15

我有一個類和一個哈希。我怎樣才能讓哈希的成員動態地成爲類的方法,並且使用方法名作爲鍵?如何使用散列鍵作爲類的方法?

class User 
    def initialize 
    @attributes = {"sn" => "Doe", "givenName" => "John"} 
    end 
end 

例如,我想能夠有以下輸出Doe

u = User.new 
puts u.sn 
+4

請務必查看OpenStruct(標準庫中的struct.rb)。這與你所要求的有點不同:它允許OpenStruct的任何方法調用成爲一個訪問器,無論它是否已經被定義。但是它不需要編寫代碼,有時候可以加上代碼。 – 2010-02-10 22:22:28

回答

14
def method_missing(name, *args, &blk) 
    if args.empty? && blk.nil? && @attributes.has_key?(name) 
    @attributes[name] 
    else 
    super 
    end 
end 

解釋:如果你調用一個方法,它不存在,method_missing方法被調用,方法的名稱作爲第一個參數,然後是給定的方法和塊的參數(如果給出的話)。

在上面我們說如果一個沒有定義的方法在沒有參數的情況下被調用,並且沒有一個塊並且這個散列有一個方法名作爲關鍵字的條目,它將返回該條目的值。否則,它將照常進行。

+0

我必須添加它才能將它更改爲:name.to_s在這兩個地方,但它讓我在我想要的地方!謝謝:) – Michael 2010-02-10 22:10:36

+0

哦,對,我沒有注意到你使用字符串作爲散列鍵。 – sepp2k 2010-02-10 22:12:29

+0

此外,您還應該重寫'responds_to?'以匹配,因爲您的課程現在也「響應」該特定消息。 – 2012-01-17 15:44:20

4

sepp2k的解決方案是要走的路。但是,如果你的@屬性初始化後,永遠不會改變,你需要速度,那麼你可以做這樣:

class User 
    def initialize 
    @attributes = {"sn" => "Doe", "givenName" => "John"} 
    @attributes.each do |k,v| 
     self.class.send :define_method, k do v end 
    end 
    end 
end 

User.new.givenName # => "John" 

這會產生提前的所有方法...

3

其實severin有更好的主意,只是因爲method_missing的使用是一個不好的做法,並非所有的時間,但大部分時間。

severin提供的代碼存在一個問題:它返回已傳遞給初始值設定項的值,因此無法對其進行更改。我建議你一點點不同的方法:

class User < Hash 
    def initialize(attrs) 
    attrs.each do |k, v| 
     self[k] = v 
    end 
    end 

    def []=(k, v) 
    unless respond_to?(k) 
     self.class.send :define_method, k do 
     self[k] 
     end 
    end 

    super 
    end 
end 

讓我們檢查一下:

u = User.new(:name => 'John') 
p u.name 
u[:name] = 'Maria' 
p u.name 

,你也可以用結構做到這一點:

attrs = {:name => 'John', :age => 22, :position => 'developer'} 
keys = attrs.keys 

user = Struct.new(*keys).new(*keys.map { |k| attrs[k] }) 

讓我們測試一下:

p user 
p user.name 
user[:name] = 'Maria' 
p user.name 
user.name = 'Vlad' 
p user[:name] 

甚至是OpenStruct,但是是ca reful它不會產生方法,如果它已經有它在實例方法,您可以通過使用OpenStruct.instance_methods尋找那些(因爲類型的使用,我現在使用第二種方法):

attrs = {:name => 'John', :age => 22, :position => 'developer'} 
user = OpenStruct.new(attrs) 

是的,這樣容易:

user.name 
user[:name] # will give you an error, because OpenStruct isn't a Enumerable or Hash 
+0

你的解釋和擴展例子真的很棒。謝謝! – 2013-03-07 16:55:43

29

只需使用OpenStruct:

require 'ostruct' 
class User < OpenStruct 
end 

u = User.new :sn => 222 
u.sn 
1

您可以 「借用」 的ActiveResource這一點。它甚至處理嵌套哈希和分配:

require 'active_resource' 
class User < ActiveResource::Base 
    self.site = '' # must be a string 
end 

用法:

u = User.new "sn" => "Doe", "givenName" => "John", 'job'=>{'description'=>'Engineer'} 
u.sn # => "Doe" 
u.sn = 'Deere' 
u.job.description # => "Engineer" 
# deletion 
u.attributes.delete('givenName') 

注意,美。job是一個User :: Job - 這個類是自動創建的。 分配給嵌套值時有一個問題。你不能只分配一個哈希,但必須在適當的類包起來:

u.job = User::Job.new 'foo' => 'bar' 
u.job.foo # => 'bar 

不幸的是,當你想添加不具有對應的級嵌套哈希,這是醜陋的,因爲你必須強制AR從哈希創建類:

# assign the hash first 
u.car = {'make' => 'Ford'} 
# force refresh - this can be put into a method 
u = User.new Hash.from_xml(u.to_xml).values.first