Class: EmailTemplate
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- EmailTemplate
- Includes:
- Models::Auditable, Models::EventPublishable, PgSearch::Model
- Defined in:
- app/models/email_template.rb
Overview
== Schema Information
Table name: email_templates
Database name: primary
id :integer not null, primary key
bcc :string(255)
body :text
body_v4 :text
body_v4_email :text
category :string
cc :string(255)
css :text
css_v4 :text
default_from :string
default_reply_to :string
description :string(255)
disable_premailer :boolean default(FALSE), not null
disable_rich_editing :boolean default(FALSE), not null
from :string(255)
group :string
preview_text :string
redactor_4_ready :boolean default(FALSE), not null
resource_type :string(255)
state :enum default("active")
stylesheet :string(255)
subject :string(255)
system_code :string(20)
template :string
to :string(255)
uses_redactor_v4 :boolean
created_at :datetime not null
updated_at :datetime not null
creator_id :integer
resource_id :integer
updater_id :integer
Indexes
by_state_resource_is_null (state) WHERE (resource_id IS NULL)
email_templates_group_idx (group)
index_email_templates_on_resource_type_and_resource_id (resource_type,resource_id)
index_email_templates_on_state (state)
index_email_templates_on_system_code (system_code) UNIQUE
Defined Under Namespace
Classes: ContentMigrator
Constant Summary collapse
- DEFAULT_STYLESHEET =
Default stylesheet.
'default'- DEFAULT_TEMPLATE =
Default template.
'email'- CATEGORIES =
Categories.
%w[announcements events newsletters promotions transactional webinars reviews].freeze
Constants included from Models::Auditable
Models::Auditable::ALWAYS_IGNORED
Constants included from Schedulable
Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
- #body ⇒ Object
- #category ⇒ Object readonly
- #description ⇒ Object readonly
- #state ⇒ Object readonly
- #subject ⇒ Object
- #system_code ⇒ Object readonly
- #template ⇒ Object readonly
Belongs to collapse
-
#resource ⇒ Resource
To represent ownership, thinking Employee, Company or nil.
Methods included from Models::Auditable
Has many collapse
- #activity_chain_types ⇒ ActiveRecord::Relation<ActivityChainType>
- #activity_types ⇒ ActiveRecord::Relation<ActivityType>
- #activity_types_by_result ⇒ ActiveRecord::Relation<ActivityType>
- #campaign_emails ⇒ ActiveRecord::Relation<CampaignEmail>
- #campaigns ⇒ ActiveRecord::Relation<Campaign>
- #communications ⇒ ActiveRecord::Relation<Communication>
- #embedded_assets ⇒ ActiveRecord::Relation<EmbeddedAsset>
Class Method Summary collapse
- .available_stylesheets ⇒ Object
- .available_templates ⇒ Object
-
.blank_template_id ⇒ Object
Cached lookup for the BLANK template used for new communications.
-
.non_campaign ⇒ ActiveRecord::Relation<EmailTemplate>
A relation of EmailTemplates that are non campaign.
-
.non_system ⇒ ActiveRecord::Relation<EmailTemplate>
A relation of EmailTemplates that are non system.
-
.render_signature(sender_party, theme: :default) ⇒ Object
Renders a signature as text using the signature partial we have already theme: :default (creamy) or :technical (grey-blue for support case templates).
- .resource_for_select ⇒ Object
- .select_options(conditions = nil) ⇒ Object
-
.select_options_r4_campaign ⇒ Object
Select options for campaign email cloning - only Redactor 4 ready templates Returns array of [label, id] with Redactor 4 badge indicator.
Instance Method Summary collapse
- #all_activity_type_referenced ⇒ Object
- #allowed_categories(account) ⇒ Object
- #belongs_to_resource ⇒ Object
- #belongs_to_resource=(val) ⇒ Object
-
#body_v4=(val) ⇒ Object
Setter for body_v4 that also clears the template cache.
- #deep_dup ⇒ Object
-
#editable_body ⇒ Object
Returns the body content for editing in Redactor Uses semantic v4 content, not the email-ready version.
-
#effective_body ⇒ Object
Returns the body content to use for rendering/sending emails When redactor_4_ready is true, uses v4 content; otherwise uses legacy body + css.
-
#effective_css ⇒ Object
Returns the CSS to use for rendering When redactor_4_ready is true, uses v4 CSS (usually empty); otherwise uses legacy CSS.
-
#effective_template ⇒ Object
Returns the template to use for sending emails When redactor_4_ready is true, uses 'v4' template (raw output, body_v4_email is already complete HTML) Otherwise uses the configured template (or default).
-
#has_legacy_r3_content? ⇒ Boolean
Check if this template has meaningful Redactor 3 content Returns false for new records, blank body, or placeholder content.
-
#migrated_to_v4? ⇒ Boolean
Check if this template has been migrated to Redactor 4.
-
#needs_v4_migration? ⇒ Boolean
Check if this template has legacy content that needs migration.
- #ok_to_delete? ⇒ Boolean
-
#r4_only? ⇒ Boolean
Check if this template should be R4-only (no toggle, no R3 panels) True for new templates or templates without meaningful R3 content.
- #referenced? ⇒ Boolean
-
#render_body(options = nil) ⇒ Object
Renders body as Liquid Markup template.
-
#render_body_v4(options = nil) ⇒ Object
Renders body_v4_email content directly, bypassing redactor_4_ready? check Used for admin previews when body_v4 exists but template is not yet marked ready.
-
#render_editable_body(options = nil) ⇒ Object
Renders body_v4 content for loading into Redactor 4 editor Uses body_v4 (editor content), not body_v4_email (email output).
- #render_from(options = nil) ⇒ Object
- #render_subject(options = nil) ⇒ Object
- #to_s ⇒ Object
Methods included from Models::EventPublishable
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
Methods included from Models::AfterCommittable
Instance Attribute Details
#body ⇒ Object
83 |
# File 'app/models/email_template.rb', line 83 validates :subject, :body, :description, :template, :category, presence: true |
#category ⇒ Object (readonly)
83 |
# File 'app/models/email_template.rb', line 83 validates :subject, :body, :description, :template, :category, presence: true |
#description ⇒ Object (readonly)
83 |
# File 'app/models/email_template.rb', line 83 validates :subject, :body, :description, :template, :category, presence: true |
#state ⇒ Object (readonly)
85 86 |
# File 'app/models/email_template.rb', line 85 validates :state, inclusion: { in: %w[active], if: :system_code, message: 'System code presence requires an active state' } |
#subject ⇒ Object
83 |
# File 'app/models/email_template.rb', line 83 validates :subject, :body, :description, :template, :category, presence: true |
#system_code ⇒ Object (readonly)
84 |
# File 'app/models/email_template.rb', line 84 validates :system_code, length: { maximum: 20 }, uniqueness: true, allow_nil: true |
#template ⇒ Object (readonly)
83 |
# File 'app/models/email_template.rb', line 83 validates :subject, :body, :description, :template, :category, presence: true |
Class Method Details
.available_stylesheets ⇒ Object
152 153 154 155 156 |
# File 'app/models/email_template.rb', line 152 def self.available_stylesheets stylesheets_dir = Rails.public_path.join('stylesheets/emails/*.css') files = Dir.glob(stylesheets_dir) files.filter_map { |f| File.basename(f).split('.')[0] }.uniq.sort end |
.available_templates ⇒ Object
158 159 160 161 162 163 |
# File 'app/models/email_template.rb', line 158 def self.available_templates template_dir = Rails.root.join('app/views/communication_mailer/*.erb') files = Dir.glob(template_dir) files.delete_if { |x| x.include?('/_') } # remove partials files.filter_map { |f| File.basename(f).split('.')[0] }.uniq.sort end |
.blank_template_id ⇒ Object
Cached lookup for the BLANK template used for new communications
148 149 150 |
# File 'app/models/email_template.rb', line 148 def self.blank_template_id @blank_template_id ||= find_by(system_code: 'BLANK')&.id end |
.non_campaign ⇒ ActiveRecord::Relation<EmailTemplate>
A relation of EmailTemplates that are non campaign. Active Record Scope
108 |
# File 'app/models/email_template.rb', line 108 scope :non_campaign, -> { where("description not like 'Template for campaign email%'") } |
.non_system ⇒ ActiveRecord::Relation<EmailTemplate>
A relation of EmailTemplates that are non system. Active Record Scope
109 |
# File 'app/models/email_template.rb', line 109 scope :non_system, -> { where(system_code: [nil, '']) } |
.render_signature(sender_party, theme: :default) ⇒ Object
Renders a signature as text using the signature partial we have already
theme: :default (creamy) or :technical (grey-blue for support case templates)
327 328 329 330 331 |
# File 'app/models/email_template.rb', line 327 def self.render_signature(sender_party, theme: :default) # av = ActionView::Base.new(Heatwave::Application.config.paths['app/views'].first) ApplicationController.render(partial: 'communications/signature', layout: false, locals: { sender_party:, theme: }) end |
.resource_for_select ⇒ Object
165 166 167 168 169 |
# File 'app/models/email_template.rb', line 165 def self.resource_for_select [] + Employee..map { |e| [e[0], "Employee|#{e[1]}"] } + Company..map { |e| [e[0], "Company|#{e[1]}"] } end |
.select_options(conditions = nil) ⇒ Object
127 128 129 130 131 132 133 134 135 |
# File 'app/models/email_template.rb', line 127 def self.(conditions = nil) res = order(:description).select(:id, :description, :subject) res = if conditions res.where(conditions) else res.active end res.map { |e| ["#{e.description || e.subject} [#{e.id}]", e.id] } end |
.select_options_r4_campaign ⇒ Object
Select options for campaign email cloning - only Redactor 4 ready templates
Returns array of [label, id] with Redactor 4 badge indicator
139 140 141 142 143 144 145 |
# File 'app/models/email_template.rb', line 139 def self. order(:description) .select(:id, :description, :subject) .where(group: 'campaign', redactor_4_ready: true) .active .map { |e| ["#{e.description || e.subject} [#{e.id}] 🟢 Redactor 4", e.id] } end |
Instance Method Details
#activity_chain_types ⇒ ActiveRecord::Relation<ActivityChainType>
75 |
# File 'app/models/email_template.rb', line 75 has_many :activity_chain_types, dependent: :nullify, inverse_of: :email_template |
#activity_types ⇒ ActiveRecord::Relation<ActivityType>
74 |
# File 'app/models/email_template.rb', line 74 has_many :activity_types, dependent: :nullify, inverse_of: :email_template |
#activity_types_by_result ⇒ ActiveRecord::Relation<ActivityType>
76 77 |
# File 'app/models/email_template.rb', line 76 has_many :activity_types_by_result, class_name: 'ActivityType', through: :activity_chain_types, source: :activity_type |
#all_activity_type_referenced ⇒ Object
186 187 188 |
# File 'app/models/email_template.rb', line 186 def all_activity_type_referenced activity_types | activity_types_by_result end |
#allowed_categories(account) ⇒ Object
179 180 181 182 183 184 |
# File 'app/models/email_template.rb', line 179 def allowed_categories(account) categories = EmailTemplate::CATEGORIES.dup categories.delete('transactional') unless account.has_role?('marketing_rep') categories << category categories.compact.uniq.sort end |
#belongs_to_resource ⇒ Object
198 199 200 |
# File 'app/models/email_template.rb', line 198 def belongs_to_resource resource ? "#{resource.class.name}|#{resource_id}" : nil end |
#belongs_to_resource=(val) ⇒ Object
190 191 192 193 194 195 196 |
# File 'app/models/email_template.rb', line 190 def belongs_to_resource=(val) if val.present? && !val.index('|').nil? self.resource_type, self.resource_id = val.split('|') else self.resource = nil end end |
#body_v4=(val) ⇒ Object
Setter for body_v4 that also clears the template cache
275 276 277 278 |
# File 'app/models/email_template.rb', line 275 def body_v4=(val) self[:body_v4] = val @body_v4_template = nil end |
#campaign_emails ⇒ ActiveRecord::Relation<CampaignEmail>
78 |
# File 'app/models/email_template.rb', line 78 has_many :campaign_emails |
#campaigns ⇒ ActiveRecord::Relation<Campaign>
79 |
# File 'app/models/email_template.rb', line 79 has_many :campaigns, -> { distinct }, through: :campaign_emails |
#communications ⇒ ActiveRecord::Relation<Communication>
80 |
# File 'app/models/email_template.rb', line 80 has_many :communications |
#deep_dup ⇒ Object
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'app/models/email_template.rb', line 111 def deep_dup deep_clone(except: %i[system_code resource_id resource_type category]) do |original, copy| next unless copy.is_a?(EmailTemplate) copy.description = "Copy of #{original.description}" if original.redactor_4_ready? copy.redactor_4_ready = true copy.body_v4 = original.body_v4 if original.body_v4.present? copy.body_v4_email = original.body_v4_email if original.body_v4_email.present? copy.css_v4 = original.css_v4 if original.css_v4.present? copy.body = '<p>This template uses Redactor 4. Edit the content in the editor below.</p>' copy.css = nil end end end |
#editable_body ⇒ Object
Returns the body content for editing in Redactor
Uses semantic v4 content, not the email-ready version
244 245 246 |
# File 'app/models/email_template.rb', line 244 def editable_body body_v4.presence || body end |
#effective_body ⇒ Object
Returns the body content to use for rendering/sending emails
When redactor_4_ready is true, uses v4 content; otherwise uses legacy body + css
234 235 236 237 238 239 240 |
# File 'app/models/email_template.rb', line 234 def effective_body if redactor_4_ready? body_v4_email.presence else body end end |
#effective_css ⇒ Object
Returns the CSS to use for rendering
When redactor_4_ready is true, uses v4 CSS (usually empty); otherwise uses legacy CSS
250 251 252 253 254 255 256 |
# File 'app/models/email_template.rb', line 250 def effective_css if redactor_4_ready? '' else css end end |
#effective_template ⇒ Object
Returns the template to use for sending emails
When redactor_4_ready is true, uses 'v4' template (raw output, body_v4_email is already complete HTML)
Otherwise uses the configured template (or default)
261 262 263 264 265 266 267 |
# File 'app/models/email_template.rb', line 261 def effective_template if redactor_4_ready? && body_v4_email.present? 'v4' else template.presence || DEFAULT_TEMPLATE end end |
#embedded_assets ⇒ ActiveRecord::Relation<EmbeddedAsset>
81 |
# File 'app/models/email_template.rb', line 81 has_many :embedded_assets, as: :parent, dependent: :destroy |
#has_legacy_r3_content? ⇒ Boolean
Check if this template has meaningful Redactor 3 content
Returns false for new records, blank body, or placeholder content
213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'app/models/email_template.rb', line 213 def has_legacy_r3_content? return false if new_record? return false if body.blank? # Check if body is just a placeholder (from copy or new template) placeholder_patterns = [ /\A\s*<p>\s*This template uses Redactor 4/i, %r{\A\s*<p>\s*</p>\s*\z}, /\A\s*\z/ ] placeholder_patterns.none? { |pattern| body.match?(pattern) } end |
#migrated_to_v4? ⇒ Boolean
Check if this template has been migrated to Redactor 4
207 208 209 |
# File 'app/models/email_template.rb', line 207 def migrated_to_v4? body_v4.present? end |
#needs_v4_migration? ⇒ Boolean
Check if this template has legacy content that needs migration
270 271 272 |
# File 'app/models/email_template.rb', line 270 def needs_v4_migration? body.present? && body_v4.blank? end |
#ok_to_delete? ⇒ Boolean
171 172 173 |
# File 'app/models/email_template.rb', line 171 def ok_to_delete? !referenced? and system_code.blank? end |
#r4_only? ⇒ Boolean
Check if this template should be R4-only (no toggle, no R3 panels)
True for new templates or templates without meaningful R3 content
228 229 230 |
# File 'app/models/email_template.rb', line 228 def r4_only? new_record? || !has_legacy_r3_content? end |
#referenced? ⇒ Boolean
175 176 177 |
# File 'app/models/email_template.rb', line 175 def referenced? activity_types.any? or activity_chain_types.any? or campaign_emails.any? end |
#render_body(options = nil) ⇒ Object
Renders body as Liquid Markup template
285 286 287 |
# File 'app/models/email_template.rb', line 285 def render_body( = nil) Liquid::Renderer.render(body_template_instance, , to_s) end |
#render_body_v4(options = nil) ⇒ Object
Renders body_v4_email content directly, bypassing redactor_4_ready? check
Used for admin previews when body_v4 exists but template is not yet marked ready
291 292 293 294 295 296 |
# File 'app/models/email_template.rb', line 291 def render_body_v4( = nil) return nil if body_v4_email.blank? template = Liquid::ParseEnvironment.parse(body_v4_email.to_s) Liquid::Renderer.render(template, , to_s) end |
#render_editable_body(options = nil) ⇒ Object
Renders body_v4 content for loading into Redactor 4 editor
Uses body_v4 (editor content), not body_v4_email (email output)
300 301 302 303 304 305 |
# File 'app/models/email_template.rb', line 300 def render_editable_body( = nil) return nil if body_v4.blank? template = Liquid::ParseEnvironment.parse(body_v4.to_s) Liquid::Renderer.render(template, , to_s) end |
#render_from(options = nil) ⇒ Object
311 312 313 |
# File 'app/models/email_template.rb', line 311 def render_from( = nil) Liquid::Renderer.render(sender_email_template_instance, , to_s) end |
#render_subject(options = nil) ⇒ Object
307 308 309 |
# File 'app/models/email_template.rb', line 307 def render_subject( = nil) Liquid::Renderer.render(subject_template_instance, , to_s) end |
#resource ⇒ Resource
To represent ownership, thinking Employee, Company or nil
72 |
# File 'app/models/email_template.rb', line 72 belongs_to :resource, polymorphic: true, optional: true |
#to_s ⇒ Object
333 334 335 |
# File 'app/models/email_template.rb', line 333 def to_s "[EmailTemplate:#{id}] #{description}" end |