Module: Marketing::Audiences
- Defined in:
- app/services/marketing/audiences.rb,
app/services/marketing/audiences/target.rb,
app/services/marketing/audiences/openai_export.rb,
app/services/marketing/audiences/google_adapter.rb,
app/services/marketing/audiences/facebook_adapter.rb,
app/services/marketing/audiences/pinterest_adapter.rb
Overview
Multi-platform audience reconcile (see Audiences in
audiences.rb) and its per-platform adapters.
Defined Under Namespace
Modules: OpenaiExport Classes: FacebookAdapter, GoogleAdapter, PinterestAdapter, Result, Target
Class Method Summary collapse
-
.adapter_enabled?(adapter) ⇒ Boolean
A raising
enabled?(e.g. a missing-config lookup) must skip only that adapter, not abort the whole sync — so it's rescued here, not left to blow up theselect. -
.default_adapters ⇒ Array<Class>
itself via
enabled?; Facebook/Pinterest stay dormant until their per-platformaudience_sync_enabledflag + creds are set (the daily run is LIVE). -
.guarded(adapter, scope) { ... } ⇒ Result
Run one reconcile step, converting a raise into a recorded failure so the daily pass is all-or-nothing per step, not per run.
-
.member_emails(customer_scope) ⇒ Array<String>
Consent-gated marketable email addresses for an audience's members.
-
.member_parties(customer_scope) ⇒ ActiveRecord::Relation
An audience's parties: its customers AND their contacts (trade-account people live on Contact rows, FK customer_id).
-
.member_phones(customer_scope) ⇒ Array<String>
Consent-gated (not on the DNC list) phone numbers for an audience's members.
-
.sync(validate_only: true, audiences: Target.all, adapters: default_adapters) ⇒ Array<Result>
Reconcile every ad-synced audience into each platform it targets, then run each stateful adapter's opt-out purge once.
Class Method Details
.adapter_enabled?(adapter) ⇒ Boolean
A raising enabled? (e.g. a missing-config lookup) must skip only that
adapter, not abort the whole sync — so it's rescued here, not left to blow
up the select.
102 103 104 105 106 107 |
# File 'app/services/marketing/audiences.rb', line 102 def adapter_enabled?(adapter) adapter.enabled? rescue StandardError => e Rails.logger.error("[Marketing::Audiences] #{adapter.label} enabled? raised: #{e.class}: #{e.}") false end |
.default_adapters ⇒ Array<Class>
itself via enabled?; Facebook/Pinterest stay dormant until their
per-platform audience_sync_enabled flag + creds are set (the daily run is
LIVE). ChatGPT/OpenAI is deliberately absent — its Ads API has no
audience-ingest endpoint, so it goes through the manual CSV export
(OpenaiExport), not an adapter.
41 |
# File 'app/services/marketing/audiences.rb', line 41 def default_adapters = [GoogleAdapter, FacebookAdapter, PinterestAdapter] |
.guarded(adapter, scope) { ... } ⇒ Result
Run one reconcile step, converting a raise into a recorded failure so the
daily pass is all-or-nothing per step, not per run.
116 117 118 119 120 121 |
# File 'app/services/marketing/audiences.rb', line 116 def guarded(adapter, scope) Result.new(adapter: adapter.label, scope: scope, ok: true, detail: yield) rescue StandardError => e Rails.logger.error("[Marketing::Audiences] #{adapter.label}/#{scope} failed: #{e.class}: #{e.}") Result.new(adapter: adapter.label, scope: scope, ok: false, detail: e.) end |
.member_emails(customer_scope) ⇒ Array<String>
Consent-gated marketable email addresses for an audience's members. The raw
detail strings — each adapter hashes per its platform's spec. .distinct:
party_ids spans the customer Party AND its Contact parties, so a shared
address would otherwise be returned (and hashed) more than once.
84 85 86 |
# File 'app/services/marketing/audiences.rb', line 84 def member_emails(customer_scope) ContactPoint.marketable_email.where(party_id: member_parties(customer_scope).select(:id)).distinct.pluck(:detail) end |
.member_parties(customer_scope) ⇒ ActiveRecord::Relation
An audience's parties: its customers AND their contacts (trade-account
people live on Contact rows, FK customer_id). Shared by adapters that build
their own payload.
72 73 74 75 |
# File 'app/services/marketing/audiences.rb', line 72 def member_parties(customer_scope) ids = customer_scope.select(:id) Party.where(id: ids).or(Party.where(customer_id: ids)) end |
.member_phones(customer_scope) ⇒ Array<String>
Consent-gated (not on the DNC list) phone numbers for an audience's members.
92 93 94 |
# File 'app/services/marketing/audiences.rb', line 92 def member_phones(customer_scope) ContactPoint.marketable_phone.where(party_id: member_parties(customer_scope).select(:id)).distinct.pluck(:detail) end |
.sync(validate_only: true, audiences: Target.all, adapters: default_adapters) ⇒ Array<Result>
Reconcile every ad-synced audience into each platform it targets, then run
each stateful adapter's opt-out purge once. A failure in one audience/
platform step is logged and recorded but never aborts the rest.
51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'app/services/marketing/audiences.rb', line 51 def sync(validate_only: true, audiences: Target.all, adapters: default_adapters) instances = adapters.map { |klass| klass.new(validate_only:) }.select { |a| adapter_enabled?(a) }.index_by(&:label) results = [] audiences.each do |audience| audience.platforms.each do |platform| adapter = instances[platform] results << guarded(adapter, audience.key) { adapter.reconcile(audience) } if adapter end end instances.each_value do |adapter| results << guarded(adapter, :purge_suppressed) { adapter.purge_suppressed } if adapter.respond_to?(:purge_suppressed) end results end |