How do I render a mailer template within the .rb file (no template file)

2.1k views Asked by At

Part of this is coming from how convenient react has made it to have a small snipped of template code in with the component. Here is what I have so far:

# app/mailers/membership_mailer.rb
# frozen_string_literal: true
class MembershipMailer < ApplicationMailer
  def one_week_expiration(member, renewal, org)
    template = slim(%(
      = content_for :header
        = org.name
      Hello, #{member.name},
      br
      Your membership for #{org.name} is set to expire at
      #{renewal.expires_at.to_s(:short)}.
      br
      Please visit <a href=#{org.url}>#{org.url}</a> to purchase
      a membership renewal.
      br
      br
    ))

    subject = "Your membership for #{org.name} is going to expire soon"
    mail(to: member.email, subject: subject) do |format|
      # there isn't a slim renderer
      format.html { render text: template }
    end
  end
end

I'm using this layout for all my emails, and it's set as the default layout application_mailer.

# app/views/layouts/email.html.slim
= stylesheet_link_tag "email"

header
  .shell
    h1 = yield :header

.shell
  br
  = yield :body
  = yield

= render partial: "/shared/email_footer"

I want all the rendering methods in here, but what I'm struggling with is where to find / figure out how to build out the template for the mailer, the layout, and have all the variables I want accessible passed to the templates.

# app/mailers/application_mailer.rb
# frozen_string_literal: true
class ApplicationMailer < ActionMailer::Base
  default from: APPLICATION_CONFIG['support_email']
  layout 'email'

  # taken from slim/test/block rendering / helper
  def slim(source, options = {}, &block)
    scope = options.delete(:scope)
    locals = options.delete(:locals)
    Slim::Template.new('', {}) { source }.render(scope, locals, &block)
  end
end

Eventually, I'll have erb, arbre, etc.

So, in summary, I when I make the call to mail(...){...}, I'd like to be able to have my template defined in ruby, rather than in a template file, because I don't like have the mailer and template so far apart (in the file system.. why is outside the scope of this question, I just want to solve the problem of rendering in ruby for now).

1

There are 1 answers

0
NullVoxPopuli On BEST ANSWER

Looks like the easiest way is to use render_anywhere which just makes a very stripped down controller, which then gives us access to the render method.

So, my changes to my classes are as follows:

In my mailer

  def one_week_expiration(member, renewal, org)
    template_environment = {
      org: org,
      member: member,
      renewal: renewal
    }

    template = slim(env: template_environment) do
      %q(
        = content_for :header
          = org.name

        h3 Hello, #{member.name},

        | Your membership for #{org.name} is set to expire at #{renewal.expires_at.to_s(:short)}.
        br
        | Please visit <a href=#{org.url}>#{org.url}</a> to purchase a membership renewal.
        br
        br
      )
    end

    subject = "Your membership for #{org.name} is going to expire soon"

    mail(to: member.email, subject: subject) do |format|
      format.html { render text: template }
    end
  end

And then the important bit (in application_mailer)

require 'render_anywhere'

class ApplicationMailer < ActionMailer::Base
  layout 'email'

  default from: APPLICATION_CONFIG['support_email']



 def slim(env: {})
    raise 'No Block Given' unless block_given?

    RenderAnywhere::RenderingController.new.render_to_string(
      inline: yield, layout: 'email', type: :slim, locals: env
    )
  end
end

And that's it.