Aarthi
Aarthi

Reputation: 1521

Rendering html template outside of controller/Mailer rails

I have a situation to render a HTML template outside of controller(A class under service/lib directory), I have gone through https://api.rubyonrails.org/classes/ActionController/Renderer.html

@template = ApplicationController.render(
        template: 'template',
        layout: mailer_template,
    )

Is passing all the instance variable to the render method only option to get access to the instance variables in view(html template) or any other configurations available to get access to all the instance variables?.

FooController.render :action, locals: { ... }, assigns: { ... }

If we do really pass all the instance variables that are being used in the template/layout file, we need to check all the time when we add new variables. I have referred the flow of action controller and action view and got this How are Rails instance variables passed to views?, how do I have this flow for a non controller class?

Upvotes: 0

Views: 939

Answers (1)

jvillian
jvillian

Reputation: 20263

The short version of the answer is:

Your service should pass itself in as a 'presenter' local and your partial should delegate variables back to your service.

There is a longer answer, but first some throat clearing...

I started doing the "render anywhere" bit before I got into Rails 5, so my approach looks a little different than what you'll end up doing. But, perhaps it will help.

And then some background...

I have a module (something like ActsAs::Rendering) that includes an instance method called render_partial that looks something like:

module ActsAs::Rendering
  def render_partial(file_name)
    file_name = file_name.to_s
    render(partial: file_name, locals: {presenter: self})
  end
end

There's more to it than that, but I think that'll give you idea. The important bit (which we'll get to later) is that self is passed in as presenter.

Then, in my service I might do something like:

class BatchMailerService < ApplicationService

  def call
    render_partial :my_cool_partial
  end

  def tell_method_something_good
    "Tell me that you like it, yeah."
  end

end

I guess with Rails 5, you'll use that FooController.render :action, locals: { ... } bit. But, like I said, I haven't gotten around to doing this Rails 5 yet, so I'm not sure.

Naturally, I have an ApplicationService (under app/services) that looks something like:

class ApplicationService
  include ActsAs::Rendering

  attr_accessor *%w(
    args
  ).freeze

  class << self 

    def call(args={})
      new(args).call
    end        

  end  # Class Methods

  #==============================================================================================
  # Instance Methods
  #==============================================================================================

    def initialize(args={})
      @args = args
      assign_args
    end

  private

    def assign_args
      args.each do |k,v|
        class_eval do 
          attr_accessor k
        end
        send("#{k}=",v)
      end
    end        

end

Having said all of that...

:my_cool_partial might look something like:

"my_cool_partial.html.haml"

- @presenter = local_assigns[:presenter] if local_assigns[:presenter]

#tell-me-something-good-container
  = @presenter.tell_me_something_good

Now, if I do:

BatchMailerService.call    

I will get something back like:

<div id="tell-me-something-good-container">
  Tell me that you like it, yeah.
</div>

This way, the service object doesn't have to pass in a long list of locals. It just has to pass in itself and I just have to ensure that the service object responds to any calls made inside the partial.

Upvotes: 1

Related Questions