Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rendering Rails helpers in haml_example blocks #233

Open
lovehasnologic opened this issue May 19, 2015 · 5 comments
Open

Rendering Rails helpers in haml_example blocks #233

lovehasnologic opened this issue May 19, 2015 · 5 comments

Comments

@lovehasnologic
Copy link

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.

@lovehasnologic lovehasnologic changed the title Rendering Rails helpers in ham_example blocks Rendering Rails helpers in haml_example blocks May 19, 2015
@chris-canipe
Copy link

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

@lovehasnologic
Copy link
Author

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.

@chris-canipe
Copy link

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.

@lovehasnologic
Copy link
Author

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.

@lovehasnologic
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants