Skip to content

Instantly share code, notes, and snippets.

@aseroff
Last active April 27, 2023 18:48
Show Gist options
  • Save aseroff/1535c132e01300680592a48d4c636106 to your computer and use it in GitHub Desktop.
Save aseroff/1535c132e01300680592a48d4c636106 to your computer and use it in GitHub Desktop.
YARD customization for Rails

So you're running a Rails application and want to spruce up your YARD documentation. Here's the guide I wish I had.

Step 0: Plugin

Add yard-activerecord and yard-activesupport-concern to your Gemfile, add --plugin activerecord and --plugin activesupport-concern to your .yardopts flags, and db/schema.rb to the end of your .yardopts sources. Your models' attributes and assocations should now be included in the documentation.

Step 1: CSS/JS

To modify your YARD template, create doc-src/templates/default/fulldoc/html, then add --template-path doc-src/templates to your project's .yardopts. You can now create a doc-src/templates/default/fulldoc/html/css/common.css and the styles will be included (but overwrites need to be !important).

Step 2: Menu logic

To modify the side menu of the docs, create doc-src/templates/default/fulldoc/html/setup.rb to override the default methods. Here's a mediocre sample based on aws's sdk that creates folders like those in a Rails app/. Note that this also involves adding a doc-src/templates/default/fulldoc/html/js/nolink.js which can be found here.

include Helpers::ModuleHelper

def javascripts_full_list
  super + ['js/nolink.js']
end

def class_list(root = Registry.root, tree = TreeContext.new)
  out = String.new("")
  children = run_verifier(root.children)
  children += @items.select {|o| o.namespace.is_a?(CodeObjects::Proxy) } if root == Registry.root
  controller_children, other_children = children.partition { |child| child.title&.include?('Controller') }
  helper_children, other_children = other_children.partition { |child| child.title&.include?('Helper') }
  model_children, other_children = other_children.partition { |child| child.is_a?(CodeObjects::ClassObject) && child.superclass }
  module_children, other_children = other_children.partition { |child| child.is_a?(CodeObjects::ModuleObject) }
  out << class_list_section(:controllers, tree, controller_children)
  out << class_list_section(:helpers, tree, helper_children)
  out << class_list_section(:models, tree, model_children)
  out << class_list_section(:modules, tree, module_children)
  out << class_list_children(tree, other_children)
  out
end

def class_list_section(type, tree, children)
  out = String.new("")
  out << "<li class='#{tree.classes.join(' ')} nolink'>"
  out << "<div class='item' style='padding-left:#{tree.indent}'>"
  out << "<a class='toggle'></a> " + type.to_s.capitalize
  out << "</div><ul>"
  tree.nest do
    out << class_list_children(tree, children)
  end
  out << '</ul></li>'
end

def class_list_children(tree, children)
  out = String.new("")
  children&.compact&.sort_by(&:path)&.each do |child|
    if child.is_a?(CodeObjects::NamespaceObject)
      name = child.namespace.is_a?(CodeObjects::Proxy) ? child.path : child.name
      grand_children = run_verifier(child.children)
      is_parent = grand_children.any? {|o| o.is_a?(CodeObjects::NamespaceObject) }
      out << "<li id='object_#{child.path}' class='#{tree.classes.join(' ')}'>"
      out << "<div class='item' style='padding-left:#{tree.indent}'>"
      out << "<a class='toggle'></a> " if is_parent
      out << linkify(child, name)
      out << " &lt; #{child.superclass.name}" if child.is_a?(CodeObjects::ClassObject) && child.superclass
      out << "<small class='search_info'>"
      out << child.namespace.title
      out << "</small>"
      out << "</div>"
      if is_parent
        tree.nest do
          out << "<ul>#{class_list_children(tree, grand_children)}</ul>"
        end
      end
      out << "</li>"
    end
  end
  out
end

Step 3: Additional Files

You can add additional files to your documentation by adding them to a doc-src/files and adding doc-src/files/**/*.md in your .yardopts sources argument (after the '-').

The ordering of these files is unintuitive, so if you're like me and would prefer to see them listed alphabetically, you can add the following to doc-src/templates/default/fulldoc/html/full_list_file.erb:

<% even_odd = 'odd' %>
<% @items.sort_by { |i| i.attributes['title'] || ''}.each do |item| %>
  <li id="object_<%= item.path %>" class="<%= even_odd %>">
    <div class="item"><span class="object_link"><%= link_file item %></span></div>
  </li>
  <% even_odd = (even_odd == 'even' ? 'odd' : 'even') %>
<% end %>

You've added custom files, styles, and menu logic to your documentation in about 5 minutes. Good work!

Extra credit: GitHub Pages

it's super easy to use GitHub Pages as the host of your documentation. There's really only two gotchas:

  • YARD defaults to outputting to 'doc', but GitHub prefers 'docs'. Add -o docs to your .yardopts.
  • GitHub Pages uses Jekyll to serve the pages. Jekyll doesn't like files that start with an underscore, and YARD uses a file called _index.html, which will 404 on a Jekyll build, unless you drop a _config.yml in your docs folder with the line include: ['_index.html'].

Once you've done that, you can use the GitHub website to setup the GitHub Pages documentation hosting integration.

Your final .yardopts should look something like

-o docs
-r README.md
--protected
--private
--template-path docsrc/templates
--plugin activerecord
--plugin activesupport-concern
'app/**/*.rb'
'lib/**/*.rb'
'db/schema.rb' -
docsrc/files/**/*.md
@tcd
Copy link

tcd commented Sep 24, 2020

Thank you for the guide :)

Also,aws-sdk-ruby removed nolink.js, here's what was in it in case anyone else needs it:

// https://github.com/aws/aws-sdk-ruby/blob/1P_lib_env/doc-src/templates/default/fulldoc/html/js/nolink.js
$(function () {
  $('li.nolink').off('click');
});

@aseroff
Copy link
Author

aseroff commented Sep 24, 2020

@tcd Glad it was helpful, and thanks for providing that .js file!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment