Rendering Rails helpers in haml_example blocks
lovehasnologic opened this issue · comments
Out of the box, generating documentation that includes a rails helper in a haml_example code block results in the following error...
(haml):1:in `block in render': undefined method `link_to' for #<Object:0x007fcef187af10> (NoMethodError)
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/gems/haml-4.0.6/lib/haml/engine.rb:129:in `eval'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/gems/haml-4.0.6/lib/haml/engine.rb:129:in `render'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/gems/hologram-1.4.0/lib/hologram/code_example_renderer/renderers/haml_renderer.rb:14:in `block (2 levels) in <top (required)>'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/gems/hologram-1.4.0/lib/hologram/code_example_renderer/factory.rb:13:in `call'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/gems/hologram-1.4.0/lib/hologram/code_example_renderer/factory.rb:13:in `block (2 levels) in define'
from (erb):3:in `get_binding'
from /Users/[username]/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/erb.rb:863:in `eval'
from /Users/[username]/.rvm/rubies/ruby-2.2.0/lib/ruby/2.2.0/erb.rb:863:in `result'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/gems/hologram-1.4.0/lib/hologram/block_code_renderer.rb:16:in `render'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/gems/hologram-1.4.0/lib/hologram/markdown_renderer.rb:38:in `block_code'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/gems/hologram-1.4.0/lib/hologram/doc_builder.rb:199:in `render'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/gems/hologram-1.4.0/lib/hologram/doc_builder.rb:199:in `block in write_docs'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/gems/hologram-1.4.0/lib/hologram/doc_builder.rb:185:in `each'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/gems/hologram-1.4.0/lib/hologram/doc_builder.rb:185:in `write_docs'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/gems/hologram-1.4.0/lib/hologram/doc_builder.rb:147:in `build_docs'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/gems/hologram-1.4.0/lib/hologram/doc_builder.rb:87:in `build'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/gems/hologram-1.4.0/lib/hologram/cli.rb:38:in `build'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/gems/hologram-1.4.0/lib/hologram/cli.rb:30:in `run'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/gems/hologram-1.4.0/bin/hologram:6:in `<top (required)>'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/bin/hologram:23:in `load'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/bin/hologram:23:in `<main>'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/bin/ruby_executable_hooks:15:in `eval'
from /Users/[username]/.rvm/gems/ruby-2.2.0@hologram-txi/bin/ruby_executable_hooks:15:in `<main>'
Does this need to be done with a custom renderer or is it not possible. Specifically, I'm looking at three types of helpers.
1. Common Rails Helpers
= link_to '#', '#'
2. Gem Helpers
= simple_form_for "example" do |f|
= f.input :username
= f.input :password
= f.button :submit
3. Custom Helpers from application.rb
= custom_icon_helper 'iconName'
Thank you in advance. I tried to work through building a custom markdown renderer, but just ended up getting lost in my own head, as Ruby/Rails is not my expertise.
I've handled this by passing the files through Rails (#172) and parsing the HAML like so:
HELPERS_TO_FILTER = [:notification]
HELPERS_TO_FILTER_REGEX = /
(?<==)
\s*
(?=
(?:#{HELPERS_TO_FILTER.join('|')})
\b
)
/x
HELPER_PREFIX = 'view_context.'
protected
def apply_styleguide_filters(html)
apply_example_output_filter(html)
html.to_html
end
def looks_like_haml?(string)
!!(string =~ /\A\s*[=%.#]/m)
end
private
def apply_example_output_filter(html)
html.css('.exampleOutput').each do |example_output|
example_output_content = example_output.content.strip
next unless looks_like_haml?(example_output_content)
example_output_with_prefixed_helpers =
prefix_helpers(example_output_content)
rendered_helper =
render_helper_from_haml(example_output_with_prefixed_helpers)
example_output.children.remove
example_output.add_child rendered_helper
end
end
# Helpers must be called within the controller's view context.
def prefix_helpers(string)
string.gsub(HELPERS_TO_FILTER_REGEX, HELPER_PREFIX)
end
def render_helper_from_haml(string)
begin
rendered_helper = Haml::Engine.new(string).render(binding)
rescue => e
raise "#{h(e)}<br><br>Perhaps this is a custom helper that needs filtering?".html_safe
end
Nokogiri::HTML::fragment(rendered_helper)
end
Appreciate the feedback, but I'm still getting an error when I follow these instructions and try to load a URL (I read this approach as running through the regular render process and not needing to generate the static files.
No such file or directory @ rb_sysopen - /Users/lovehasnologic/Sites/hologram-txi/app/views/styleguide/javascripts_-_page_data.html
Extracted source (around line #22):
20
21 def set_page_html
22 file = File.open(@page_absolute_file_path)
23 @page_html = ::Nokogiri::HTML(file)
24 file.close
25 end
When I run hologram
from the command line, I get:
(haml):1:in `block in render': undefined method `link_to' for #<Object:0x007fcecb023770> (NoMethodError)
Here are my files in full:
/hologram_config.yml
# Hologram will run from same directory where this config file resides
# All paths should be relative to there
# The directory containing the source files to parse recursively
source:
- ./app/assets/javascripts
- ./app/assets/stylesheets
- ./vendor/assets/javascripts
# The directory that hologram will build to
destination: ./app/views/styleguide
# The assets needed to build the docs (includes header.html,
# footer.html, etc)
# You may put doc related assets here too: images, css, etc.
documentation_assets: ./app/views/styleguide
# The folder that contains templates for rendering code examples.
# If you want to change the way code examples appear in the styleguide,
# modify the files in this folder
code_example_templates: ./app/views/styleguide/code_example_templates
# The folder that contains custom code example renderers.
# If you want to create additional renderers that are not provided
# by Hologram (i.e. coffeescript renderer, jade renderer, etc)
# place them in this folder
code_example_renderers: ./app/views/styleguide/code_example_renderers
# To additionally output navigation for top level sections, set the value to
# 'section'. To output navigation for sub-sections,
# set the value to `all`
nav_level: all
# Hologram displays warnings when there are issues with your docs
# (e.g. if a component's parent is not found, if the _header.html and/or
# _footer.html files aren't found)
# If you want Hologram to exit on these warnings, set the value to 'true'
# (Default value is 'false')
exit_on_warnings: false
/config/routes.rb
Rails.application.routes.draw do
root to: 'styleguide#page', defaults: { page: :index, format: :html }
get 'styleguide/*page', to: 'styleguide#page', format: :html
end
/app/controllers/styleguide_controller.rb
class StyleguideController < ApplicationController
before_filter :set_page_uri
before_filter :set_page_absolute_file_path
before_filter :set_page_html
before_filter :apply_styleguide_filters
def page
render text: @page_html and return
end
private
def apply_styleguide_filters
@page_html = super(@page_html)
end
def set_page_absolute_file_path
@page_absolute_file_path = "#{Rails.root}/app/views/#{@page_uri}"
end
def set_page_html
file = File.open(@page_absolute_file_path)
@page_html = ::Nokogiri::HTML(file)
file.close
end
def set_page_uri
@page_uri = '%s/%s.%s' %
params.values_at(:controller, :page, :format)
end
end
/app/controllers/application.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :set_default_meta_data
helper_method :gon
HELPERS_TO_FILTER = [:notification]
HELPERS_TO_FILTER_REGEX = /
(?<==)
\s*
(?=
(?:#{HELPERS_TO_FILTER.join('|')})
\b
)
/x
HELPER_PREFIX = 'view_context.'
protected
def apply_styleguide_filters(html)
apply_example_output_filter(html)
html.to_html
end
def looks_like_haml?(string)
!!(string =~ /\A\s*[=%.#]/m)
end
private
def apply_example_output_filter(html)
html.css('.exampleOutput').each do |example_output|
example_output_content = example_output.content.strip
next unless looks_like_haml?(example_output_content)
example_output_with_prefixed_helpers =
prefix_helpers(example_output_content)
rendered_helper =
render_helper_from_haml(example_output_with_prefixed_helpers)
example_output.children.remove
example_output.add_child rendered_helper
end
end
# Helpers must be called within the controller's view context.
def prefix_helpers(string)
string.gsub(HELPERS_TO_FILTER_REGEX, HELPER_PREFIX)
end
def render_helper_from_haml(string)
begin
rendered_helper = Haml::Engine.new(string).render(binding)
rescue => e
raise "#{h(e)}<br><br>Perhaps this is a custom helper that needs filtering?".html_safe
end
Nokogiri::HTML::fragment(rendered_helper)
end
def set_default_meta_data
@meta_data = {
title: text("meta_data.title", scope: "controllers.application"),
description: text("meta_data.description", scope: "controllers.application"),
google_site_verification: Rails.application.secrets.google_site_verification_id,
og_title: text("meta_data.og.title", scope: "controllers.application"),
og_description: text("meta_data.og.description", scope: "controllers.application"),
og_type: text("meta_data.og.type", scope: "controllers.application"),
og_image: "cards/twit.png",
og_sitename: text("meta_data.og.sitename", scope: "controllers.application"),
fb_app_id: text("meta_data.app_id.fb", scope: "controllers.application"),
twit_title: text("meta_data.twit.title", scope: "controllers.application"),
twit_account: text("meta_data.twit.account", scope: "controllers.application"),
twit_description: text("meta_data.twit.description", scope: "controllers.application"),
twit_card: text("meta_data.twit.card", scope: "controllers.application"),
twit_image: "cards/twit.png",
apple_app_id: text("meta_data.app_id.apple", scope: "controllers.application") }
end
end
Looks like I have everything in the right place, but I could have messed up something obvious.
Thanks in advance. Your help is (and has been) appreciated.
Ah, yes. I forgot one key element that is unfortunate: put your HAML example inside of html_example
not haml_example
. It's a disconnect, but it prevents hologram from parsing and therefore bombing; instead, Rails handles the file when it's served up. Perhaps there's a better way, but this is what I've found so far.
Also, you'll need to adjust HELPERS_TO_FILTER
to determine which haml helpers are parsed.
Still not working. I am now getting an error that says it can't copy an unknown file type.
I'm going to try some other approaches that some of the developers I work with suggested and if I hit on anything, I'll reply back in this thread.
Thanks again for your help.
I wanted to follow up since one of our developers had some time and was able to look at this. We ended up creating a custom haml renderer.
./styleguide_assets/code_example_renderers/haml.rb
load "config/environment.rb"
# Public: A context for rendering HAML that knows about helpers from Rails,
# gems and the current application.
#
# NOTE: This is totally hacked together.
class RailsRenderingContext
# Public: Creates a new context into which we can render a chunk of HAML.
#
# Returns a properly-configured instance of ActionView::Base.
def self.create
# Create a new instance of ActionView::Base that has all of the helpers
# that our ApplicationController does. This allows us to use normal Rails
# helpers like `link_to`, most gem-provided helpers, and also custom
# application helpers like `svg_icon`.
view_context = ApplicationController.helpers
# Add named route support to our view context, so we can reference things
# like `root_path`.
class << view_context; include Rails.application.routes.url_helpers; end
# Create a new controller instance and give it a fake request; this vaguely
# mirrors what happens when Rails receives a request and routes it. This
# step allows us to use `simple_form_for`.
controller = ApplicationController.new
controller.request = ActionDispatch::TestRequest.new
view_context.request = controller.request
# Set up our view paths so that both `render` and gems that provide helpers
# that use `render` (e.g. kaminari) can work.
controller.append_view_path "app/views"
view_context.view_paths = controller.view_paths
view_context.controller = controller
view_context
end
end
# We overwrite the default "haml" handler from hologram to use our Rails-aware
# version.
Hologram::CodeExampleRenderer::Factory.define "haml" do
example_template "markup_example_template"
table_template "markup_table_template"
lexer { Rouge::Lexer.find("haml") }
rendered_example do |code|
require "haml"
haml_engine = Haml::Engine.new(code.strip)
context = RailsRenderingContext.create
haml_engine.render(context, {})
end
end
As it says, this is hacked together and can probably be a bit cleaner (but I honestly have no idea). However, it works perfectly, and renders all rails helpers, be they default helpers, custom helpers or gem helpers. Quite a nice addition.