2012-12-12 53 views
1

我正在編寫一個充當裁縫測量窗體的應用程序。DRY在軌道中定義多個虛擬屬性的方法

客戶模型具有很多以毫米爲單位的整數存儲在數據庫中的屬性。 因爲這個應用程序將在歐洲和美國使用。我將使用虛擬屬性向用戶顯示數據的英寸和釐米版本。

例如,對於客戶的高度我在我的模型有這樣的:

def height_in_cm 
    height/10 
    end 

    def height_in_cm=(height) 
    self.height = height.to_f * 10 
    end 

    def height_in_in 
    height * 0.039370 
    end 

    def height_in_in=(height) 
    self.height = height.to_f/0.039370 
    end 

這在我_form觀點:

<% if @customer.measure_unit.eql? "imperial" %> 
    <%= f.input :height_in_in %></br> 
    <% else %>  
    <% if @customer.measure_unit.eql? "metric" %> 
     <%= f.input :height_in_cm %></br> 
    <% end %>  
    <% end %> 

因爲正如我說我有很多的屬性,我的客戶模型文件變得非常漫長,非常容易出錯。

是否有幹縮短的方法?

回答

1

封裝了邏輯是這樣的:

def display_height 
    case measure_unit 
    when 'imperial' then height_in_in 
    when 'metric' then height_in_cm 
    else height_in_in 
    end 
end 

那麼你的視圖可以僅僅是這樣的:

<%= @customer.display_height %> 

如果您使用的是大量的模型,這些相同的轉換方法,提取出來到一個像這樣的模塊:

module HeightConversions 

    def height_in_cm 
    height/10 
    end 

    def height_in_in 
    height * 0.039370 
    end 

    def display_height 
    case measure_unit 
    when 'imperial' then height_in_in 
    when 'metric' then height_in_cm 
    else height_in_in 
    end 
    end 

end 

並在需要的模型中包括它:

class Customer 
    include HeightConversions 
end 

編輯:

好吧,也許你需要更多的東西是這樣的:

%w(neck waist arm).each do |name| 
    self.class_eval do 

     define_method :"#{name}_in_cm" do 
     self.send(name)/10 
     end 

     define_method :"#{name}_in_cm=" do |n| 
     self.send("#{name}=", (n.to_f * 10)) 
     end 

     define_method :"#{name}_in_in" do 
     self.send(name) * 0.03 
     end 

     define_method :"#{name}_in_in=" do |n| 
     self.send("#{name}=", (n.to_f/0.039370)) 
     end 

    end 
    end 

這通常被稱爲元編程。這裏有一個不同的方法來做到這一點在Rails的不錯的簡潔文章: http://www.trottercashion.com/2011/02/08/rubys-define_method-method_missing-and-instance_eval.html

+0

謝謝您的回答,但也許我的問題不明確。我的問題是,我的模型有很多屬性,在我的模型中,我必須爲每個屬性定義虛擬屬性。我張貼的片段是爲高度屬性,但我有更多:neck_circonference,sleeve_lenght,waist_circumference,bust_circumference等... – TopperH

+0

@TopperH編輯回答一個元編程示例 – Unixmonkey

+0

這看起來不錯。它只是工作,但我需要研究一點,因爲我不明白這一切。同樣使用這個例子,我需要爲每個屬性預設一個值(甚至爲零),否則在第一個創建操作中它告訴我不能對無類操作。分配零可以防止我添加一個功能,告訴用戶他已完成的表單的百分比。無論如何,這是迄今爲止最好的解決方案。 – TopperH

0

我會讓一個寶石或JavaScript做轉換。諸如ruby-units之類的寶石可能會過度殺傷,但爲您可能想要的任何轉換提供持久的解決方案。

爲了讓javascript進行轉換,可以將這些內容添加到application.js中。你也可以使用「基線」概念來幹你的方法。

function convert_units(input, input_unit, output_unit) { 
// Used to convert all units to similar intermediary unit. I used meters. 
var inches_per_meter = 39.3701; 
var sixteenths_per_meter = 629.9216; 
var cm_per_meter = 100; 
var mm_per_meter = 1000; 

var intermediary = 0; //in meters 

switch(input_unit) 
{ 
    case: "mm": intermediary = input * mm_per_meter; 
    break; 
    case: "cm": intermediary = input * cm_per_meter; 
    break; 
    case: "meter": intermediary = input; 
    break; 
    case: "inches": intermediary = input * inches_per_meter; 
    break; 
    case: "sixteenths": intermediary = input * sixteenths_per_meter; 
    break; 
    default: return "Input unit not correctly accomodated"; 
} 

switch(output_unit) 
{ 
    case: "mm": return intermediary/mm_per_meter; 
    break; 
    case: "meter": return intermediary; 
    break; 
    case: "inches": return intermediary/inches_per_meter; 
    break; 
    case: "sixteenths": return intermediary/sixteenths_per_meter; 
    break; 
    case: "cm": return intermediary/cm_per_meter; 
    break; 
    default: return "Output unit not correctly accomodated"; 
} 
} 

然後在你的看法

convert_units(@customer.height, 'inches', 'cm'); 
+0

謝謝。這種方法看起來很有趣,但由於我對js的瞭解非常有限,所以我更傾向於採用更加鐵路的方法。這將使我的代碼更容易維護。此外,我還研究過紅寶石單元。這個寶石按照它的說法提供了很好的轉換精度。無論如何它存儲我的數據字符串(如「23釐米」)而不是整數,如果我需要直接在數據庫上操作,長期來看可能會成爲一個問題。 – TopperH