在Ruby中循环使用多个数组

时间:2011-06-07 20:29:43

标签: ruby-on-rails ruby refactoring

我有多个ActiveRecord子类Item实例数组,我需要按照最早的事件循环打印。在这种情况下,我需要打印打印付款和维护日期如下:

  

项目5天内需要维护
  项目B在6天内付款   项目7天内需要付款
  B项维护需要8天

我目前有两个查询用于查找maintenancepayment项(非独占查询)以及类似以下内容的输出:

<%- item_p = nil -%>
<%- item_m = nil -%>

<%- loop do -%>
  <% item_p ||= @items_p.shift %>
  <% item_m ||= @items_m.shift %>

  <%- if item_p.nil? and item_m.nil? then break -%>
  <%- elsif item_p and (item_m.nil? or item_p.paymt < item_m.maint) then -%>
    <%= item_p.name %> payment required in ...
  <%- elsif item_m and (item_p.nil? or item_m.maint < item_p.paymt) then -%>
    <%= item_m.name %> maintenance required in ...
  <%- end -%>
<%- end -%>

有什么方法可以清除上面(丑陋)代码的可读性吗?

3 个答案:

答案 0 :(得分:5)

拥抱鸭子并确保您的对象多态。您希望付款项与维护可比较,以便对其进行排序。

因此,假设您有一个Payment和一个Maintenance类:

module Due
  include Comparable

  # Compare this object with another. Used for sorting.
  def <=>(other)
    self.due <=> other.due
  end
end

class Payment < ActiveRecord::Base
  include Due

  alias_method :due, :payment

  def action
    "#{name} requires payment"
  end
end

class Maintenance < ActiveRecord::Base
  include Due

  alias_method :due, :maintenance

  def action
    "#{name} requires maintenance"
  end
end

了解我们如何在两个类中创建actiondue<=>方法?我们还注意包含Ruby内置模块Comparable。这允许我们执行以下操作:

# Assuming 'payment' and 'maintenance' are date fields...
a = Payment.new :payment => 3.days.from_now
b = Maintenance.new :maintenance => 2.days.from_now
[a, b].sort
#=> [b, a]

然后视图变得如此简单:

<% (@payment_items + @maintenance_items).sort.each do |item| %>
  <%= item.action %> in <%= distance_of_time_in_words_to_now(item.due) %><br/>
<% end %>

我确信我没有正确掌握您的实施细节,但我希望这能让您了解如何处理您的问题。

答案 1 :(得分:2)

这很快且很脏(即未优化):

# In your controller:
@items = @items_p.map{ |item| {:item => item, :days => item.paymt, :description => "payment"} } 
@items += @items_m.map{ |item| {:item => item, :days => item.maint, :description => "maintenance"} }
@items = @items.sort_by{ |item| item[:day] }

# In your view:
<% @items.each do |item| %>
  <%= item[:item].name %> <%= item[:description] %> required in <%= item[:days] %> days
<% end %>

答案 2 :(得分:1)

你的观点太过分了。真的,你应该在控制器中找出所有这些并通过一个清理过的结构,你可以迭代它以便显示。

举个例子:

length = [ @items_p.length, @items_m.length ].sort.last

@messages = [ ]

length.times do |i|
  item_p = @items_p[i]
  item_m = @items_m[i]

  if (item_p and (item_m and item_p.paymt < item_m.maint) or !item_m)
    @messages << "#{item_p.name} payment required in ..."
  elsif (item_m and (item_p and item_m.maint < item_p.paymt) or !item_p)
    @messages << "#{item_m.name} maintenance required in ..."
  end
end

然后根据需要迭代@messages

这里真正的问题是,从战略角度来说,你还没有为这类事物构建这些对象。如果您有截止日期的单一方法,而不必根据类型区分paymtmaint,那将是很好的。同样,如果这两个都配对到一个数组而不是单独提供,那就更好了。

如果您使用[ p, m ]对,则可以更简单地迭代:

items.each do |pair|
  first_due = pair.compact.sort_by(&:due).first
  @messages << "#{first_due.name} #{first_due.action} required in ..."
end

action方法会根据需要返回paymentmaintenance