我怎么能干这个代码并清理我的模型?

时间:2011-08-29 02:27:01

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

我有Number模型的以下两种方法。

def track
  number = sanitize(tracking)

  case determine_type(number)
  when 'UPS'
    tracker = ups.track(:tracking_number => number)
    self.carrier              = Carrier.where(:name => 'UPS').first
    self.service              = tracker.service_type
    self.destination_country  = tracker.destination_country
    self.destination_state    = tracker.destination_state
    self.destination_city     = tracker.destination_city
    self.origin_country       = tracker.origin_country
    self.origin_state         = tracker.origin_state
    self.origin_city          = tracker.origin_city
    self.signature            = tracker.signature_name
    self.scheduled_delivery   = tracker.scheduled_delivery_date
    self.weight               = tracker.weight

    tracker.events.each do |event|
      new_event             = Event.new
      new_event.number      = self
      new_event.city        = event.city
      new_event.state       = event.state
      new_event.postalcode  = event.postal_code if event.postal_code
      new_event.country     = event.country
      new_event.status      = event.name
      new_event.status_code = event.type
      new_event.occured_at  = event.occurred_at

      new_event.save
    end
  end

  save
end

def update_number

  case determine_type(number)
  when 'UPS'
    tracker = ups.track(:tracking_number => tracking)
    self.carrier              = Carrier.where(:name => 'UPS').first
    self.service              = tracker.service_type
    self.destination_country  = tracker.destination_country
    self.destination_state    = tracker.destination_state
    self.destination_city     = tracker.destination_city
    self.origin_country       = tracker.origin_country
    self.origin_state         = tracker.origin_state
    self.origin_city          = tracker.origin_city
    self.signature            = tracker.signature_name
    self.scheduled_delivery   = tracker.scheduled_delivery_date
    self.weight               = tracker.weight

    last_event = self.events.ordered.first.occured_at

    tracker.events.each do |event|
      if last_event and (event.occurred_at > last_event)
        new_event             = Event.new
        new_event.number      = self
        new_event.city        = event.city
        new_event.state       = event.state
        new_event.postalcode  = event.postal_code if event.postal_code
        new_event.country     = event.country
        new_event.status      = event.name
        new_event.status_code = event.type
        new_event.occured_at  = event.occurred_at

        new_event.save
      end
    end
  end
  save
end

如您所见,许多代码都是重复的。当我开始添加十几个其他运营商(联邦快递,USPS,DHL等)时,问题就出现了......我的Number模型变得庞大而且毛茸茸。

trackupdate_number之间唯一真正的区别在于update_number作为if事件的比较,以检查来自运营商的事件是否比我最近发生的事件更新存储在数据库中,使用if last_event and (event.occurred_at > last_event)行。

那么我该如何清理这些代码,以便我的模型不会那么胖呢?

3 个答案:

答案 0 :(得分:5)

我建议的几件事:

  1. 看看Strategy Pattern,即使Ruby没有接口,但它会给你一些关于如何更好地设计这个功能的想法(谷歌的战略模式为Ruby找到替代品)。基本上,您希望有单独的类来处理switch case语句并在运行时调用适当的类。
  2. 尝试将跟踪器处理代码与事件处理代码分开。例如,我会考虑将每个跟踪器事件的事件创建/保存逻辑移动到单独的类(如果有的话,甚至移动到Tracker实体)。
  3. 希望这会有所帮助。

答案 1 :(得分:1)

这应该使用策略模式来解决。对于每个可能的跟踪器(DHL,UPS,......),它们将适当地处理创建和更新。

这样就会成为:

class Number

  def track
    tracker = get_tracker(tracking)
    tracker.create_tracking(self)
    save
  end

  def update_number
    tracker = get_tracker(tracking)
    tracker.update_tracking(self)
    save
  end

  def get_tracker(tracking)
    tracking_number = sanitize(tracking)
    case determine_type(tracking_number)
    when 'UPS'
      UPSTracker.new(tracking_number)
    when 'DHL'
      DHLTracker.new(tracking_number)
    end
  end      
end

class UPSTracker

  def initialize(tracking_number)
    @tracking_number = tracking_number
  end

  def create_tracking(number)
    tracker = ups.track(:tracking_number => number)
    update_number_properties(number, tracking)

    # store events
    tracker.events.each do |event|
      create_new_event(event)
    end
  end

  def update_tracking(number)
    tracker = ups.track(:tracking_number => number)
    update_number_properties(number, tracking)

    last_event = self.events.ordered.first.occured_at

    tracker.events.each do |event|
      if last_event and (event.occurred_at > last_event)
        create_new_event(event)
      end
    end
  end

  protected

  def update_number_properties
    number.carrier = Carrier.where(:name => 'UPS').first
    number.service              = tracker.service_type
    number.destination_country  = tracker.destination_country
    number.destination_state    = tracker.destination_state
    number.destination_city     = tracker.destination_city
    number.origin_country       = tracker.origin_country
    number.origin_state         = tracker.origin_state
    number.origin_city          = tracker.origin_city
    number.signature            = tracker.signature_name
    number.scheduled_delivery   = tracker.scheduled_delivery_date
    number.weight               = tracker.weight
  end

  def create_new_event
    new_event             = Event.new
    new_event.number      = self
    new_event.city        = event.city
    new_event.state       = event.state
    new_event.postalcode  = event.postal_code if event.postal_code
    new_event.country     = event.country
    new_event.status      = event.name
    new_event.status_code = event.type
    new_event.occured_at  = event.occurred_at
    new_event.save
  end
end

此代码可以进一步改进,我想事件和跟踪的创建将在不同的运营商之间共享。所以共享基类也许。其次,在Number内的两个方法中,我们调用get_tracker:可能这个跟踪器可以在创建时(Number - 实例)决定,并且应该被提取一次并存储在实例中变量

此外,我想补充一点,名称应该是有意义的,因此类Number对我来说听起来不够有意义。名称应表达意图,并且最好与您的问题域中的名称和概念相匹配。

答案 2 :(得分:0)

关于你的代码的某些内容对我来说没有意义,组织有点奇怪,而且我没有足够的关于你的应用程序外观的上下文。在轨道中完成这样的事情的OOP方法非常简单;您也可以使用自己的策略,适配器甚至是Builder模式来执行此操作。

但是,有一些悬而未决的成果,你可以重构你的代码,所以常见的部分不那么突兀 - 这有点好,但create_events仍然非常case'y:< / p>

def track
  create_events
end

def update_number
  create_events {|e| last_event and (event.occurred_at > last_event) }
end

def create_events(&block)
  case determine_type(number)
  when 'UPS'
    tracker = ups.track(:tracking_number => tracking)
    self.carrier = Carrier.where(:name => 'UPS').first
    self.assign_tracker(tracker)
  end
  tracker.events.each do |e|
    self.create_event(e) unless (block_given? && !block.call(e))
  end
  save
end

def assign_tracker(tracker)
  self.service              = tracker.service_type
  self.destination_country  = tracker.destination_country
  self.destination_state    = tracker.destination_state
  self.destination_city     = tracker.destination_city
  self.origin_country       = tracker.origin_country
  self.origin_state         = tracker.origin_state
  self.origin_city          = tracker.origin_city
  self.signature            = tracker.signature_name
  self.scheduled_delivery   = tracker.scheduled_delivery_date
  self.weight               = tracker.weight
end

def create_event(event)
  new_event             = Event.new
  new_event.number      = self
  new_event.city        = event.city
  new_event.state       = event.state
  new_event.postalcode  = event.postal_code if event.postal_code
  new_event.country     = event.country
  new_event.status      = event.name
  new_event.status_code = event.type
  new_event.occured_at  = event.occurred_at
  new_event.save
end