lundi 17 août 2020

How to call enumerator's next in yield block

I have a bulk update process which updates a Product's (has_many) Subscription and is called in several places, so I refactor it into a service.

In each place where calls this process still has its own special pre-process: like adding counter, etc. and some subscriptions :update can be skipped if it will be destroyed. So I send block to it:

# 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

However, I need to explicitly return true or false to do "next", it will be hard to maintain after... like 6 months. Every developer will be confused that why it needs to return true, false explicitly. Is there any way I can call next from the block? or don't need to use the block?

I know I can solve this problem by applying Template Pattern: make an abstract class containing the process and inherit it to overwrite each private method:

class SavingService
  def call!
    pre_process
    process
    post_process
  end

  private

  def pre_process; end
  def process; end
  def post_process; end
end

But the different parts of each place calling the process are very small, just 1~3 lines. I don't want to create so many classes for such tiny differences, so I choosed to use block first.

Aucun commentaire:

Enregistrer un commentaire