如何在导出为 CSV 的 Ruby 方法中重构嵌套循环?
How to refactor nested loops in a Ruby method that exports to CSV?
我必须将一些信息导出到 CSV。我写了这段代码,但我不太喜欢它。我不知道如何重构它并摆脱嵌套循环。
我的关系如下:Order 有很多 Moves,Move 有很多 Stops。
我必须将所有这些导出到 CSV,所以我将有多个行用于同一个订单。
这是我的(低质量)代码:
def to_csv
CSV.generate(headers: true) do |csv|
csv << h.t(self.first.exported_attributes.values.flatten) # headers
self.each do |order|
order.moves.map do |move|
move.stops.map do |stop|
order_data = order.exported_attributes[:order].map do |attributes|
order.public_send(attributes)
end
move_data = order.exported_attributes[:move].map do |attributes|
move.decorate.public_send(attributes)
end
stop_data = order.exported_attributes[:stop].map do |attributes|
stop.decorate.public_send(attributes)
end
csv << order_data + move_data + stop_data
end
end
end
end
end
我昨天做了这个:
def to_csv
CSV.generate(headers: true) do |csv|
csv << h.t(self.first.exported_attributes.values.flatten) # headers
self.each do |order|
order.moves.each do |move|
move.stops.each do |stop|
csv << order.exported_attributes[:order].map { |attr| order.public_send(attr) } +
order.exported_attributes[:move].map { |attr| move.decorate.send(attr) } +
order.exported_attributes[:stop].map { |attr| stop.decorate.send(attr) }
end
end
end
end
end
我闻到的最大气味不是嵌套循环,而是从每个模型中获取值的方式几乎重复。
让我们在 Order
、Move
和 Stop
:
上将重复提取到具有相同名称 exported_values
的类似方法中
class Order
def exported_values
exported_attributes[:order].map { |attrs| { public_send(attrs) }
end
end
class Move
def exported_values
order.exported_attributes[:stop].map { |attrs| { decorate.public_send(attrs) }
end
end
class Stop
def exported_values
move.order.exported_attributes[:move].map { |attrs| { decorate.public_send(attrs) }
end
end
并在 to_csv
:
中使用它们
def to_csv
CSV.generate(headers: true) do |csv|
csv << h.t(first.exported_attributes.values.flatten) # headers
each do |order|
order_values = order.exported_values
order.moves.each do |move|
order_and_move_values = order_values + move.exported_values
move.stops.each do |stop|
csv << order_and_move_values + stop.exported_values
end
end
end
end
end
以上还有一些额外的小改进:
- 在最外层的循环中获取并连接导出的值以提高效率。
- 循环以
each
而不是 map
移动和停止,因为循环是针对副作用而不是 return 值完成的。
- 删除
self.
. 的不必要使用
现在 to_csv
还不错。但是它还有一点点feature envy(就是调用了太多其他对象的方法),所以让我们提取更多的方法到模型上:
def to_csv
CSV.generate(headers: true) do |csv|
csv << h.t(first.exported_attributes.values.flatten) # headers
each { |order| order.append_to_csv(csv) }
end
end
class Order
def append_to_csv(csv)
values = exported_values
moves.each { |move| move.append_to_csv(csv, values) }
end
end
class Move
def append_to_csv(csv, prefix)
values = exported_values
stops.each { |stop| stop.append_to_csv(csv, prefix + values) }
end
end
class Stop
def append_to_csv(csv, prefix)
csv << prefix + exported_values
end
end
没有更多的嵌套循环。提取的方法有点重复,但我认为如果提取重复的话,它们就不清楚了。
接下来我们可以尝试将 exported_values
个方法重构为一个方法。
也许 Order#exported_attributes
可以分解为每个 class 上的一个方法,它不接受任何参数,return 只接受 class 的方法导出的属性。
这些方法之间的另一个区别是 Order
不需要 .decorator
但其他 class 需要。如果它有一个装饰器,只需使用它而不是实际的顺序;如果没有,就给它一个假的:
class Order
def decorator
self
end
end
然后您可以在模块中定义一个 exported_values
方法并将其包含在所有三个 class 中:
def exported_values
exported_attributes.map { |attrs| { decorator.public_send(attrs) }
end
还有一个可能的改进:如果每个模型的导出值在实例的生命周期内保持不变是可以的,您可以像这样缓存它们
def exported_values
@exported_values ||= exported_attributes.map { |attrs| { decorator.public_send(attrs) }
end
并在 append_to_csv
方法中内联 values
局部变量 和 从这些方法中的父对象获取 "prefixes" 而不是传递它们作为参数。
可能所有新方法都应该提取到装饰器而不是模型;我不确定您的装饰器是用于 CSV 生成还是仅用于其他目的。
我必须将一些信息导出到 CSV。我写了这段代码,但我不太喜欢它。我不知道如何重构它并摆脱嵌套循环。
我的关系如下:Order 有很多 Moves,Move 有很多 Stops。
我必须将所有这些导出到 CSV,所以我将有多个行用于同一个订单。
这是我的(低质量)代码:
def to_csv
CSV.generate(headers: true) do |csv|
csv << h.t(self.first.exported_attributes.values.flatten) # headers
self.each do |order|
order.moves.map do |move|
move.stops.map do |stop|
order_data = order.exported_attributes[:order].map do |attributes|
order.public_send(attributes)
end
move_data = order.exported_attributes[:move].map do |attributes|
move.decorate.public_send(attributes)
end
stop_data = order.exported_attributes[:stop].map do |attributes|
stop.decorate.public_send(attributes)
end
csv << order_data + move_data + stop_data
end
end
end
end
end
我昨天做了这个:
def to_csv
CSV.generate(headers: true) do |csv|
csv << h.t(self.first.exported_attributes.values.flatten) # headers
self.each do |order|
order.moves.each do |move|
move.stops.each do |stop|
csv << order.exported_attributes[:order].map { |attr| order.public_send(attr) } +
order.exported_attributes[:move].map { |attr| move.decorate.send(attr) } +
order.exported_attributes[:stop].map { |attr| stop.decorate.send(attr) }
end
end
end
end
end
我闻到的最大气味不是嵌套循环,而是从每个模型中获取值的方式几乎重复。
让我们在 Order
、Move
和 Stop
:
exported_values
的类似方法中
class Order
def exported_values
exported_attributes[:order].map { |attrs| { public_send(attrs) }
end
end
class Move
def exported_values
order.exported_attributes[:stop].map { |attrs| { decorate.public_send(attrs) }
end
end
class Stop
def exported_values
move.order.exported_attributes[:move].map { |attrs| { decorate.public_send(attrs) }
end
end
并在 to_csv
:
def to_csv
CSV.generate(headers: true) do |csv|
csv << h.t(first.exported_attributes.values.flatten) # headers
each do |order|
order_values = order.exported_values
order.moves.each do |move|
order_and_move_values = order_values + move.exported_values
move.stops.each do |stop|
csv << order_and_move_values + stop.exported_values
end
end
end
end
end
以上还有一些额外的小改进:
- 在最外层的循环中获取并连接导出的值以提高效率。
- 循环以
each
而不是map
移动和停止,因为循环是针对副作用而不是 return 值完成的。 - 删除
self.
. 的不必要使用
现在 to_csv
还不错。但是它还有一点点feature envy(就是调用了太多其他对象的方法),所以让我们提取更多的方法到模型上:
def to_csv
CSV.generate(headers: true) do |csv|
csv << h.t(first.exported_attributes.values.flatten) # headers
each { |order| order.append_to_csv(csv) }
end
end
class Order
def append_to_csv(csv)
values = exported_values
moves.each { |move| move.append_to_csv(csv, values) }
end
end
class Move
def append_to_csv(csv, prefix)
values = exported_values
stops.each { |stop| stop.append_to_csv(csv, prefix + values) }
end
end
class Stop
def append_to_csv(csv, prefix)
csv << prefix + exported_values
end
end
没有更多的嵌套循环。提取的方法有点重复,但我认为如果提取重复的话,它们就不清楚了。
接下来我们可以尝试将 exported_values
个方法重构为一个方法。
也许
Order#exported_attributes
可以分解为每个 class 上的一个方法,它不接受任何参数,return 只接受 class 的方法导出的属性。这些方法之间的另一个区别是
Order
不需要.decorator
但其他 class 需要。如果它有一个装饰器,只需使用它而不是实际的顺序;如果没有,就给它一个假的:class Order def decorator self end end
然后您可以在模块中定义一个 exported_values
方法并将其包含在所有三个 class 中:
def exported_values
exported_attributes.map { |attrs| { decorator.public_send(attrs) }
end
还有一个可能的改进:如果每个模型的导出值在实例的生命周期内保持不变是可以的,您可以像这样缓存它们
def exported_values
@exported_values ||= exported_attributes.map { |attrs| { decorator.public_send(attrs) }
end
并在 append_to_csv
方法中内联 values
局部变量 和 从这些方法中的父对象获取 "prefixes" 而不是传递它们作为参数。
可能所有新方法都应该提取到装饰器而不是模型;我不确定您的装饰器是用于 CSV 生成还是仅用于其他目的。