Class: Testimonials::PromptEvent

Inherits:
ApplicationRecord show all
Defined in:
app/models/testimonials/prompt_event.rb

Overview

The throttling ledger. Every auto-open is a "shown", every close without submitting is a "dismissed", every saved submission is a "submitted" — keyed by author_id for signed-in users, by a visitor cookie otherwise. eligible? reads this history so the widget never nags:

* submitted a testimonial     -> never auto-prompted for one again
* submitted NPS               -> not again within nps_reprompt_after
* shown or dismissed recently -> not again within reprompt_after
* shown max_prompts times     -> never auto-prompted for that kind again

Explicit opens (the user clicked something) bypass all of this.

The ledger is optional: an install run with --skip-prompt-events has no table behind this class and config.prompt_events is false, so record! is a no-op and eligible? answers without a query. Every write and every read goes through the two methods below, so that flag is the only guard needed — nothing here asks the schema at runtime.

Constant Summary collapse

KINDS =
%w[testimonial nps].freeze
ACTIONS =
%w[shown dismissed submitted].freeze

Class Method Summary collapse

Class Method Details

.eligible?(kind:, author_id: nil, visitor_token: nil, tenant: nil) ⇒ Boolean

Throttling is per-tenant: a user prompted in one tenant is still eligible in another, so each collection can ask independently.

Returns:

  • (Boolean)


39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'app/models/testimonials/prompt_event.rb', line 39

def eligible?(kind:, author_id: nil, visitor_token: nil, tenant: nil)
  kind = kind.to_s
  return false unless KINDS.include?(kind)
  # No identity, no history: a brand-new visitor is always eligible.
  return true if author_id.blank? && visitor_token.blank?
  # No ledger, no history either — the same answer, one flag earlier.
  # This is not a licence to nag: with the ledger off nothing auto-opens
  # (see WidgetHelper#testimonials_auto_open), and the one caller left is
  # the promoter offered the form inside a flow they opened themselves.
  return true unless Testimonials.config.prompt_events

  history = subject(author_id, visitor_token, tenant).where(kind: kind)
  config = Testimonials.config

  return false if (history, kind, config)
  return false if history.where(action: %w[shown dismissed])
                         .exists?(created_at: (Time.current - config.reprompt_after)..)

  history.where(action: 'shown').count < config.max_prompts
end

.record!(kind:, action:, author_id: nil, visitor_token: nil, tenant: nil) ⇒ Object



29
30
31
32
33
34
35
# File 'app/models/testimonials/prompt_event.rb', line 29

def record!(kind:, action:, author_id: nil, visitor_token: nil, tenant: nil)
  return unless Testimonials.config.prompt_events
  return if author_id.blank? && visitor_token.blank?

  create!(kind: kind.to_s, action: action.to_s, tenant: tenant.presence,
          author_id: author_id.presence, visitor_token: visitor_token.presence)
end