【问题标题】:DRY way to define multiple virtual attributes in rails在 Rails 中定义多个虚拟属性的 DRY 方法
【发布时间】:2012-12-12 15:12:45
【问题描述】:

我正在编写一个作为定制测量表单的应用程序。

customers 模型有很多属性以毫米为单位的整数形式存储在数据库中。 由于此应用程序将在欧洲和美国使用,因此我将使用虚拟属性向用户显示英寸和厘米版本的数据。

例如,对于我的模型中的客户身高:

  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 %>

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

有没有干燥的方法可以缩短它?

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 dry


    【解决方案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

    【讨论】:

    • 感谢您的回答,但可能我的问题并不清楚。我的问题是我的模型有很多属性,在我的模型中我必须为每个属性定义虚拟属性。我发布的 sn-p 是针对身高属性的,但我还有更多:neck_circonference、sleeve_lenght、wist_circumference、bust_circumference 等......
    • 这看起来很棒。它只是工作,但我需要研究一下,因为我不明白这一切。同样使用此示例,我需要为每个属性(甚至为零)预先分配一个值,否则在第一个创建操作中它告诉我无法对 nil 类执行操作。分配零会阻止我添加一个功能,告诉用户他已完成表单的百分比。无论如何,这是迄今为止最好的解决方案。
    【解决方案2】:

    我会让 gem 或 javascript 进行转换。 ruby-units 之类的 gem 可能有点矫枉过正,但可以为您可能想要的任何转换提供持久的解决方案。

    为了让 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');
    

    【讨论】:

    • 谢谢。这种方法看起来很有趣,但是由于我对 js 的了解非常有限,所以我更喜欢一种更偏激的方法。这将使我的代码更易于维护。我之前也研究过 ruby​​-units。这个 gem 做到了它所说的,并提供了很高的转换精度。无论如何,它将我的数据存储在字符串中(例如“23 cm”)而不是整数,如果我需要直接在数据库上操作,从长远来看这可能会成为一个问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-06-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-11
    相关资源
    最近更新 更多