簡體   English   中英

如何在產量塊中調用枚舉器的下一個

[英]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

但是,我需要明確地返回truefalse來執行"next" ,這將很難在......像 6 個月之后維護。 每個開發人員都會對為什么需要顯式返回truefalse感到困惑。 有什么辦法可以從塊中調用 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? 是使用此回調結構的唯一方法(沒有像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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM