Class: MaquinaNewsletters::Newsletter
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- MaquinaNewsletters::Newsletter
- Defined in:
- app/models/maquina_newsletters/newsletter.rb
Constant Summary collapse
- STATUSES =
%w[draft approved scheduled sending sent failed].freeze
Class Method Summary collapse
-
.compose_datetime(date, time) ⇒ Object
Combine a “yyyy-mm-dd” date string and an “HH:MM” time string into a Time in the app zone.
Instance Method Summary collapse
-
#actionable? ⇒ Boolean
Has at least one state-transition action available (drives whether the show page renders an actions footer at all).
-
#approve! ⇒ Object
state transitions (called by controllers / jobs).
- #approved? ⇒ Boolean
- #back_to_draft! ⇒ Object
-
#draft? ⇒ Boolean
status predicates.
-
#enqueue_send ⇒ Object
Enqueues the first batch of this newsletter’s send.
- #failed? ⇒ Boolean
- #mark_failed! ⇒ Object
- #mark_sent! ⇒ Object
-
#recipient_batch(recipients, index) ⇒ Object
Returns the slice of
recipientsthat belongs to the batch atindex. -
#resolved_recipients ⇒ Object
Returns the array of email addresses this newsletter would be sent to: queries configured recipient model + scope, plucks the email attribute, subtracts the exclusion list.
- #schedule!(at:, batch_size:) ⇒ Object
- #scheduled? ⇒ Boolean
-
#scheduled_time ⇒ Object
The schedule form’s time dropdown pre-selects this (the time-of-day part of the stored scheduled_at).
- #sending? ⇒ Boolean
- #sent? ⇒ Boolean
- #start_sending! ⇒ Object
-
#test_sendable? ⇒ Boolean
A test email may be sent while the newsletter is still being prepared, but not once it is sending/sent or has failed.
- #unschedule! ⇒ Object
Class Method Details
.compose_datetime(date, time) ⇒ Object
Combine a “yyyy-mm-dd” date string and an “HH:MM” time string into a Time in the app zone. Returns nil when no date is given. Used by the schedule action, which gets the two parts straight from the form.
70 71 72 73 74 75 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 70 def self.compose_datetime(date, time) date = date.to_s.strip return nil if date.empty? Time.zone.parse("#{date} #{time.to_s.strip.presence || "00:00"}") end |
Instance Method Details
#actionable? ⇒ Boolean
Has at least one state-transition action available (drives whether the show page renders an actions footer at all). sending/sent are terminal for the UI — nothing to do but watch.
26 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 26 def actionable? = !(sending? || sent?) |
#approve! ⇒ Object
state transitions (called by controllers / jobs)
33 34 35 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 33 def approve! update!(status: "approved") end |
#approved? ⇒ Boolean
17 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 17 def approved? = status == "approved" |
#back_to_draft! ⇒ Object
45 46 47 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 45 def back_to_draft! update!(status: "draft", scheduled_at: nil, batch_size: 0) end |
#draft? ⇒ Boolean
status predicates
16 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 16 def draft? = status == "draft" |
#enqueue_send ⇒ Object
Enqueues the first batch of this newsletter’s send. A single path serves both immediate sends and future-scheduled sends, and both batch_size 0 and >0: SendBatchJob (batch 0) owns the scheduled → sending transition and recipients_count (Sidecar A1/A3/A4). There is no MarkSentIfCompleteJob.
81 82 83 84 85 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 81 def enqueue_send SendBatchJob .set(wait_until: scheduled_at || Time.current) .perform_later(id, 0) end |
#failed? ⇒ Boolean
21 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 21 def failed? = status == "failed" |
#mark_failed! ⇒ Object
57 58 59 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 57 def mark_failed! update!(status: "failed") end |
#mark_sent! ⇒ Object
53 54 55 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 53 def mark_sent! update!(status: "sent", sent_at: Time.current) end |
#recipient_batch(recipients, index) ⇒ Object
Returns the slice of recipients that belongs to the batch at index. batch_size 0 (Sidecar A4) means a single batch of everyone.
89 90 91 92 93 94 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 89 def recipient_batch(recipients, index) slice_size = batch_size.zero? ? recipients.size : batch_size return [] if slice_size.zero? recipients.each_slice(slice_size).to_a[index] || [] end |
#resolved_recipients ⇒ Object
Returns the array of email addresses this newsletter would be sent to: queries configured recipient model + scope, plucks the email attribute, subtracts the exclusion list. Output is downcased, deduped, and sorted.
The result is sorted (Sidecar C1) to give a STABLE order. SendBatchJob re-queries this list at the start of each batch (spec §6) and slices it by index; without a deterministic order, recipients could shift between day-apart batches and be double-sent or skipped. Sorting in Ruby (not via SQL ORDER BY) keeps the order stable across DB adapters.
105 106 107 108 109 110 111 112 113 114 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 105 def resolved_recipients model_class = MaquinaNewsletters.recipient_class scope_name = MaquinaNewsletters.recipient_scope attr_name = MaquinaNewsletters.recipient_email_attr pool = model_class.public_send(scope_name).pluck(attr_name) excluded = MaquinaNewsletters::ExcludedEmail.pluck(:email) (pool.compact.map(&:downcase) - excluded.map(&:downcase)).uniq.sort end |
#schedule!(at:, batch_size:) ⇒ Object
37 38 39 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 37 def schedule!(at:, batch_size:) update!(status: "scheduled", scheduled_at: at, batch_size: batch_size) end |
#scheduled? ⇒ Boolean
18 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 18 def scheduled? = status == "scheduled" |
#scheduled_time ⇒ Object
The schedule form’s time dropdown pre-selects this (the time-of-day part of the stored scheduled_at). The date picker reads scheduled_at directly.
63 64 65 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 63 def scheduled_time scheduled_at&.strftime("%H:%M") end |
#sending? ⇒ Boolean
19 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 19 def sending? = status == "sending" |
#sent? ⇒ Boolean
20 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 20 def sent? = status == "sent" |
#start_sending! ⇒ Object
49 50 51 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 49 def start_sending! update!(status: "sending") end |
#test_sendable? ⇒ Boolean
A test email may be sent while the newsletter is still being prepared, but not once it is sending/sent or has failed.
30 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 30 def test_sendable? = draft? || approved? || scheduled? |
#unschedule! ⇒ Object
41 42 43 |
# File 'app/models/maquina_newsletters/newsletter.rb', line 41 def unschedule! update!(status: "approved", scheduled_at: nil) end |