Class: Marketing::RecordEventAttendance
- Inherits:
-
Object
- Object
- Marketing::RecordEventAttendance
- Defined in:
- app/services/marketing/record_event_attendance.rb
Overview
Records that a customer was at a trade show — as a TOUCH, dated when the
touch happened, without changing anyone's source.
This is the answer to the case the window rules cannot serve: a customer
reaching out a year later saying "I saw you at KBIS 2022". That is a true
statement and it must be recordable, but it is not a claim that the show
ACQUIRED them — their acquisition source was settled at creation and is
write-once. The show TOUCHED them, and touches are joined, never written.
So Source#attributable_at? stays strict about sourcing while this stays
open about attribution: attendance can be recorded at any time, for a show of
any age, and it shows up in campaign_influenced_invoices against that show
without rewriting a single source_id. Enforcement that makes people record
something false is worse than no enforcement.
The show is reached through a Campaign because the influence view keys on
campaigns. An event campaign points at the show's existing source rather
than generating one (Campaign#generate_source is skipped when source_id
is already set), so nothing new appears in the source tree.
Defined Under Namespace
Classes: NotAnEventError
Constant Summary collapse
- AUDIENCE_SUFFIX =
Suffix for the audience holding a show's attendees. One per show, created
on first use — a badge-scan import and a customer's own recollection land
in the same place. 'attendees'
Class Method Summary collapse
-
.call(source:, customer:, at: nil) ⇒ AudienceMember
The recorded touch, existing or new.
Instance Method Summary collapse
- #call ⇒ AudienceMember
-
#initialize(source, customer, at) ⇒ RecordEventAttendance
constructor
A new instance of RecordEventAttendance.
Constructor Details
#initialize(source, customer, at) ⇒ RecordEventAttendance
Returns a new instance of RecordEventAttendance.
52 53 54 55 56 |
# File 'app/services/marketing/record_event_attendance.rb', line 52 def initialize(source, customer, at) @source = source @customer = customer @at = at end |
Class Method Details
.call(source:, customer:, at: nil) ⇒ AudienceMember
Returns the recorded touch, existing or new.
45 46 47 |
# File 'app/services/marketing/record_event_attendance.rb', line 45 def self.call(source:, customer:, at: nil) new(source, customer, at).call end |
Instance Method Details
#call ⇒ AudienceMember
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'app/services/marketing/record_event_attendance.rb', line 59 def call raise NotAnEventError, "#{@source&.full_name.inspect} is not a trade show" unless @source&.event? # The WHOLE resolution runs under the campaign lock — audience and member # both. Rails implements find_or_create_by! as `find_by || create!`, so the # create path runs model validations: if a concurrent request commits # between our SELECT and our INSERT, AudienceMember's per-audience # uniqueness validation fails and raises RecordInvalid *before* the database # constraint is reached. Rescuing RecordNotUnique alone does not cover that # window; serialising does. campaign.with_lock do member = audience.audience_members.find_or_create_by!(customer_id: @customer.id) # The touch is dated when it HAPPENED, not when it was typed in. A show # recalled a year later must join the influence report at the show's date, # or it would look like a touch that post-dates every deal it influenced. member.update_columns(created_at: touched_at) if member.created_at != touched_at member end end |