Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## [0.5.2] - 2026-05-25

### Fixed

- **Proc/block context in `emits`**: Payload builder blocks and `if:`/`unless:` condition procs now
execute bound to the service instance via `instance_exec`, matching the behaviour of Symbol method
references. Previously, `builder.call(result)` left `self` as the class, so instance variables set
during `#call` (e.g. `@my_flag`) were silently unreadable inside a block or lambda. Both forms now
behave identically — `self` is always the service instance.

## [0.5.1] - 2026-05-25

### Added
Expand Down
2 changes: 1 addition & 1 deletion gem/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
servus (0.5.1)
servus (0.5.2)
activesupport (>= 8.0)
json-schema (~> 5)

Expand Down
5 changes: 2 additions & 3 deletions gem/lib/servus/events/emitter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def emission_condition_met?(emission, result)
# @return [Object] truthy or falsy value
# @api private
def evaluate_emission_condition(condition, result)
condition.is_a?(Proc) ? condition.call(result) : send(condition, result)
condition.is_a?(Proc) ? instance_exec(result, &condition) : send(condition, result)
end

# Validates the payload against the Event class's schema registered for the event.
Expand All @@ -216,8 +216,7 @@ def build_event_payload(emission, result)
builder = emission[:payload_builder]

if builder.is_a?(Proc)
# Block-based payload builder
builder.call(result)
instance_exec(result, &builder)
elsif builder.is_a?(Symbol)
# Method-based payload builder
send(builder, result)
Expand Down
2 changes: 1 addition & 1 deletion gem/lib/servus/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Servus
VERSION = '0.5.1'
VERSION = '0.5.2'
end
39 changes: 39 additions & 0 deletions gem/spec/servus/base_events_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,45 @@ def call
.to emit_event(:conditional_event)
.with(hash_including(amount: 150, label: 'large'))
end

context 'instance variable access in procs (regression: instance_exec binding)' do
it 'condition proc can read an instance variable set in initialize' do
service_class = stub_const('IvarConditionService', Class.new(Servus::Base) do
emits :ivar_event, on: :success, unless: ->(_result) { @suppress }

def initialize(suppress: false)
@suppress = suppress
end

def call
success({})
end
end)

expect { service_class.call(suppress: false) }.to emit_event(:ivar_event)
expect { service_class.call(suppress: true) }.not_to emit_event(:ivar_event)
end

it 'payload builder block can read an instance variable set in initialize' do
service_class = stub_const('IvarPayloadService', Class.new(Servus::Base) do
emits :ivar_payload_event, on: :success do |_result|
{ label: @label }
end

def initialize(label:)
@label = label
end

def call
success({})
end
end)

expect { service_class.call(label: 'vip') }
.to emit_event(:ivar_payload_event)
.with(hash_including(label: 'vip'))
end
end
end

describe 'automatic event emission' do
Expand Down