Skip to content

Replace the Auditor write path with MemberActivityRecorder to create one funnel for activity rows - #3068

Open
davidmillen50 wants to merge 2 commits into
codebar:masterfrom
davidmillen50:2944-replace-the-auditor-write-path-with-member-activity-recorder
Open

davidmillen50 wants to merge 2 commits into
codebar:masterfrom
davidmillen50:2944-replace-the-auditor-write-path-with-member-activity-recorder

Conversation

@davidmillen50

Copy link
Copy Markdown
Contributor

No description provided.

@mroderick mroderick left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs fixes before merge: the rubocop job fails on two new offenses.

  • Metrics/MethodLength — MemberActivityRecorder.record grew to 11 lines against a max of 10.
  • RSpec/MessageSpies — the new controller spec sets a message expectation with expect(...).to receive.

Beyond CI, one real defect: the four sponsor.* keys these call sites write are missing from MemberActivityRecorder::KEYS, so every consent toggle logs an unregistered key warning through the funnel this PR consolidates. The rows persist; only the log line suffers.

Two non-blocking test notes: the recorder's new contact_email: branch has no unit coverage, and the feature-spec setups re-derive the audit key with the controller's own ternary.

The migration itself checks out — owner, trackable, recipient, key and the parameters[:note] shape match Auditor::Audit#log_with_note at both call sites, and the sponsor partials read the note unchanged.

TRACKING_STARTED_ON = Date.new(2026, 9, 8).freeze

def self.record(actor:, key:, trackable: nil, recipient: nil)
def self.record(actor:, key:, trackable: nil, recipient: nil, contact_email: nil)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is now 11 lines against Metrics/MethodLength Max 10, so the lint job fails. Pulling the hash into a local keeps the create! call on one line:

params = contact_email ? { note: contact_email } : {}
PublicActivity::Activity.create!(owner: actor, key:, trackable:, recipient:, parameters: params)


def self.record(actor:, key:, trackable: nil, recipient: nil)
def self.record(actor:, key:, trackable: nil, recipient: nil, contact_email: nil)
Rails.logger.warn("MemberActivityRecorder: unregistered key '#{key}'") unless KEYS.include?(key)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both migrated call sites pass keys that KEYS does not list: sponsor.admin_contact_subscribe, sponsor.admin_contact_unsubscribe, sponsor.contact_subscribe and sponsor.contact_unsubscribe. Every consent toggle writes unregistered key here. That warning is the one signal for a genuinely unregistered key.

Adding the four keys follows the comment on KEYS: "extend when adding sites".

key:,
trackable:,
recipient:,
parameters: contact_email ? { note: contact_email } : {}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing in spec/services/member_activity_recorder_spec.rb touches parameters, so this branch is only exercised through the controller. Two examples there would pin both sides: one with contact_email: expecting { note: email }, one without expecting {}.


describe 'PATCH #update' do
it 'passes the contact and email to the activity recorder' do
expect(MemberActivityRecorder).to receive(:record).with(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two problems with this example.

expect(...).to receive trips RSpec/MessageSpies, so the lint job fails. It also restates what the DB-backed examples below already prove through the real recorder (owner, trackable, parameters[:note]), while coupling the test to the exact keyword list, so a harmless new optional kwarg would break it.

Dropping it is enough. If the intent is to pin that no recipient: is passed, assert activity.recipient.nil? in the subscribe example below instead.

contact = sponsor.contacts.first
audit = Auditor::Audit.new(sponsor, 'sponsor.admin_contact_subscribe', manager, contact)
audit.log_with_note(contact.email)
contact_audit_key = contact.mailing_list_consent ? 'subscribe' : 'unsubscribe'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mirrors the controller's own ternary, so a key-derivation regression there would still satisfy the rendering assertions below. Hardcode it here (sponsor.admin_contact_subscribe, matching the fabricator default) and leave derivation to the form-driven scenarios. Same for the deleted-contact setup further down.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants