Intercepting template content with ActionView - ruby-on-rails

I have a Haml template with a bunch of “custom tags” scattered around, e.g. {{REPLACE_ME}}. Before rendering this template, I am going through the template and replacing all occurrences using Ruby’s String#gsub.
That has not been a problem—I have simply created a custom template handler, which essentially acts as a middleman by first modifying the template source and then passing it on to the Haml template handler.
For each occurrence of one of these “custom tags” in the view, it adds to a numerator that is stored in the background. The problem that I am facing, however, is that the numerator is being bumped up for content that will never be displayed to the user, e.g. <% if false %>{{SECTION_NO}}<% end %>.
Such content as above should be filtered out before I replace the content. I seem, however, to have hit a wall, as the Haml (and ERB) handlers return a content buffer and not the “end/finished” content.
Is there a straight-forward solution to this issue?
Here is an example of a view:
- if true
%h1 {{SECTION_NO}}
- if false
%h1 {{SECTION_NO}}
- if true
%h1 {{SECTION_NO}}
So basically, I need a way to filter out the middle (false-statement) of the view source before replacing and numerating occurrences of “{{SECTION_NO}}”.
For reference, here is the gist of the template handler I created for this purpose:
class ActionView::Template::Handlers::Caml
def initialize
#section = 0
#paragraph = 0
#references = {}
end
def call(template)
rendered_haml = ActionView::Template.registered_template_handler(:haml).call(template)
updated_source = render_sections(rendered_haml)
updated_source = render_paragraphs(updated_source)
updated_source = render_references(updated_source)
return ActionView::Template.new(
updated_source,
template.identifier,
template.handler,
{
locals: template.locals,
virtual_path: template.virtual_path,
updated_at: template.updated_at
}
)
end
private
def render_sections(source)
source.gsub(/{{section:([a-zA-Z0-9-_]+)}}/).each do |match|
fail 'Key already exists.' if #references.key?($1)
#section += 1
#references[$1] = "#{#section}"
"#{#section}"
end
end
def render_paragraphs(source)
# ...
end
def render_references(source)
source.gsub(/{{([a-zA-Z0-9-_]+)}}/).each do |match|
#references.fetch($1)
end
end
end
ActionView::Template.register_template_handler(
:caml,
ActionView::Template::Handlers::Caml.new
)

Related

PDF from ERB template with PDFKit and Rails

In my Rails application I'd like to have a special route to download a custom PDF.
This PDF should be generated via PDFKit from an ERB template in my application. Instead of describing what I'd like to achieve, I better paste some non-executable but commented code:
class MyController < ApplicationController
def download_my_list_as_pdf
# The template uses the instance variables below
#user_id = params[:user_id]
#items = ['first_item', 'second_item']
# This line describes what I'd like to do but behaves not like I want ;)
# Render the ERB template and save the rendered html in a variable
# I'd also use another layout
rendered_html = render :download_my_list_as_pdf
kit = PDFKit.new(rendered_html, page_size: 'A4')
kit.to_pdf
pdf_file_path = "#{Rails.root}/public/my_list.pdf"
kit.to_file(pdf_file_path)
send_file pdf_file_path, type: 'application/pdf'
# This is the message I'd like to show at the end
# But using 'render' more than once is not allowed
render plain: 'Download complete'
end
end
I wasn't able to find an answer to this problem yet, any help would be greatly appreciated!
render_to_string(*args, &block)
Raw rendering of a template to a string.
It is similar to render, except that it does not set the response_body
and it should be guaranteed to always return a string.
render does not return a string it sets the response_body of the response.
class MyController < ApplicationController
def download_my_list_as_pdf
# The template uses the instance variables below
#user_id = params[:user_id]
#items = ['first_item', 'second_item']
# This line describes what I'd like to do but behaves not like I want ;)
# Render the ERB template and save the rendered html in a variable
# I'd also use another layout
rendered_html = render_string(:download_my_list_as_pdf)
kit = PDFKit.new(rendered_html, page_size: 'A4')
kit.to_pdf
pdf_file_path = "#{Rails.root}/public/my_list.pdf"
kit.to_file(pdf_file_path)
send_file pdf_file_path, type: 'application/pdf'
end
end
However if you are sending a file you cannot send text or html as well. This is not a limitation of Rails but rather how HTTP works. One request - one response.
Usually javascript is used to create notifications surrounding file downloads. But consider first if its really needed as its pretty annoying to users as the browser usually tells you that you downloaded a file anyways.

How to add views for custom member action in active admin?

I have to add view for custom action which is member action, and want to display association records on it. Is there way to add custom view instead of just adding html.erb in admin's view folder?
I dont want to add or create html files but by using the active admins helpers.
The member action in nothing else like a controller action, thats mean you can do the same things in it.
You can use thinks like:
render text: "Hello world!"
Or if you want a complex markup:
message1 = "Hello"
#message2 = "world!"
view = Arbre::Context.new(message: message, self) do
h1 do
span message
span #message
end
end
render body: view.to_html # or .to_s
You can use the following code for the render html for rails 4.1:
render html: '<html><body>Some body text</body></html>'.html_safe ## Add html_safe
But, if you use rails 4.2, so you can use the following:
render text: '<html><body>Some body text</body></html>'
I think the following answer is very useful for your question.
Ended up with adding the following in html.erb file in admin/user/messages.html.erb
<% view = Arbre::Context.new({messages: #messages, user: #user}, self) do
panel "Sent Messages" do
paginated_collection(messages, download_links: false) do
table_for collection do
column :id
column :content
end
end
end
end
%>
<%= view.to_s %>
Depending on your namespace (ActiveAdmin is on /admin in my case) you can create the folder app/views/admin in the same way you would in the rest of your application.
For example, if you have a resource User and an action apply_discount
ActiveAdmin.register User do
member_action :apply_discount, method: [:get, :put] do
if request.get?
render :apply_discount
else
# TODO ...
end
end
end
you could put your ARBRE view file into app/views/admin/users/apply_discount.html.arb -> notice the extension is ARB, not ERB - though ERB should work too according to the docs

Rails - print a "recursive" html variable in Slim

I would like to render in my view a "html"-content variable, let me explain:
Somewhere in a helper there is a pseudocode like this
# view helper for an ERB view
def render_something_recursively(i = 1)
html = "<li>"
html << "hi number #{i}"
if i < 1000
html << render_something_recursively(i++)
end
html << "</li>"
end
Sorry for the bad example but I hope that it give you an idea, in facts I would like to iterate on a hierarchy structure (a tree) with flexible depth. For this reason I need a recursive method and I would like to keep it out from the view.
My question is: how can I accomplish to the same result but in Slim (or eventually in HAML)? How can I give the information of "indentation" at the htmlvariable?
Is it possible or I must use an ERB view?
My final goal should be "easily" something like that:
# recursive_list.html.slim
ul class="a_recursive_list"
=render_something_recursively
Use content_tag and it will render whatever templating engine you are using:
def render_something_recursively(i = 1)
content_tag(:li, "hi number #{i}") do
if i < 1000
render_something_recursively(i++)
end
end
end
Additionally, I don't think you should nest <li> elements directly inside each other, you should either render text or a <ul> element nested inside the <li>.

creating dynamic helper methods in rails

I am trying to create a bunch of dynamic helper methods like these:
show_admin_sidebar
show_posts_sidebar
show_users_sidebar
So far I have this in my helper.rb file:
#spits out a partial
def show_sidebar(name, show_sidebar = true)
#content_for_sidebar = render :partial => "partials/#{name}"
#show_sidebar = show_sidebar
end
def show_sidebar?
#show_sidebar
end
In my application layout file I have this: (NB - I'm using HAML):
- if show_sidebar?
= yield(:sidebar)
This allows me to say the following in my views:
- show_sidebar(:foo)
- show_sidebar(:bar)
And this renders the desired partial.
The problem with this is that I can only add one sidebar per page. So, I figure I need to have dynamic methods like: show_admin_sidebar, show_foo_sidebar.
So I have tried to do this:
def show_#{name}_sidebar(show_sidebar = true)
#name = name
#content_for_#{#name}_sidebar = render :partial => "partials/#{#name}"
#show_sidebar = show_sidebar
end
and then in my layout:
- if show_sidebar?
= yield("{#name}_sidebar")
But rails does not like this at all.
I have tried almost everything I can think of in my helper file and nothing works.
The reason I am using helper methods for this is because I want my content div to be 100% page width unless there is a sidebar present in which case the main content goes into a smaller div and the sidebar content goes into it's own..
If I can't get this working, then I can easily fix the problem by just adding the partials manually but I'd like to get my head round this....
Anyone got any experience with this kind of thing?
The entire approach to this was bizarrely overcomplicated, didn't follow Rails conventions at all, nor make the slightest bit of sense, and shame on prior respondents for enabling this approach instead of helping him to simplify. My apologies for being 13 months late with the answer.
Your controller should be deciding if a sidebar is to be shown or not, and setting an instance variable #side_bar_name to either nil or a sidebar name string. Then somewhere in shared view code, probably views/layouts/application.html.erb, you would have something as simple as this:
<% if #side_bar_name %>
<%= render :partial => "partials/#{#side_bar_name}" %>
<% end %>
Or better yet:
<%= render(:partial => "partials/#{#side_bar_name}") if #side_bar_name %>
If you want to use a helper (which is not a bad idea for keeping your code DRY and readable) it would basically be the same code, just moved into the helper.
<%= side_bar_helper %>
def side_bar_helper
render(:partial => "partials/#{#side_bar_name}") if #side_bar_name
end
What the controller does is up to you. It would probably do something like this:
if session[:show_side_bar]
# maybe use cookies instead of session, or store user preference in a database
#side_bar_name = session[:side_bar_name]
end
Here is a solution for you, however I wouldn't suggest too much metaprogramming:
#Add the following snippet to the proper helper module:
['admin','user','whatever'].each do |name|
class_eval{
"def show_#{name}_sidebar(show_sidebar = true)
#name = #{name}
#content_for_#{#name}_sidebar = render :partial => 'partials/#{#name}'
#show_sidebar = show_sidebar
end"
}
end
def show_#{name}_sidebar(show_sidebar = true)
That doesn't look like valid Ruby to me. Are you parsing and evaling this yourself or just throwing that right in the file and expecting it to work?

Easy breadcrumbs for RESTful rails application

Is there any helper method (Other than default rails breadcrumb) that generates bread crumb navigation dynamically for a particular page without having to pass trivial parameters in RESTful application? That is, something that figures out automatically where the user is based on the REST url she is visiting?
For above mentioned implementation, we need to pass parameters like
REST
<% add_crumb(‘Profile’, user_profile_path) %>
Current page
<% add_crumb(“My Incoming Messages”, request.path) %>
There must be a way to generalize the code so that no parameter passing is required and should work for all RESTful apps with minimal configuration.
Developed a simple hack. The method however assumes that there exists a method 'name' for every model object corresponding to each resource in the RESTful url. Whatever that the method 'name' returns is shown as breadcrumb name. If it is not found, it is shown as it is without making it link to anything. Separator used is '->' You may change it to suit your requirement.
def get_bread_crumb(url)
begin
breadcrumb = ''
sofar = '/'
elements = url.split('/')
for i in 1...elements.size
sofar += elements[i] + '/'
if i%2 == 0
begin
breadcrumb += "<a href='#{sofar}'>" + eval("#{elements[i - 1].singularize.camelize}.find(#{elements[i]}).name").to_s + '</a>'
rescue
breadcrumb += elements[i]
end
else
breadcrumb += "<a href='#{sofar}'>#{elements[i].pluralize}</a>"
end
breadcrumb += ' -> ' if i != elements.size - 1
end
breadcrumb
rescue
'Not available'
end
end
The method generally accepts request.url (Which given url of the current page) as the parameter. The method purposefully accepts the url for customization purposes. To generate the breadcrumb, simply add following code in your view -
<%= get_bread_crumb(request.url) %>
For the url /ideabox/2/idea/1, the bread crumb looks like
alt text http://www.imagechicken.com/uploads/1234855404069992300.png
Excuse me if code quality is not that great. I'm sure this code can be re-factored but I'm also sure you would be able to do that before using it.
Thanks.
The solution provided by chirantan is great. If you need breabcrumbs for namespaced controller and need to change the breadcrumbs depending on the namespace as well then try this. This is not perfect but refactor it as you need. It works for my project.
Define a new helper: navigation_helper.rb
module NavigationHelper
def navigation_add(title, url, namespace)
if defined? ##namespace and !##namespace.nil? and ##namespace == namespace
##navigation ||= []
else
##navigation = []
end
##navigation << {title: title, url: url} unless title == "Home"
new_nav = []
##navigation.each do |hash|
new_nav.push hash
if hash[:title].to_s == title.to_s
break
end
end
##navigation = new_nav
##navigation.uniq!
##namespace = namespace
end
def render_navigation
if (request.path_parameters[:controller].sub('/', '::_').camelize + 'Controller').classify.constantize.action_methods.to_a.include? 'index'
navigation_add controller_name.camelize.to_s, request.path_parameters.merge({action: 'index'}).except(:id), params[:controller].include?('/') ? params[:controller].split("/").first : nil
end
if defined? ##navigation
render partial: 'navigation/navigation', locals: { navs: ##navigation, namespace: ##namespace }
else
render text: ''
end
end
end
Then define a view for this helper _navigation.haml
- unless navs.blank?
%ol.breadcrumb
- navs.each_with_index do |nav, index|
- if index == 0
%li=link_to fa_icon('arrow-left', text: 'Go Back'), :back
- unless namespace.nil?
%li
%h4.inline= request.fullpath.split('/')[1].gsub('-', '_').camelize
= fa_icon('angle-double-right')
%li= link_to_unless (nav[:title] == controller_name.camelize and action_name == 'index'), fa_icon(nav[:title].downcase.singularize, text: nav[:title]), nav[:url]

Resources