6

I am looking for a gem or elegant way to setup system where I would be able to see where certain HTML content came from. I am working on a big project, and a lot of my times goes determining where a certain piece of HTML content came from. I know you are able to see which layouts, partials are used to render a page in logs but I am looking for something more practical.

An example of this would be.

<!-- ... app/views/layouts/main.html.slim -->
<body>
  <!-- ... app/views/people/index.html.slim -->
  <div class="foo">
    <table clas="items">
      <!-- ... app/views/people/shared/_person.html.slim -->
      <td>
        <span>John Doe</span>
      </td>
    </table>
  </div>
</body>

Where before rendering any partial / page / layout rails render engine would add a comment describing origin.

Haris Krajina
  • 14,824
  • 12
  • 64
  • 81
  • Let me see if I understand this. You'd like to add a comment into the html source of your rendered page with the route to your rendered html, everytime there's a call to render? And this would be only in your development environment? – mlabarca Dec 21 '16 at 13:01
  • if I get the requirements right you want before each render to add a comment with the file path – Thor odinson Dec 21 '16 at 19:00
  • It's not config-based, but you could loop through app/views/* and prepend the filename comment with a [shell script](http://stackoverflow.com/questions/10587615/unix-command-to-prepend-text-to-a-file). – anyarms Dec 22 '16 at 00:52
  • To clarify, yes I would like each section of HTML to be annotated as described. – Haris Krajina Dec 22 '16 at 20:25
  • let me guess this is for a legacy project with no specs/tests. Most of this can be figured out by knowing Rails' conventions on partial rendering. However assuming you're not doing some kind of weird manual process of checking page content. A better approach IMO would be to write feature specs for the the html you're debugging. This has the side benefit of bringing your views under testing and should eliminate regression errors and manually checking html. – engineerDave Dec 22 '16 at 21:32
  • How about putting the path of the file as an HTML comment in each partial? That will include the comment whenever the partial is included in another file. Is there anyway we can generate this comment dynamically? :| – 31piy Dec 23 '16 at 17:27

2 Answers2

4

Maybe this rails_view_annotator gem would be a good starting point:

The Rails View Annotator wraps the rendering of Rails partials with html comments indicating the disk location of the rendered partial.

According to the project README, the gem will output rendered content like this:

<!-- begin: app/views/user/_bio.html.haml (from app/views/user/show.html.haml:4) -->
<div class='bio'>Ed's Bio</div>
<!-- end: app/views/user/_bio.html.haml (from app/views/user/show.html.haml:4) -->

However, it doesn't look like it's in active development so might not be compatible with recent Rails versions, YMMV.

wjordan
  • 19,770
  • 3
  • 85
  • 98
  • This gem is only for rendering partials. It will not display the template used for rendering the view – Aleks Dec 28 '16 at 08:43
  • Correct, this gem is only a 'partial' solution to the question (See https://github.com/duncanbeevers/rails_view_annotator/issues/4). It still looks like a good starting point, though. – wjordan Dec 28 '16 at 20:09
  • Thanks, this helps a lot. Well deserved bounty. – Haris Krajina Jan 02 '17 at 15:25
0

I don't know whether there is a gem for this, but you can overwrite a class designed for rendering the output of the all the views and partials located in:

.bundle/ruby/ruby-2.3.1/gems/action-view-4.2.5.1/lib/action_view/template.rb (replace your version number)

There is a method called def compile(mod) owerwrite that method to add your custom strings that you want to present in the view. For your case you could do something like this:

def compile(mod) #:nodoc:
    encode!
    method_name = self.method_name
    code = @handler.call(self)

    # This is the code I have added
    code.insert(63, "@output_buffer.safe_append='\n<!--#{self.inspect} start -->\n'.freeze\;")
    code.insert(code.size-19, "@output_buffer.safe_append='\n<!--#{self.inspect} end -->\n'.freeze\;")

    # Make sure that the resulting String to be eval'd is in the
    # encoding of the code
    source = <<-end_src
      def #{method_name}(local_assigns, output_buffer)
        _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code}
      ensure
        @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer
      end
    end_src

    # Make sure the source is in the encoding of the returned code
    source.force_encoding(code.encoding)

    # In case we get back a String from a handler that is not in
    # BINARY or the default_internal, encode it to the default_internal
    source.encode!

    # Now, validate that the source we got back from the template
    # handler is valid in the default_internal. This is for handlers
    # that handle encoding but screw up
    unless source.valid_encoding?
      raise WrongEncodingError.new(@source, Encoding.default_internal)
    end

    mod.module_eval(source, identifier, 0)
    ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
  end

The code I have added is:

code.insert(63, "@output_buffer.safe_append='\n<!--#{self.inspect} start -->\n'.freeze\;")
code.insert(code.size-19, "@output_buffer.safe_append='\n<!--#{self.inspect} end -->\n'.freeze\;")

And it will output in the views all the partials and all the views as they are being rendered.

The result would be like:

<!--app/views/profiles/_header.html.erb start -->
Your text on the page
<!--app/views/profiles/_header.html.erb end -->

If you would like to extract that into a separate class, create a new initializes in config/initializers/ directory of your Rails project, called, for example render_override.rb and paste code something like this:

ActionView::Template.class_eval do
  # @override
  def compile(mod)
    encode!
    method_name = self.method_name
    code = @handler.call(self)

    # This is the code I have added
    code.insert(63, "@output_buffer.safe_append='\n<!--#{self.inspect} start -->\n'.freeze\;")
    code.insert(code.size-19, "@output_buffer.safe_append='\n<!--#{self.inspect} end -->\n'.freeze\;")

    # Make sure that the resulting String to be eval'd is in the
    # encoding of the code
    source = <<-end_src
      def #{method_name}(local_assigns, output_buffer)
        _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code}
      ensure
        @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer
      end
    end_src

    # Make sure the source is in the encoding of the returned code
    source.force_encoding(code.encoding)

    # In case we get back a String from a handler that is not in
    # BINARY or the default_internal, encode it to the default_internal
    source.encode!

    # Now, validate that the source we got back from the template
    # handler is valid in the default_internal. This is for handlers
    # that handle encoding but screw up
    unless source.valid_encoding?
      raise WrongEncodingError.new(@source, Encoding.default_internal)
    end

    mod.module_eval(source, identifier, 0)
    ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
  end
end

Restart the server so the changes can be picked up, and your views will now be rendered with those new settings.

You might want to surround your eval class with a check RAILS_ENV='dev' or something similar so it could be run just in development, and you are ready to go.

Aleks
  • 4,866
  • 3
  • 38
  • 69