[英]How to call enumerator's next in yield block
我有一個批量更新過程,它更新Product的( 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" ,這將很難在......像 6 個月之后維護。 每個開發人員都會對為什么需要顯式返回true 、 false感到困惑。 有什么辦法可以從塊中調用 next 嗎? 還是不需要使用塊?
我知道我可以通過應用模板模式來解決這個問題:制作一個包含進程的抽象 class 並繼承它以覆蓋每個私有方法:
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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.