这个define_method用例是否过于复杂?

时间:2012-08-25 16:43:04

标签: ruby oop metaprogramming

我现在已经反对这个约三天了。我创建了一个模拟html页面的类,并告诉黄瓜步骤定义填充表单数据的位置:

class FlightSearchPage

  def initialize(browser, page, brand)
    @browser = browser
    @start_url = page

    #Get reference to config file
    config_file = File.join(File.dirname(__FILE__), '..', 'config', 'site_config.yml')

    #Store hash of config values in local variable
    config = YAML.load_file config_file

    @brand = brand #brand is specified by the customer in the features file

    #Define instance variables from the hash keys
    config.each do |k,v|
      instance_variable_set("@#{k}",v)
    end
  end

  def method_missing(sym, *args, &block)
    @browser.send sym, *args, &block
  end

  def page_title
    #Returns contents of <title> tag in current page.
    @browser.title
  end

  def visit
    @browser.goto(@start_url)
  end

  def set_origin(origin)
    self.text_field(@route[:attribute] => @route[:origin]).set origin
  end

  def set_destination(destination)
    self.text_field(@route[:attribute] => @route[:destination]).set destination
  end

  def set_departure_date(outbound)
    self.text_field(@route[:attribute]  => @date[:outgoing_date]).set outbound
  end

  # [...snip]

end

正如您所看到的,我已经使用instance_variable_set创建了动态保存引用的变量,并且变量名称和值由配置文件提供(设计为可由不受限制的人编辑) #39;必须熟悉Ruby)。

不幸的是,这是一个很大的毛茸茸的课程,我每次想要添加一个新字段时都要编辑源代码,这显然是糟糕的设计,所以我一直试图更进一步,创建使用define_method动态设置变量名称的方法,这就是在过去的几个晚上凌晨4点让我保持清醒。

这就是我所做的:

require File.expand_path(File.dirname(__FILE__) + '/flight_search_page')

class SetFieldsByType <  FlightSearchPage
  def text_field(config_hash)
    define_method(config_hash) do |data|
      self.text_field(config_hash[:attribute] => config_hash[:origin]).set data
    end
  end
end

我们的想法是,添加新字段所需要做的就是在YAML文件中添加一个新条目,define_method将创建允许黄瓜填充它的方法。

目前,我遇到了范围问题 - Ruby认为define_method是@browser的成员。但我想知道的是:这甚至可行吗?我完全误解了define_method吗?

2 个答案:

答案 0 :(得分:2)

  

您是否希望在类定义之外看到需求和文件加载?

不,在类定义中。 Ruby类声明只是按照它看到的顺序执行的代码。像attr_accessor之类的东西只是碰巧对正在定义的类做事的类方法,正在定义。这个似乎就像你想做的那样。

在你的情况下你会改为阅读YAML文件,并运行你自己的逻辑来创建访问器,构建所需的任何后备数据等等。我不完全了解用例,但这听起来并不常见或困难--yet。

那就是说,通过将这些定义放在YAML文件中,您获得了多少“便利”?考虑像我曾经做过的一样来创建用于驱动Watir的页面实例:

class SomePage < HeavyWatir
  has_text :fname     # Assumed default CSS accessor pattern
  has_text :whatever, accessor: 'some accessor mechanism', option: 'some other option'
end

has_xxx是创建实例变量访问器的类方法(就像attr_accessor那样),构建了一些其他数据结构,用于确保应该在页面上的所有内容实际上是,等等。例如,非常大致:

page = SomePage.new
page.visit
if page.missing_fields?
  # Do something saying the page isn't complete
end

听起来就像你想要一些模糊相似的东西:你想要给这个类(或者一个子类,或者你可以将它混合到任意一个类中)有一堆“东西”等等。)

这些“东西”具有附加功能,该功能以多种方式工作,例如:

<强>事情 - 即-发生-期间定义

例如,has_text将名称添加到页面元数据的类实例哈希中,如字段名称。

<强>事情 - 即-发生-期间使用率

例如,当调用fname=时,在实例的元数据中设置一个标志,表示调用了setter。

答案 1 :(得分:1)

这是元编程的合适案例,但看起来你的方式错误。

首先,是否会为FlightSearchPage的每个实例设置不同的配置文件,或者仅控制所有页面的一个配置文件?看起来你正在加载相同的配置文件而不管initialize的参数如何,所以我猜你的情况是前者。

如果是这样,您需要将所有元编程代码移动到类中(外部方法定义)。即在定义类时,您希望它加载配置文件,然后根据该配置创建每个实例。现在你每次创建一个实例时都重新加载配置文件,这似乎是不正确的。例如,define_method属于Module,因此它应该出现在类范围内,而不是实例方法中。

另一方面,如果您确实需要为每个实例配置不同的配置,则需要将所有元编程代码移动到单例类中,例如define_singleton_method而不是define_method