DRY在rails中定义多个虚拟属性的方法

时间:2012-12-12 15:12:45

标签: ruby-on-rails ruby-on-rails-3 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 %>

由于我说我有很多属性,我的客户模型文件变得非常长且非常容易出错。

是否有一种干燥方法可以缩短它?

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:0)

我会让gem或javascript进行转换。像ruby-units这样的宝石可能有点矫枉过正,但为你想要的转换提供了持久的解决方案。

为了让javascript进行转换,这里有一些你可以添加到application.js的东西。您还可以使用“基线”概念在rails中干燥您的方法。

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');