Class: EmailTemplate
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- EmailTemplate
- Includes:
- Models::Auditable, 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 :integer 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'- DEFAULT_TEMPLATE =
'email'- CATEGORIES =
%w[announcements events newsletters promotions transactional webinars reviews].freeze
Constants included from Models::Auditable
Models::Auditable::ALWAYS_IGNORED
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>
- #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::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 Models::EventPublishable
Instance Attribute Details
#body ⇒ Object
75 |
# File 'app/models/email_template.rb', line 75 validates :subject, :body, :description, :template, :category, presence: true |
#category ⇒ Object (readonly)
75 |
# File 'app/models/email_template.rb', line 75 validates :subject, :body, :description, :template, :category, presence: true |
#description ⇒ Object (readonly)
75 |
# File 'app/models/email_template.rb', line 75 validates :subject, :body, :description, :template, :category, presence: true |
#state ⇒ Object (readonly)
77 78 |
# File 'app/models/email_template.rb', line 77 validates :state, inclusion: { in: %w[active], if: :system_code, message: 'System code presence requires an active state' } |
#subject ⇒ Object
75 |
# File 'app/models/email_template.rb', line 75 validates :subject, :body, :description, :template, :category, presence: true |
#system_code ⇒ Object (readonly)
76 |
# File 'app/models/email_template.rb', line 76 validates :system_code, length: { maximum: 20 }, uniqueness: true, allow_nil: true |
#template ⇒ Object (readonly)
75 |
# File 'app/models/email_template.rb', line 75 validates :subject, :body, :description, :template, :category, presence: true |
Class Method Details
.available_stylesheets ⇒ Object
136 137 138 139 140 |
# File 'app/models/email_template.rb', line 136 def self.available_stylesheets stylesheets_dir = Rails.public_path.join('stylesheets/emails/*.css') files = Dir.glob(stylesheets_dir) files.map { |f| File.basename(f).split('.')[0] }.compact.uniq.sort end |
.available_templates ⇒ Object
142 143 144 145 146 147 |
# File 'app/models/email_template.rb', line 142 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.map { |f| File.basename(f).split('.')[0] }.compact.uniq.sort end |
.blank_template_id ⇒ Object
Cached lookup for the BLANK template used for new communications
132 133 134 |
# File 'app/models/email_template.rb', line 132 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
92 |
# File 'app/models/email_template.rb', line 92 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
93 |
# File 'app/models/email_template.rb', line 93 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)
311 312 313 314 315 |
# File 'app/models/email_template.rb', line 311 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
149 150 151 152 153 |
# File 'app/models/email_template.rb', line 149 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
111 112 113 114 115 116 117 118 119 |
# File 'app/models/email_template.rb', line 111 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
123 124 125 126 127 128 129 |
# File 'app/models/email_template.rb', line 123 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>
68 |
# File 'app/models/email_template.rb', line 68 has_many :activity_chain_types, dependent: :nullify, inverse_of: :email_template |
#activity_types ⇒ ActiveRecord::Relation<ActivityType>
67 |
# File 'app/models/email_template.rb', line 67 has_many :activity_types, dependent: :nullify, inverse_of: :email_template |
#activity_types_by_result ⇒ ActiveRecord::Relation<ActivityType>
69 70 |
# File 'app/models/email_template.rb', line 69 has_many :activity_types_by_result, class_name: 'ActivityType', through: :activity_chain_types, source: :activity_type |
#all_activity_type_referenced ⇒ Object
170 171 172 |
# File 'app/models/email_template.rb', line 170 def all_activity_type_referenced activity_types | activity_types_by_result end |
#allowed_categories(account) ⇒ Object
163 164 165 166 167 168 |
# File 'app/models/email_template.rb', line 163 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
182 183 184 |
# File 'app/models/email_template.rb', line 182 def belongs_to_resource resource ? "#{resource.class.name}|#{resource_id}" : nil end |
#belongs_to_resource=(val) ⇒ Object
174 175 176 177 178 179 180 |
# File 'app/models/email_template.rb', line 174 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
259 260 261 262 |
# File 'app/models/email_template.rb', line 259 def body_v4=(val) self[:body_v4] = val @body_v4_template = nil end |
#campaign_emails ⇒ ActiveRecord::Relation<CampaignEmail>
71 |
# File 'app/models/email_template.rb', line 71 has_many :campaign_emails |
#communications ⇒ ActiveRecord::Relation<Communication>
72 |
# File 'app/models/email_template.rb', line 72 has_many :communications |
#deep_dup ⇒ Object
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'app/models/email_template.rb', line 95 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
228 229 230 |
# File 'app/models/email_template.rb', line 228 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
218 219 220 221 222 223 224 |
# File 'app/models/email_template.rb', line 218 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
234 235 236 237 238 239 240 |
# File 'app/models/email_template.rb', line 234 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)
245 246 247 248 249 250 251 |
# File 'app/models/email_template.rb', line 245 def effective_template if redactor_4_ready? && body_v4_email.present? 'v4' else template.presence || DEFAULT_TEMPLATE end end |
#embedded_assets ⇒ ActiveRecord::Relation<EmbeddedAsset>
73 |
# File 'app/models/email_template.rb', line 73 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
197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'app/models/email_template.rb', line 197 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, /\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
191 192 193 |
# File 'app/models/email_template.rb', line 191 def migrated_to_v4? body_v4.present? end |
#needs_v4_migration? ⇒ Boolean
Check if this template has legacy content that needs migration
254 255 256 |
# File 'app/models/email_template.rb', line 254 def needs_v4_migration? body.present? && body_v4.blank? end |
#ok_to_delete? ⇒ Boolean
155 156 157 |
# File 'app/models/email_template.rb', line 155 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
212 213 214 |
# File 'app/models/email_template.rb', line 212 def r4_only? new_record? || !has_legacy_r3_content? end |
#referenced? ⇒ Boolean
159 160 161 |
# File 'app/models/email_template.rb', line 159 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
269 270 271 |
# File 'app/models/email_template.rb', line 269 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
275 276 277 278 279 280 |
# File 'app/models/email_template.rb', line 275 def render_body_v4( = nil) return nil unless body_v4_email.present? 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)
284 285 286 287 288 289 |
# File 'app/models/email_template.rb', line 284 def render_editable_body( = nil) return nil unless body_v4.present? template = Liquid::ParseEnvironment.parse(body_v4.to_s) Liquid::Renderer.render(template, , to_s) end |
#render_from(options = nil) ⇒ Object
295 296 297 |
# File 'app/models/email_template.rb', line 295 def render_from( = nil) Liquid::Renderer.render(sender_email_template_instance, , to_s) end |
#render_subject(options = nil) ⇒ Object
291 292 293 |
# File 'app/models/email_template.rb', line 291 def render_subject( = nil) Liquid::Renderer.render(subject_template_instance, , to_s) end |
#resource ⇒ Resource
To represent ownership, thinking Employee, Company or nil
65 |
# File 'app/models/email_template.rb', line 65 belongs_to :resource, polymorphic: true, optional: true |
#to_s ⇒ Object
317 318 319 |
# File 'app/models/email_template.rb', line 317 def to_s "[EmailTemplate:#{id}] #{description}" end |