Class: SubscriberList

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

Overview

== Schema Information

Table name: subscriber_lists
Database name: primary

id :integer not null, primary key
add_all_emails :boolean
customer_search_params :jsonb
list_type :string
name :string
created_at :datetime not null
updated_at :datetime not null
creator_id :integer
updater_id :integer

Constant Summary collapse

LIST_TYPES =

Recognised list types.

%w[email_static email_dynamic customer].freeze

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Constants included from Schedulable

Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary collapse

Has many collapse

Has and belongs to many collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #creator, #should_not_save_version, #stamp_record, #updater

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_search_paramsObject (readonly)



39
# File 'app/models/subscriber_list.rb', line 39

validates :customer_search_params, presence: true, if: :email_dynamic?

#imported_subscribers_csvObject

Returns the value of attribute imported_subscribers_csv.



46
47
48
# File 'app/models/subscriber_list.rb', line 46

def imported_subscribers_csv
  @imported_subscribers_csv
end

#list_typeObject (readonly)



38
# File 'app/models/subscriber_list.rb', line 38

validates :name, :list_type, presence: true

#nameObject (readonly)



38
# File 'app/models/subscriber_list.rb', line 38

validates :name, :list_type, presence: true

Class Method Details

.customersActiveRecord::Relation<SubscriberList>

A relation of SubscriberLists that are customers. Active Record Scope

Returns:

See Also:



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

scope :customers, -> { where(list_type: 'customer') }

.ensure_unique_name(name) ⇒ Object



48
49
50
51
52
53
# File 'app/models/subscriber_list.rb', line 48

def self.ensure_unique_name(name)
  unique_name = name.dup
  existing_list = where(name: unique_name).exists?
  unique_name << " #{Time.current.to_fs(:compact)}" if existing_list # we need to make the name unique
  unique_name
end

Instance Method Details

#campaignsActiveRecord::Relation<Campaign>

Returns:

See Also:



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

has_and_belongs_to_many :campaigns

#customer?Boolean

Returns:

  • (Boolean)


59
60
61
# File 'app/models/subscriber_list.rb', line 59

def customer?
  list_type == 'customer'
end

#email?Boolean

Returns:

  • (Boolean)


55
56
57
# File 'app/models/subscriber_list.rb', line 55

def email?
  %w[email_static email_dynamic].include?(list_type)
end

#email_dynamic?Boolean

Returns:

  • (Boolean)


63
64
65
# File 'app/models/subscriber_list.rb', line 63

def email_dynamic?
  list_type == 'email_dynamic'
end

#generate_subscribersObject



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'app/models/subscriber_list.rb', line 167

def generate_subscribers
  return true if list_type.in?(%w[email_static customer])

  results = perform_search_query
  email_addresses = []
  if add_all_emails.to_b
    customer_ids = results.where.not(state: %w[closed bankrupt]).reorder('').ids
    email_addresses = ContactPoint.emails.under_customer_ids(customer_ids).distinct.pluck(:detail)
  else
    results.each do |search_result|
      if (email = search_result.customer_main_email || search_result.customer_first_contact_email)
        email_addresses << email
      end
    end
  end

  email_addresses.uniq! # remove any duplicates

  timestamp = Time.current

  logger.info "[generate_subscribers] Subscriber list generation candidates: #{subscribers.size}"

  # Who's already subscribed, who's not
  already_subscribed = subscribers.pluck(:email_address) & email_addresses
  logger.info "[generate_subscribers] * already subscribed: #{already_subscribed.size}"

  # inactivate those not already_subscribed
  not_on_our_list = subscribers.where.not(email_address: already_subscribed)
  logger.info "[generate_subscribers] * Inactivating those not on our candidate list: #{not_on_our_list.size}"
  not_on_our_list.update_all(active: false, updated_at: timestamp)

  # Those already subscribed gets activated in case they where not active
  if already_subscribed.present?
    on_our_list_inactive = subscribers.where(email_address: already_subscribed).where.not(active: true)
    logger.info "[generate_subscribers] * Activating those from our candidate list which were inactive: #{on_our_list_inactive.size}"
    on_our_list_inactive.update_all(active: true, updated_at: timestamp)

    existing_active_unsubscribed = subscribers.where(active: true).joins(:email_preference).merge(EmailPreference.completely_unsubscribed)
    logger.info "[generate_subscribers] * De-activating #{existing_active_unsubscribed.size} subscribers who have completely unsubscribed"
    existing_active_unsubscribed.update_all(active: false, updated_at: timestamp)
  end

  # From our list who remains to create
  not_subscribed = email_addresses - already_subscribed

  # But we will trim it down first and remove everyone who is completele unsubscribed to emails
  completely_unsubscribed_emails = EmailPreference.completely_unsubscribed.where(email: not_subscribed).pluck(:email)
  logger.info "Out of #{not_subscribed.size} subscribers to add, removing #{completely_unsubscribed_emails.size} which are completely unsubcribed"

  not_subscribed -= completely_unsubscribed_emails

  if not_subscribed.present?
    logger.info "[generate_subscribers] * Need to now create #{not_subscribed.size} new subscribers"
    good_emails = not_subscribed.select { |email| email =~ ContactPoint::EMAIL_REGEXP } # filter out any bad email addresses, else we'll get a PG::SyntaxError
    new_subscribers = good_emails.map do |email|
      { subscriber_list_id: id,
                           email_address: email,
                           active: true }
    end
    result = Subscriber.insert_all(new_subscribers) if new_subscribers.any?
    logger.info "[generate_subscribers] created #{result&.rows&.size || 0} subscribers"
  end
  logger.info '[generate_subscribers] * All done with generate_subscribers'

  true
end

#import_subscribers_from_csvObject



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'app/models/subscriber_list.rb', line 67

def import_subscribers_from_csv
  return if imported_subscribers_csv.blank?

  emails = []
  CSV.foreach(imported_subscribers_csv.path) do |row|
    emails << row[0] if row[0].present?
  end
  if add_all_emails == true
    emails.each do |email|
      parties = Party.joins(:contact_points).merge(ContactPoint.emails.where(detail: email))
      parties.each do |p|
        if p.is_a?(Contact) && p.customer.present?
          emails += p.customer.all_emails
        elsif p.is_a?(Customer)
          emails += p.all_emails
        end
      end
    end
  end
  emails.uniq.each do |email|
    subscribers << Subscriber.new(email_address: email) unless subscribers.any? { |s| s.email_address == email }
  end
end

#name_and_typeObject

Label used by the subscriber-list picker on the campaign form. Includes
the creation date so duplicates with the same name are distinguishable.
Subscriber count is intentionally excluded โ€” for email_dynamic lists
it would fire perform_search_query_count (a full customer search) per
option on every render of the campaign form.



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

def name_and_type
  "#{name} [#{list_type}] ยท #{created_at.to_date}"
end

#perform_search_queryObject



161
162
163
164
165
# File 'app/models/subscriber_list.rb', line 161

def perform_search_query
  assert_valid_customer_search_params!
  cs = CustomerSearch.create(query_params: customer_search_params)
  cs.perform(nil, nil, nil, false, nil, true, true)
end

#perform_search_query_countObject



155
156
157
158
159
# File 'app/models/subscriber_list.rb', line 155

def perform_search_query_count
  assert_valid_customer_search_params!
  cs = CustomerSearch.new(query_params: customer_search_params, selected_columns: [:id])
  cs.fast_count
end

#subscriber_countObject



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

def subscriber_count
  if email_dynamic? && subscribers.empty?
    perform_search_query_count
  else
    subscribers.active.size
  end
end

#subscriber_counts_groupedObject



143
144
145
146
147
148
149
150
151
152
153
# File 'app/models/subscriber_list.rb', line 143

def subscriber_counts_grouped
  if email_dynamic? && subscribers.empty?
    count = perform_search_query_count
    { 'active' => count }
  else
    {
      'active' => subscribers.active.count,
      'inactive' => subscribers.inactive.count
    }
  end
end

#subscribersActiveRecord::Relation<Subscriber>

No dependent: :destroy โ€” subscribers are disposed individually so the ones
with delivery activity are archived (kept) rather than deleted. dispose runs
before the list row is removed; archived subscribers are detached so the
subscriber_list_id ON DELETE CASCADE FK doesn't reclaim them.

Returns:

See Also:



33
# File 'app/models/subscriber_list.rb', line 33

has_many :subscribers, inverse_of: :subscriber_list