Class: Tracking::ConsentPreferences
- Inherits:
-
Object
- Object
- Tracking::ConsentPreferences
- Defined in:
- app/services/tracking/consent_preferences.rb
Overview
Handles consent preferences at the Party level
Uses jsonb_accessor for typed access to consent_preferences JSONB column
Constant Summary collapse
- CONSENT_MODE_OPT_IN =
Quebec, GDPR countries - requires explicit consent
'opt_in'- CONSENT_MODE_OPT_OUT =
US states (CCPA/CPRA) - opt-out model
'opt_out'- CONSENT_MODE_IMPLIED =
Canada (non-Quebec) - implied consent
'implied'- GDPR_COUNTRIES =
EU/EEA countries requiring GDPR consent
%w[AT BE BG HR CY CZ DK EE FI FR DE GR HU IE IT LV LT LU MT NL PL PT RO SK SI ES SE GB IS LI NO CH].freeze
- MODE_STRICTNESS =
Consent mode strictness ranking (higher = stricter)
{ CONSENT_MODE_OPT_OUT => 1, # Least strict - implied consent, opt-out CONSENT_MODE_IMPLIED => 2, # Medium - implied consent CONSENT_MODE_OPT_IN => 3 # Most strict - requires explicit opt-in }.freeze
Class Method Summary collapse
-
.build_defaults(mode, country, region) ⇒ Hash
Build default consent preferences based on mode (without persisting).
-
.determine_mode(country, region) ⇒ String
Determine consent mode based on visitor location.
-
.initialize_defaults(party, country:, region:) ⇒ Hash
Initialize default consent preferences for a Party based on geo Uses typed jsonb_accessor fields for clean access.
-
.requires_reconsent?(party, current_country:, current_region:) ⇒ Boolean
Check if user needs to re-consent due to moving to a stricter region.
-
.stricter_mode?(new_mode, stored_mode) ⇒ Boolean
Check if a consent mode is stricter than another.
-
.update_consent(party, analytics:, marketing:, mode: nil, country: nil, region: nil) ⇒ Hash
Update consent preferences when user changes them Uses typed accessors for clean updates.
Class Method Details
.build_defaults(mode, country, region) ⇒ Hash
Build default consent preferences based on mode (without persisting)
109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'app/services/tracking/consent_preferences.rb', line 109 def build_defaults(mode, country, region) is_permissive = mode != CONSENT_MODE_OPT_IN { 'consent_mode' => mode, 'consent_country' => country, 'consent_region' => region, 'consent_analytics' => is_permissive, 'consent_marketing' => is_permissive # NOTE: consent_updated_at intentionally NOT set on auto-defaults # Only set when user explicitly interacts with consent banner } end |
.determine_mode(country, region) ⇒ String
Determine consent mode based on visitor location
27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'app/services/tracking/consent_preferences.rb', line 27 def determine_mode(country, region) country = country&.upcase region = region&.upcase if gdpr_country?(country) || quebec?(country, region) CONSENT_MODE_OPT_IN elsif country == 'US' CONSENT_MODE_OPT_OUT elsif country == 'CA' CONSENT_MODE_IMPLIED else CONSENT_MODE_OPT_OUT # Default for unknown regions end end |
.initialize_defaults(party, country:, region:) ⇒ Hash
Initialize default consent preferences for a Party based on geo
Uses typed jsonb_accessor fields for clean access
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'app/services/tracking/consent_preferences.rb', line 82 def initialize_defaults(party, country:, region:) return party. if party..present? mode = determine_mode(country, region) is_permissive = mode != CONSENT_MODE_OPT_IN # Use typed accessors from jsonb_accessor gem # NEVER set consent_updated_at on auto-defaults - only set when user explicitly # interacts with consent banner (via update_consent method) party.update_columns( consent_preferences: { 'consent_mode' => mode, 'consent_country' => country, 'consent_region' => region, 'consent_analytics' => is_permissive, 'consent_marketing' => is_permissive } ) party. end |
.requires_reconsent?(party, current_country:, current_region:) ⇒ Boolean
Check if user needs to re-consent due to moving to a stricter region
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'app/services/tracking/consent_preferences.rb', line 57 def (party, current_country:, current_region:) return false if party.blank? || party..blank? stored_mode = party. current_mode = determine_mode(current_country, current_region) # If moving to stricter region, check if they've consented at that level return false unless stricter_mode?(current_mode, stored_mode) # They're in a stricter region - did they explicitly consent there? # Check if stored consent was from an opt-in action (not implicit) stored_country = party. stored_region = party. # If their stored consent was from a permissive region, they need re-consent = determine_mode(stored_country, stored_region) != CONSENT_MODE_OPT_IN end |
.stricter_mode?(new_mode, stored_mode) ⇒ Boolean
Check if a consent mode is stricter than another
46 47 48 49 50 |
# File 'app/services/tracking/consent_preferences.rb', line 46 def stricter_mode?(new_mode, stored_mode) return false if new_mode.blank? || stored_mode.blank? MODE_STRICTNESS.fetch(new_mode, 0) > MODE_STRICTNESS.fetch(stored_mode, 0) end |
.update_consent(party, analytics:, marketing:, mode: nil, country: nil, region: nil) ⇒ Hash
Update consent preferences when user changes them
Uses typed accessors for clean updates
131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'app/services/tracking/consent_preferences.rb', line 131 def (party, analytics:, marketing:, mode: nil, country: nil, region: nil) updates = { consent_analytics: analytics, consent_marketing: marketing, consent_updated_at: Time.current } updates[:consent_mode] = mode if mode.present? updates[:consent_country] = country if country.present? updates[:consent_region] = region if region.present? # Update using typed accessors party.update!(updates) party. end |