如何在收益块中调用枚举器的下一个



我有一个批量更新过程,它更新产品(has_many(订阅,并在多个地方被调用,所以我将其重构为服务。

在每个调用的地方,这个过程仍然有自己的特殊预处理:比如添加计数器等。以及一些订阅:如果更新将被销毁,则可以跳过。所以我给它发送块:

# subscriptions_params is an array containing permitted parameters from controller
module SavingService
def self.call!(product, subscriptions_params)
subscriptions_params.each do |params|
subscription = product.subscriptions.find(params[:id])
next if block_given && !yield(subscription, params)
subscription.update!(params)
end
product.update_something!
end
end
# It can work well
SavingService.call!(product, subscriptions_params)
# I can add some special process in the block
SavingService.call!(product, subscriptions_params) do |subscription, params|
if params[:checked]
subscription.counter += 1
true
else
subscription.destroy!
false
end
end

然而,我需要显式地返回truefalse来执行"下一个">,之后将很难维持。。。大约6个月。每个开发人员都会感到困惑,为什么它需要显式返回truefalse。有什么办法我可以从街区打给下一个吗?或者不需要使用区块?

我知道我可以通过应用模板模式来解决这个问题:制作一个包含进程的抽象类,并继承它来覆盖每个私有方法:

class SavingService
def call!
pre_process
process
post_process
end
private
def pre_process; end
def process; end
def post_process; end
end

但每个地方调用流程的不同部分都很小,只有1~3行。我不想为这么小的差异创建这么多类,所以我选择先使用块。

next是控制流,所以不,不能从收益内部next

使用block_given?是使用这种回调结构(没有像raisethrow这样的非线性控制流(的唯一方法,正如您所提到的,它的工作方式有点奇怪,b/c抽象不太适合。

我认为";到位的东西";而不是像这样注入一个块:

to_increment, to_destroy = subscriptions_params.partition { |p| p[:checked] }
product.subscriptions.where(id: to_increment.map { _1[:id] })
.each { |sub| sub.counter += 1 }
.then { |subs| Subscription.update_all(subs) } # something like this, I forget exact syntax
product.subscriptions.where(id: to_destroy.map { _1[:id] }).destroy_all!

这是因为没有太多的共享逻辑或"逻辑";工作;要真正提取,只需要多次执行一些操作。

也许您想要的是将这些操作作为方法构建到Subscription中?像这样:

class Subscription < ApplicationRecord
def increment!
self.counter += 1
end
end
product.subscriptions.where(id: to_increment).each(&:increment!).each(&:update!)

或者,也许你只需要一个类似update_subs!

class Product < ApplicationRecord
def update_subs!(sub_ids)
subs = subscriptions.where(id: ids).each { |sub| yield sub }
subs.each(&:update!)
end
end
# one line each, can't get much more straightforward than this
product.update_subs!(to_increment) { |sub| sub.counter += 1 }
product.subscriptions.where(id: to_destroy).each(&:destroy!)

您可以使用catchthrow使跳过更加明确:

module SavingService
def self.call!(product, subscriptions_params)
subscriptions_params.each do |params|
catch(:skip) do
subscription = product.subscriptions.find(params[:id])
yield(subscription, params) if block_given?
subscription.update!(params)
end
end
product.update_something!
end
end
SavingService.call!(product, subscriptions_params) do |subscription, params|
if params[:checked]
subscription.counter += 1
else
subscription.destroy!
throw(:skip)
end
end

最新更新