问题描述
我有一个批量更新过程,该过程会更新产品( 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
但是,我需要显式地返回 true 或 false 来执行“ next” ,在...之后将很难维护。像六个月 每个开发人员都会困惑为什么需要显式返回 true , false 。 我有什么方法可以从该区块中拨打电话吗?还是不需要使用块?
我知道我可以通过应用 Template Pattern 解决此问题:制作一个包含该过程的抽象类,并继承它以覆盖每个私有方法:
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?
是使用此回调结构(没有像raise
或throw
这样的非线性控制流的唯一方法),而且正如您所提到的,它有点用奇怪的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!)
,
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