Module: Models::SourceAttributable

Extended by:
ActiveSupport::Concern
Included in:
Customer, Opportunity, Order
Defined in:
app/concerns/models/source_attributable.rb

Overview

Concern for models that have source attribution via visits with Google click IDs.
Provides methods to check if source attribution should be protected from manual changes.

Used by: Opportunity, Order, Customer

A source is considered "locked" when the record or its associated visit has a Google
click identifier (gclid, gbraid, or wbraid), indicating paid traffic attribution
that should not be overwritten by manual source selection.

Instance Method Summary collapse

Instance Method Details

#has_google_ads_attribution?Boolean

Returns true if this record has Google Ads attribution that should be protected.
Override in models that have different visit relationships.

Returns:

  • (Boolean)


54
55
56
57
58
59
60
# File 'app/concerns/models/source_attributable.rb', line 54

def has_google_ads_attribution?
  return true if respond_to?(:gclid) && gclid.present?
  return true if respond_to?(:gbraid) && gbraid.present?
  return true if respond_to?(:wbraid) && wbraid.present?

  visit_with_google_click_id?
end

#source_event_window_respectedvoid

This method returns an undefined value.

Rejects tagging a record with a trade show it cannot have come from.

An event attributes across its whole commercial arc — lead-in, show,
follow-up — so a record must date from inside that span. What this refuses
is a show stamped onto business it has nothing to do with, which is how a
2011 Toronto show ended up on 20k Costco orders.

The lead-in is not a loophole, it is the point: an order written the week
before a show the rep is about to attend is the same sale as one taken at
the booth. See Source#attribution_window.

Deliberately permissive in three places: an undated event (all 340 legacy
ones) cannot be judged and is allowed; only a CHANGE to source_id is
checked, so existing records stay editable for every other reason; and an
order inheriting its opportunity's source is not re-attributed on the later
order date. A bad inherited pairing must be corrected on the opportunity.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'app/concerns/models/source_attributable.rb', line 36

def source_event_window_respected
  return if respond_to?(:opportunity) && opportunity&.source_id == source_id

  event = Source.find_by(id: source_id)
  return if event.nil? || !event.event?

  at = created_at || Time.current
  return if event.attributable_at?(at)

  window = event.attribution_window
  errors.add(:source_id,
             "#{event.full_name} ran #{event.starts_on} to #{event.ends_on} and attributes from " \
             "#{window.first} to #{window.last} (#{event.pre_attribution_window_days} days lead-in, " \
             "#{event.attribution_window_days} days follow-up); this record dates from #{at.to_date}")
end

#source_locked?Boolean

Returns true if source should be locked from manual editing in UI

Returns:

  • (Boolean)


70
71
72
# File 'app/concerns/models/source_attributable.rb', line 70

def source_locked?
  has_google_ads_attribution?
end

#source_locked_reasonString?

Human-readable message explaining why source is locked

Returns:

  • (String, nil)


77
78
79
80
81
82
83
84
85
86
87
# File 'app/concerns/models/source_attributable.rb', line 77

def source_locked_reason
  return nil unless source_locked?

  if respond_to?(:visit) && visit.present? && (visit.marketing_meta_gclid.present? || visit.marketing_meta_gbraid.present? || visit.marketing_meta_wbraid.present?)
    "Source is locked: attributed to Google Ads via visit on #{visit.started_at&.to_date}"
  elsif respond_to?(:gclid) && gclid.present?
    'Source is locked: customer has Google Click ID (gclid)'
  else
    'Source is locked: has Google Ads attribution'
  end
end

#visit_with_google_click_id?Boolean

Check if the associated visit has a Google click identifier

Returns:

  • (Boolean)


63
64
65
66
67
# File 'app/concerns/models/source_attributable.rb', line 63

def visit_with_google_click_id?
  return false unless respond_to?(:visit) && visit.present?

  visit.marketing_meta_gclid.present? || visit.marketing_meta_gbraid.present? || visit.marketing_meta_wbraid.present?
end