Reputation: 1521
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
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