mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-04 05:09:20 +08:00
Services that process collections (bulk delete, bulk create, etc.)
currently require manual iteration inside a `step`, losing the
framework's built-in error handling and inspection. This adds a
first-class `each` step that brings the full service DSL inside the
iteration loop.
```ruby
each :users do
policy :can_delete
step :destroy
end
```
Each iteration receives the singularized item name (`user:`) and
`index:` as keyword arguments. If any nested step fails, iteration stops
and the failing item/index remain in context for error reporting.
Existing matchers (`fail_a_policy`, `fail_a_step`, etc.) work inside
each blocks.
A `persist:` option allows values to accumulate across iterations and
survive the loop's variable isolation:
```ruby
each :tag_names, persist: { results: -> { { created: [], failed: [] } } } do
step :create_tag
end
# context[:results] available after the loop
```
The steps inspector displays iteration progress ((3/3) on success, (1/3)
when failing at the first item, (empty collection) with ⏭️ when
skipped).
60 lines
1.6 KiB
Ruby
60 lines
1.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Service
|
|
module Base
|
|
class EachStep < Step
|
|
include StepsHelpers
|
|
|
|
attr_reader :steps, :item_name, :initializers
|
|
|
|
def initialize(name, as: nil, persist: nil, &block)
|
|
super(name)
|
|
@item_name = as || name.to_s.singularize.to_sym
|
|
@initializers = build_initializers(persist)
|
|
@steps = []
|
|
instance_exec(&block)
|
|
end
|
|
|
|
def run_step
|
|
context.with_isolation(persist_keys: [item_name, :index, *initializers.keys]) do
|
|
context.merge!(initializers.transform_values { instance.instance_exec(&it) })
|
|
Array
|
|
.wrap(context[name])
|
|
.tap do |collection|
|
|
context[result_key].merge!(total: collection.size, skipped?: collection.empty?)
|
|
end
|
|
.each_with_index do |item, index|
|
|
context.merge!(item_name => item, :index => index)
|
|
steps.each { |step| step.call(instance, context) }
|
|
end
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def build_initializers(value)
|
|
case value
|
|
when nil
|
|
{}
|
|
when Array
|
|
value.each_with_object({}) { |item, hash| hash.merge!(build_initializers(item)) }
|
|
when Symbol
|
|
{ value => proc {} }
|
|
when Hash
|
|
value.transform_values(&method(:make_lambda))
|
|
end
|
|
end
|
|
|
|
def make_lambda(filter)
|
|
case filter
|
|
when Symbol
|
|
proc { send(filter) }
|
|
when Proc
|
|
proc { instance_exec(&filter) }
|
|
else
|
|
proc {}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|