动态生成代码

时间:2015-02-04 17:33:44

标签: ruby dynamic methods eval

我有一大堆类似结构的方法,每个方法都是这样的:

def my_method_1
  if params[:user_id]
    #code that stays the same across my_method_1, 2, 3, 4, etc.
    #code that varies across my_method_1, 2, 3, 4, etc.
  elsif params[:tag_id]
    #code that stays the same across my_method_1, 2, 3, 4, etc.
    #code that varies across my_method_1, 2, 3, 4, etc.
  else
    #code that stays the same across my_method_1, 2, 3, 4, etc.
    #code that varies across my_method_1, 2, 3, 4, etc.
  end
end

我有my_method_2,3,4等。我试图做的是避免为我的每个方法键入所有这些,因为大多数代码都是重复的。我只想输入在方法1,2,3,4等实际变化的代码位。

我对此的尝试使用了eval(),它起作用,并且肯定会干掉我所有的方法,但让我感到不舒服。基本上,我有一个辅助方法,它接受键值对,键是" context",值是"语句"指定为字符串:

def helper_method
  hash.each do |context, statement|
    if params[eval(":#{context}_id")]
      #code that stays the same
      eval(statement)
      return
    end
  end
  eval(hash[:none])
end

现在我的个人方法可以超干,只需调用辅助方法并传入代码字符串:

def my_method_1
  helper_method(
    user:   '#code that varies',
    tag:    '#code that varies',
    none:   '#code that varies'
  )
end

再次,在字符串中键入代码块会让我感到不舒服。任何有关这种方式的帮助将非常感谢!

3 个答案:

答案 0 :(得分:1)

您可以使用instance_eval来改进这一点,self只会更改您传递的块内的String#to_symdef self.define_structured_method(name, hash) define_method(name) do hash.each do |context, block| if params["#{context}_id".to_sym] #code that stays the same instance_eval &block return end end instance_eval &hash[:none] end end define_structured_method(:my_method_1, user: proc { puts "user code" }, tag: proc { puts "tag code" }, none: proc { puts "else code" } ) 也会避免使用散列键的eval)。你也可以让helper方法定义方法本身,使用起来要短一些。

{{1}}

答案 1 :(得分:1)

代码中的重复分支告诉我,您的类可以使用一些重构来消除对多个if语句的需要。听起来您的类需要委托给另一个类来获取特定功能。虽然我不知道您的课程是什么样的,或者它的意图,但以下是您可以应用于您的代码的示例,以便您不需要生成动态方法。

具有重复if语句的假设Order

考虑这个Order类,它具有多个类似的if语句:

class Order
  attr_accessor :order_type

  def discount_amount
    if order_type == 1
      .2
    elsif order_type == 2
      .5
    else
      0
    end
  end

  def discount_end_date
    if order_type == 1
      DateTime.new(2014, 12, 31)
    elsif order_type == 2
      DateTime.new(2014, 3, 31)
    else
      # Always expires 100 years from now
      DateTime.new(DateTime.now.year + 100, 1, 1)
    end
  end
end

我们有三个折扣:2014年底到期的20%折扣; 50%将于2014年3月底到期。最后,0%的默认折扣将在未来100年内到期。让我们清除它以删除if-statments,然后委托Discount类进行这些计算。

重构Order类以使用委托方法

首先,让我们清理Order类,然后我们将实现Discount类:

class Order
  attr_accessor :order_type

  def discount
    @discount ||=
      if order_type == 1
        Discount.twenty_percent_off
      elsif order_type == 2
        Discount.half_off
      else
        Discount.default_discount
      end
  end

  def discount_amount
    discount.amount
  end

  def discount_end_date
    discount.end_date
  end
end

干净整洁。 Order对象需要Discount个对象来获取折扣金额和结束日期。 Order类现在几乎可以无限扩展,因为计算折扣的逻辑完全被卸载到另一个类。 Order#order_type值确定折扣。现在,让我们定义我们的Discount类。

实施Discount

根据我们的(虚假)商业规则,只有三个折扣:

  1. 20%-off,2014年底到期
  2. 50%-off,即2014年3月底到期
  3. 0%-off(无折扣)总是从今天起100年过期,基本上意味着它永不过期
  4. 我们不希望人们创建任意折扣,所以让我们将Discount实例限制为仅使用私有构造函数定义的实例,然后为每种类型的实例声明静态方法折扣:

    class Discount
      private_class_method :new
    
      def self.default_discount
        @@default_discount ||= new(0)
      end
    
      def self.half_off
        @@half_off_discount ||= new(.5, DateTime.new(2014, 3, 31))
      end
    
      def self.twenty_percent_off
        @@twenty_percent_off ||= new(.2, DateTime.new(2014, 12, 31))
      end
    
      def initialize(amount, end_date = nil)
        @amount = amount
        @end_date = end_date
      end
    
      def amount
        @amount
      end
    
      def end_date
        @end_date ||= DateTime.new(DateTime.now.year + 100, 1, 1)
      end
    end
    

    尝试运行Discount.new(...)会引发错误。我们只有三个可用的折扣实例:

    Discount.half_off
    Discount.twenty_percent_off
    Discount.default_discount
    

    鉴于Order#order_type用于确定折扣,我们会使用Order#discount模拟此问题,并根据Discount返回正确的Order#order_type实例。此外,我们通过定义自己的折扣来阻止人们游戏系统,并且所有折扣的逻辑都在一个类别中。

    order = Order.new
    order.order_type = 1
    puts order.discount_amount # -> .2
    
    order = Order.new
    order.order_type = 2
    puts order.discount_amount # -> .5
    

    您可以使用子类来创建更具体的业务逻辑,例如" random"折扣:

    class Discount
      protected_class_method :new
    
      ...
    
      def self.random
        @random_discount ||= RandomDiscount.new(nil)
      end
    
      class RandomDiscount < Discount
        def amount
          rand / 2
        end
      end
    end
    

    现在Discount.random.amount每次输出不同的折扣。可能性变得无穷无尽。

    这是如何适用于您的问题

    重复的if语句的存在意味着你的班级做得太多了。它应该委托给另一个专门研究其中一个代码分支的类。你不应该在运行时在Ruby中操作方法来实现这一点。它太多&#34;魔术&#34;并且让新开发者感到困惑。通过上面概述的方法,您可以获得这些折扣的强类型定义,并且您将每个课程都集中在一个任务上(并且不,#34;强类型&#34;在Ruby中不是四个字母的单词)使用得当)。您可以获得对象之间定义良好的关系,更易于测试的代码以及强大的业务规则实施。一切都没有&#34;魔术。&#34;

答案 2 :(得分:0)

您需要使用动态方法:

def method_missing(method_name)
    if method_name.to_s =~ /context_(.*)/
       #Some code here that you want
       # ...
    end
end

def respond_to_missing?(method_name, include_private = false)
   method_name.to_s.start_with?('context_') || super
end