Class: Subscriber

Inherits:
ApplicationRecord show all
Includes:
Models::Auditable
Defined in:
app/models/subscriber.rb

Overview

== Schema Information

Table name: subscribers
Database name: primary

id :integer not null, primary key
active :boolean default(TRUE), not null
email_address :string
reconciled :boolean default(FALSE), not null
created_at :datetime not null
updated_at :datetime not null
creator_id :integer
customer_id :integer
subscriber_list_id :integer
updater_id :integer

Indexes

idx_unique_subscribers (subscriber_list_id,customer_id) UNIQUE
index_subscribers_on_active (active)
index_subscribers_on_customer_id (customer_id)
index_subscribers_on_email_address (email_address)

Foreign Keys

fk_rails_... (customer_id => parties.id) ON DELETE => cascade
fk_rails_... (subscriber_list_id => subscriber_lists.id) ON DELETE => cascade

Constant Summary collapse

ACTIVITY_DELIVERY_STATES =

campaign_delivery states that mean an email was actually transmitted or
attempted — the bar for "has seen activity" that forces archival over
deletion. pending/queued/suppressed/duplicate/exception sent nothing.

%w[sent bounced].freeze

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Constants included from Schedulable

Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary collapse

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has many collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#customer_idObject (readonly)



50
# File 'app/models/subscriber.rb', line 50

validates :customer_id, presence: true, uniqueness: { scope: :subscriber_list_id, message: 'can only be present once on each subscriber list' }, if: proc { |s| s.subscriber_list&.customer? }

#email_addressObject (readonly)

subscriber_list is optional (and nil once a subscriber is archived/detached),
so guard the list-scoped validations with &. — a nil list has no scope to
enforce uniqueness against.

Validations (if => proc { |s| s.subscriber_list&.email? } ):

  • Email_format
  • Presence
  • Uniqueness ({ scope: :subscriber_list_id, message: 'can only be present once on each subscriber list' })


49
# File 'app/models/subscriber.rb', line 49

validates :email_address, email_format: true, presence: true, uniqueness: { scope: :subscriber_list_id, message: 'can only be present once on each subscriber list' }, if: proc { |s| s.subscriber_list&.email? }

#update_customerObject

Returns the value of attribute update_customer.



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

def update_customer
  @update_customer
end

Class Method Details

.activeActiveRecord::Relation<Subscriber>

A relation of Subscribers that are active. Active Record Scope

Returns:

See Also:



57
# File 'app/models/subscriber.rb', line 57

scope :active, -> { where(active: true) }

.archivedActiveRecord::Relation<Subscriber>

A relation of Subscribers that are archived. Active Record Scope

Returns:

See Also:



59
# File 'app/models/subscriber.rb', line 59

scope :archived, -> { where.not(archived_at: nil) }

.inactiveActiveRecord::Relation<Subscriber>

A relation of Subscribers that are inactive. Active Record Scope

Returns:

See Also:



58
# File 'app/models/subscriber.rb', line 58

scope :inactive, -> { where(active: false) }

.unarchivedActiveRecord::Relation<Subscriber>

A relation of Subscribers that are unarchived. Active Record Scope

Returns:

See Also:



60
# File 'app/models/subscriber.rb', line 60

scope :unarchived, -> { where(archived_at: nil) }

.with_campaign_activityActiveRecord::Relation<Subscriber>

A relation of Subscribers that are with campaign activity. Active Record Scope

Returns:

See Also:



62
# File 'app/models/subscriber.rb', line 62

scope :with_campaign_activity, -> { where(id: CampaignDelivery.where(state: ACTIVITY_DELIVERY_STATES).select(:subscriber_id)) }

.with_customerActiveRecord::Relation<Subscriber>

A relation of Subscribers that are with customer. Active Record Scope

Returns:

See Also:



64
65
66
67
68
# File 'app/models/subscriber.rb', line 64

scope :with_customer, -> {
  joins("left outer JOIN contact_points on contact_points.id = (select max(id) from contact_points where contact_points.detail = subscribers.email_address)
		left outer JOIN parties contacts on contacts.id = contact_points.party_id and contacts.type = 'Contact'
		left outer JOIN parties customers on customers.id = coalesce(subscribers.customer_id, contacts.customer_id, contact_points.party_id)")
}

Instance Method Details

#activateObject



85
86
87
# File 'app/models/subscriber.rb', line 85

def activate
  update(active: true)
end

#archive!Boolean

Retain the subscriber for record-keeping: stamp archived_at and detach it
from its list (the list is being removed; subscriber_list_id is nullable).
update_columns bypasses validations/callbacks — a detached subscriber has no
list to scope the email/customer uniqueness checks against.

Returns:

  • (Boolean)


112
113
114
# File 'app/models/subscriber.rb', line 112

def archive!
  update_columns(archived_at: Time.current, subscriber_list_id: nil)
end

#archived?Boolean

Returns whether this subscriber has been archived for record-keeping.

Returns:

  • (Boolean)

    whether this subscriber has been archived for record-keeping



102
103
104
# File 'app/models/subscriber.rb', line 102

def archived?
  archived_at.present?
end

#campaign_deliveriesActiveRecord::Relation<CampaignDelivery>

dependent: :destroy so a subscriber with NO delivery activity takes its
(pending/suppressed/…) deliveries with it on hard-delete. Subscribers WITH
activity are archived instead (archive_instead_of_destroy_if_activity, which
aborts the destroy before this cascade runs), so their deliveries are kept.

Returns:

See Also:



42
# File 'app/models/subscriber.rb', line 42

has_many :campaign_deliveries, dependent: :destroy

#campaignsActiveRecord::Relation<Campaign>

Returns:

See Also:



44
# File 'app/models/subscriber.rb', line 44

has_many :campaigns, through: :subscriber_list

#contact_pointsActiveRecord::Relation<ContactPoint>

Returns:

See Also:



43
# File 'app/models/subscriber.rb', line 43

has_many :contact_points, foreign_key: :detail, primary_key: :email_address

#customerCustomer

Returns:

See Also:



35
# File 'app/models/subscriber.rb', line 35

belongs_to :customer, optional: true

#deactivateObject



89
90
91
# File 'app/models/subscriber.rb', line 89

def deactivate
  update(active: false)
end

#determined_customerObject



143
144
145
# File 'app/models/subscriber.rb', line 143

def determined_customer
  customer || party_customer
end

#email_preferenceEmailPreference



36
# File 'app/models/subscriber.rb', line 36

belongs_to :email_preference, optional: true, primary_key: :email, foreign_key: :email_address

#nameObject



127
128
129
# File 'app/models/subscriber.rb', line 127

def name
  party&.full_name
end

#partyObject



131
132
133
# File 'app/models/subscriber.rb', line 131

def party
  contact_points.where.not(party_id: nil).first&.party
end

#party_customerObject



135
136
137
138
139
140
141
# File 'app/models/subscriber.rb', line 135

def party_customer
  if party.respond_to?(:customer)
    party.customer
  else
    party
  end
end

#populate_email_if_missingObject



151
152
153
154
155
156
157
# File 'app/models/subscriber.rb', line 151

def populate_email_if_missing
  return if email_address.present?
  return unless subscriber_list&.email?
  return unless customer

  self.email_address = customer.email
end

#remove!Boolean

The safe "remove a subscriber" entry point: archive it for record-keeping if
it has delivery activity, otherwise hard-delete it (cascading its inert
pending/suppressed deliveries). A bare #destroy is blocked for subscribers
with activity (see prevent_hard_delete_with_activity), so callers that mean
"remove from the list" should use this.

Returns:

  • (Boolean)


123
124
125
# File 'app/models/subscriber.rb', line 123

def remove!
  seen_campaign_activity? ? archive! : destroy!
end

#seen_campaign_activity?Boolean

True once an email was actually transmitted to / attempted for this
subscriber (a sent or bounced delivery exists).

Returns:

  • (Boolean)


97
98
99
# File 'app/models/subscriber.rb', line 97

def seen_campaign_activity?
  campaign_deliveries.where(state: ACTIVITY_DELIVERY_STATES).exists?
end

#subscriber_listSubscriberList



34
# File 'app/models/subscriber.rb', line 34

belongs_to :subscriber_list, inverse_of: :subscribers, optional: true

#update_linked_contact_pointsObject



147
148
149
# File 'app/models/subscriber.rb', line 147

def update_linked_contact_points
  ContactPoint.where(detail: email_address_was).update_all(detail: email_address)
end