Ruby 动态生成代码

Ruby 动态生成代码,ruby,dynamic,methods,eval,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_m

我有一大堆结构类似的方法,每一种方法看起来都像这样:

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
我有我的方法2、3、4等等。我要做的是避免为我所有的方法都键入这些内容,因为大多数代码都是重复的。我只想键入在方法1、2、3、4等中实际变化的代码位

我尝试使用eval(),它可以工作,当然会使我所有的方法都干涸,但让我感到不舒服。基本上,我有一个helper方法,它接受键-值对,键是“上下文”,值是指定为字符串的“语句”:

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
现在,我的单个方法可以是超级干式的,只需调用helper方法并传入代码字符串:

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

同样,在字符串中键入代码块让我感到不舒服。如果您能以另一种方式提供帮助,我们将不胜感激

您需要使用动态方法:

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

您可以通过使用
instance_eval
来改进这一点,它只需在您传递它的块内更改
self
(同时
String#to_sym
避免对散列键进行求值)。您还可以让helper方法定义方法本身,这会稍微缩短使用时间

def 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" }
)

代码中重复的分支告诉我,您的类可以使用一些重构来消除对多个if语句的需要。听起来您的类需要委托给另一个类来实现特定的功能。虽然我不知道您的类看起来像什么,或者它的意图是什么,但下面是一个示例,您可以将其应用到代码中,这样就根本不需要生成动态方法

假设的
顺序
类,带有重复的if语句 考虑这个
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 stations,并将这些计算委托给
折扣类

重构
Order
类以利用委托方法 首先,让我们清理一下
订单
类,然后我们将实现一个
折扣
类:

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
类现在实际上可以无限扩展。
订单#订单类型
值决定折扣。现在,让我们定义我们的
折扣

实现
折扣
类 根据我们的(假)业务规则,只有三种折扣:

  • 折扣20%,2014年底到期
  • 50%折扣,截止2014年3月底
  • 0%折扣(无折扣),从今天起100年内到期,基本上意味着它永远不会过期
  • 我们不希望人们创建任意折扣,因此让我们将
    折扣
    实例限制为仅使用私有构造函数定义的实例,然后为每种折扣声明静态方法:

    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
    
    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.half_off
    Discount.twenty_percent_off
    Discount.default_discount
    
    考虑到
    Order#Order(u type
    用于确定折扣,我们通过
    Order#折扣
    基于
    Order#Order(u type
    返回适当的
    discount
    实例进行仿真。此外,我们通过定义自己的折扣来防止人们玩系统游戏,并且所有折扣的逻辑都在一个类中

    order = Order.new
    order.order_type = 1
    puts order.discount_amount # -> .2
    
    order = Order.new
    order.order_type = 2
    puts order.discount_amount # -> .5
    
    您可以使用子分类创建更具体的业务逻辑,例如“随机”折扣:

    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
    
    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
    
    类折扣
    受保护的\u类\u方法:新建
    ...
    def self.random
    @随机折扣| |=随机折扣。新(无)
    结束
    类别折扣<折扣
    def量
    兰特/2
    结束
    结束
    结束
    
    现在
    折扣.random.amount
    每次输出不同的折扣。可能性变得无穷无尽

    这如何适用于你的问题
    重复if语句的存在意味着你的类做得太多了。它应该委托给另一个专门处理这些代码分支之一的类。您不必在运行时操纵Ruby中的方法来实现这一点。这太“神奇”了,让新开发人员感到困惑。通过我上面概述的方法,您可以得到这些折扣是什么的强类型定义,并且您可以让每个类专注于一个任务(不,如果正确使用,“强类型”在Ruby中不是一个四个字母的单词)。您可以获得对象之间定义良好的关系、更易于测试的代码以及业务规则的强大实施。所有这些都没有“魔力”。

    缺少信息。到目前为止,我不知道为什么不调用处理相同代码并接受参数的方法。或者创建一个实用程序类来为您实现这一点,并允许以某种方式基于(最有可能的)类型进行子类化或方法调度。到目前为止,我不认为需要任何过于动态的东西,这往往会使事情变得相当复杂。是的,如果不提供
    #在我的方法#u 1、2、3、4等中保持不变的代码。#在我的方法#u 1、2、3、4等中变化的代码。
    不清楚如何清理它。我不会使用
    eval
    方法#u missing
    。我会非常谨慎地使用任何元编程,因为它会使代码的可读性大大降低。你应该可以使用其他的Ruby结构。我不明白这个问题。例如,对于
    params[:user_id]
    #在我的_方法_1、2、3、4等中保持不变的代码。
    可以放入每个方法1…4调用的方法中的东西?我建议你提供