I'm trying to create an HTML string using a view. I would like to render this from a class that is not a controller. How can I use the rails rendering engine outside a controller? Similar to how ActionMailer does?
Thanks!
Rails 5 and 6 support this in a much more convenient manner that handles creating a request and whatnot behind the scenes:
rendered_string = ApplicationController.render(
template: 'users/show',
assigns: { user: #user }
)
This renders app/views/users/show.html.erb and sets the #user instance variable so you don't need to make any changes to your template. It automatically uses the layout specified in ApplicationController (application.html.erb by default). Full documentation can be found here.
The test shows a handful of additional options and approaches.
You can use ActionView::Base to achieve this.
view = ActionView::Base.new(ActionController::Base.view_paths, {})
view.render(file: 'template.html.erb')
The ActionView::Base initialize takes:
A context, representing the template search paths
An assigns hash, providing the variables for the template
If you would like to include helpers, you can use class_eval to include them:
view.class_eval do
include ApplicationHelper
# any other custom helpers can be included here
end
There is no need to over bloat your app with too many gems. As we know ERB is already included in your Rails app.
#jdf = JDF.new
#job = ERB.new(File.read(Rails.root + "app/views/entries/job.xml.erb"))
result = #job.result(binding)
Above there is snippet of code of an app I'm working on.
#jdf is a object to be evaluated in the erb view.
In my case I needed to render xml.
result is a string to be saved or sent anywhere you like.
In Rails 5:
view = ActionView::Base.new(ActionController::Base.view_paths)
view.render(file: 'template.html.erb')
In Rails 6.1:
lookup_context = ActionView::LookupContext.new(ActionController::Base.view_paths)
context = ActionView::Base.with_empty_template_cache.new(lookup_context, {}, nil)
renderer = ActionView::Renderer.new(lookup_context)
renderer.render(context, { file: 'app/views/template.html.erb' })
ActionView::Renderer.new() takes a lookup_context arg, and render() method takes a context, so we set those up first
ActionView::Base is the default ActiveView context, and must be initialized with with_empty_template_cache method, else render() will error
The {}, nil are required assigns and controller args, which used to default to {}, nil in Rails 5
Rails 6.1 requires a full filepath file: 'app/views/template.html', whereas Rails 5 only required the filename
"I'm trying to create an HTML string using a view." -- If you mean you're in the context of a view template, then just use a helper method or render a partial.
If you're in some other "Plain Old Ruby Object", then keep in mind you're free to use the ERB module directly:
erb = ERB.new("path/to/template")
result = erb.result(binding)
The trick is getting that 'binding' object that gives the context for the code in the template. ActionController and other Rails classes expose it for free, but I couldn't find a reference that explains where it comes from.
http://www.ruby-doc.org/stdlib-2.2.0/libdoc/erb/rdoc/ERB.html#method-i-result
Calling directly render method on ApplicationController may raise error
Helper Devise: could not find the `Warden::Proxy` instance on request environment
Instead we can use ApplicationController.renderer.render like
rendered_string = ApplicationController.renderer.render(
partial: 'users/show',
locals: { user: user }
)
Technically, ActionMailer is a subclass implementation of AbstractController::Base. If you want to implement this functionality on your own, you'll likely want to inherit from AbstractController::Base as well.
There is a good blog post here: https://www.amberbit.com/blog/2011/12/27/render-views-and-partials-outside-controllers-in-rails-3/ that explains the steps required.
For future reference, I ended up finding
this handy gem that makes this a breeze:
https://github.com/yappbox/render_anywhere
Best to render the view using a controller, as that's what they're for, and then convert it to a string, as that is your ultimate goal.
require 'open-uri'
html = open(url, &:read)
Related
In my rails app, I am using Kramdown to parse Markdown. I want to extend the functionality of the convert_a method in the HTML converter. Part of this involves accessing the database, but it is dependent on a parameter in the URL. Because I am not directly calling the method that I am overriding I cannot simply pass the method the params hash. Is there a way to access this hash, or even just get the current URL in a module in the lib directory?
to give a bit more context, the method call is in a helper method here:
# in app/helpers/myhelper.rb
def to_html(text)
Kramdown::Document.new(text, parse_block_html: true).to_custom_html
end
and here is the file in which I override the convert_a:
# in lib/custom_html.rb
class CustomHtml < Kramdown::Converter::Html
def convert_a(el, indent)
# use params[:foo] to make query
format_as_span_html(el.type, el.attr, inner(el, indent))
end
end
Edit:
To give a bit more context on where the overrided method is called. I am not extremely familiar with the Kramdown codebase, however it seems that when to_custom_html is called the following bit of code is run inside of Kramdown.rb:
output, warnings = Converter.const_get(name).convert(#root, #options)
which subsequently calls convert_#{el.type} on the internal kramdown elements.
You can pass additional options in Kramdown::Document#new, so just do something like Kramdown::Document.new(text, my_params: params). Then you can use the #options method of the converter to access your params.
Im trying to implement Shopify's liquid templates in my project & render them from the database.
What would be the prettiest & most "railsy" way of doing this, while keeping my controllers free from excessive clutter?
render text: template.render('name' => name).html_safe
seems a bit ugly to me, not to mention possible inheritance logic.
I was in the same situation and wanted to abstract the logic a bit. I ended up with a controller concern with a render_liquid method, that injects some default locals and registers a custom file system to find the templates, etc. It somehow looks like this:
def render_liquid(name, locals, registers = {})
registers[:file_system] = CustomFileSystem.new(self)
template = registers[:file_system].read_template_file(name)
Liquid::Template.parse(template).render!(locals, registers: registers)
end
Eventually my controllers just tell to render a template with specific locals:
render_liquid "page", {
page: PageDrop(#page),
chapter: ChapterDrop(#chapter),
}
User Story:
Action for Facebook that has open graph object.
For this I need to modify the tag defined in application.html
Problem:
The logic would need to be defined in helpers or the application_controller
From my understanding this is not clean.
Question:
I want to pass variables directly into the application.html view.
Preferably pass those variables from a custom controller into the application.html. This way I can still utilize the rails routing system to only pass those variables when I am on the facebook action.
The common mechanism for passing variables in to the view is to create instance variables in your controller as these are ported over automatically.
This is the standard approach if it is almost certain they will be used. For things that may not be used, create a helper method that will take care of providing them.
This is the difference between doing this:
def show
#facebook_graph = ...
end
And this in a helper:
def facebook_graph
...
end
I'm thinking about this since a while: I'm build a single-page webapp with ExtJS library.
The whole GUI will be handled with JSON through AJAX requests, so I effectively never use the view functionality (except for the main page, where the view is six lines just to write basic tags).
However, I struck in a problem which I think will happen often during my application: I have a complex json which needs a lot of view helpers to be built, expecially for referencing other resources like images. These json objects are statically written by me, for example I need to configure ext to render some desktop-like icons which needs title and a reference to an image. I think this should be written as a view where params are an array of hashes containing title/image.
That being said, I were thinking about an approach where my json objects will be built by the view, and not by the controller as I'm currently doing.
My questions is:
Is this approach OK? I feel like violating MVC pattern when I try to use image_path helper inside my controller.
It's important to understand that I'm not trying to fetch something in the view, I'm just passing some model objects as params to views and write them in a json fashion. I can't (always) use .to_json method because sometimes I need to organize those json objects in a totally different way.
Edit 1:
I'm adding a small question that could become really useful with this approach: is possible to parse (in the controller) a YML.ERB file in the controller but allowing it (the yml) to use all those nice helpers that I have in html.erb files? I would like to use them because is nicer to build some static json objects in yml rather than plain json. If that's the case, how to do it? Remember that I'm in a controller. Thanks a lot.
You can easily write json directly in your view templates, just like you would be writing erb, or xml. E.g. if you have a controller like this
class FoosController < ApplicationController
def index
end
end
And you accept format in your routes, then you can simply create a view
<% # app/views/foos/index.json.erb %>
{ some_json_stuff: "<%= link_to 'home', root_url %>" }
This view will be rendered when you access /foos.json. This enables you to write any custom json with helpers, partials, etc.
You might want to check out Draper, which is a gem that provides view models for Rails. It is ideal for things like JSON views, which arguably have no place in the model.
It also allows you access to helpers and the request context.
class ArticleDecorator < ApplicationDecorator
decorates :article
def as_json(optsions={})
{
:id => model.id,
:foo => helpers.some_helper_method,
:secret => helpers.current_user.admin? ? "secrets!" : "no secrets"
# ...
}
end
end
In my Rails application I have an action which creates a XML document using an XML Builder template (rxml) template and render_to_string. The XML document is forwarded to a backend server.
After creating the XML document I want to send a normal HTML response to the browser, but somehow Rails is remembering the first call to render_to_string.
For example:
Rails cannot find the default view show.html.erb because it looks for a show.rxml.
Simply putting a render 'mycontroller/show.html.erb' at the bottom of my action handler makes Rails find the template, but the browser doesn't work because the response header's content type is text/xml.
Is there any way to use render_to_string without "tainting" the actual browser response?
EDIT: It seems that in Rails 2 erase_render_results would do the trick, but in Rails 3 it is no longer available.
The pragmatic answer is that using a view file and two calls to render is Not The Rails Way: views are generally something that is sent to the client, and ActionPack is engineered to work that way.
That said, there's an easy way to achieve what you're trying to do. Rather than using ActionView, you could use Builder::XmlMarkup directly to generate your XML as a string:
def action_in_controller
buffer = ""
xml = Builder::XmlMarkup.new(buffer)
# build your XML - essentially copy your view.xml.builder file here
xml.element("value")
xml.element("value")
# send the contents of buffer to your 3rd server
# allow your controller to render your view normally
end
Have a look at the Builder documentation to see how it works.
The other feature of Builder that you can take advantage of is the fact that XML content is appended to the buffer using <<, so any IO stream can be used. Depending how you're sending content to the other server, you could wrap it all up quite nicely.
Of course, this could end up very messy and long, which is why you'd want to encapsulate this bit of functionality in another class, or as a method in your model.
Seems as if this may be a bug in rails 3 (at least compared to the behavior of 2.3.x render_to_string). In the source for 2.3.8 they clearly take extra steps to reset content_type and set the response body to nil (among other things).
def render_to_string
...
ensure
response.content_type = nil
erase_render_results
reset_variables_added_to_assigns
end
but in the 3.0.3 source for AbstractController::Rendering
def render_to_string(*args, &block)
options = _normalize_args(*args, &block)
_normalize_options(options)
render_to_body(options)
end
You can see there is no explicit resetting of variables, render_to_body just returns view_context.render. It is possible that content-type, response_body, etc are handled elsewhere and this is a red herring, but my first instinct would be to set
response.headers['Content-Type'] = 'text/html'
after your render_to_string before actually rendering.
In migrating the actionwebservice gem I encountered the same error. In their code they circumvent the double render exception by calling the function erase_render_results.
This function is no longer available in rails3. Luckily the fix is quite easy (but it took me a while to find).
Inside actionwebservice the following function was called inside a controller to allow a second render:
def reset_invocation_response
erase_render_results
response.instance_variable_set :#header, Rack::Utils::HeaderHash.new(::ActionController::Response::DEFAULT_HEADERS.merge("cookie" => []))
end
To make this work in rails3, you just have to write:
def reset_invocation_response
self.instance_variable_set(:#_response_body, nil)
response.instance_variable_set :#header, Rack::Utils::HeaderHash.new("cookie" => [], 'Content-Type' => 'text/html')
end
Hope this helps.