A Walk Through of a Code Sleuthing Exercise

Using a myriad of tools to orient

In this post I use several tools to find my way to what I hope to be the answer to an initial question.

There are two repositories of interest:

A Hyku Repository
A Hyrax-application
Hyrax
A Rails engine

The Hyku Repository mounts the Hyrax Engine; providing much of it’s functionality.

I also use a few command-line tools:

rg
“recursively searches directories for a regex pattern while respecting your gitignore”; a super speedy alternative to the venerable grep command. (see the source code)
git
the ubiquitous distributed source control tool

Instead of rg, you could use ag (aka the Silver Searcher).

And as always, I’m relying on Emacs 📖 for viewing files. The git-link package helps me quickly select a region of code and generate a remote link (e.g. a link to Github to the current commit of the selected region).

Now, with the preliminaries out of the way, let’s dive into the journey through two code-bases.

The Walk Through

A team member wanted to know in the Hyku repository what items were available in app/views/hyrax/base/_collections_parent_row.html.erb (see code here):

<!--<dt> <%#= t(".label", type: type.humanize) %> </dt>-->

<dt>Collection</dt>
<dd>
    <% if items.blank? %>
      <p><%= t('.empty', type: type.humanize) %></p>
    <% else %>
      <ul class="tabular">
	<% items.each do |item| %>
	  <li class='attribute attribute-title'>
	    <%= link_to item.title.first, url_for_document(item) %>
	  </li>
	<% end %>
      </ul>
    <% end %>
  </dd>

First I wanted to know if this was in Hyrax or custom code for the application. I checked the current repository version of Hyrax. It was on v2.9.6. I ran rg "hyrax \(\d+\.\d+\.\d+\)" Gemfile.lock to get the Hyrax version. I didn’t see the above partial in that version of Hyrax, so assumed it was something local. For completeness, I could check later version of Hyrax, but I chose not to. In the link sent to me of the above view, I checked the SHA of that file (e.g. 21402d0fcf989dafac315e048e87f2709eeee931). Locally, I ran git show with the given SHA. For the commit that introduced the partial, I wanted to see what called that partial:

> git show 21402d0fcf989dafac315e048e87f2709eeee931 | rg collections_parent_row

+<%= render 'collections_parent_rows', presenter: presenter %>
diff --git a/app/views/hyrax/base/_collections_parent_row.html.erb b/app/views/hyrax/base/_collections_parent_row.html.erb
+++ b/app/views/hyrax/base/_collections_parent_row.html.erb
diff --git a/app/views/hyrax/base/_collections_parent_rows.html.erb b/app/views/hyrax/base/_collections_parent_rows.html.erb
+++ b/app/views/hyrax/base/_collections_parent_rows.html.erb
+  <%= render 'collections_parent_row', type: model_name, items: items, presenter: presenter %>
+    <%= render 'collections_parent_row', type: type, items: items, presenter: presenter %>

The above results are a bit ugly but I wanted to see the files and where all it was referenced at the time of the introduced change. In my experience I want to see two things: the state of the code when we introduced something and the current state of the code. I also checked the current state of the repository:

❯ rg collections_parent_row
app/views/hyrax/base/_collections.html.erb
1:<%= render 'collections_parent_rows', presenter: presenter %>

app/views/hyrax/base/_collections_parent_rows.html.erb
3:  <%= render 'collections_parent_row', type: model_name, items: items, presenter: presenter %>
9:    <%= render 'collections_parent_row', type: type, items: items, presenter: presenter %>

Looking at app/views/hyrax/base/_collections_parent_rows.html.erb we have the following:

<% presenter.presenter_types.each do |type| %>
  <% presenter.grouped_presenters(filtered_by: type).each_pair do |_, items| %>
    <%= render 'collections_parent_row', type: type, items: items, presenter: presenter %>
  <% end %>
<% end %>

Given that we’re in app/views/hyrax/base/ the presenter could be a variety of things; works, collections, pages, my, etc. (I ran ls app/views/hyrax on the Hyrax repository to see the potential candidates; not all of the results are actual candidates but it is enough to be cautious about making assumptions).

The partial had two methods to go searching for: #presenter_types and #grouped_presenters.

Hopping over to Hyrax (checking out v2.9.6), I wanted to find where we defined those methods; namely what is the presenter object’s class in the above views.

Though finding by def may not be adequate, we’ll consider it good enough. Especially since I got a hit.

The regular expression I used would check for both class and instance methods that started with either presenter_types or grouped_presenters.

❯ rg "def (self\.)?(presenter_types|grouped_presenters)"
app/presenters/hyrax/work_show_presenter.rb
142:    def presenter_types
147:    def grouped_presenters(filtered_by: nil, except: nil)

It looks like Hyrax::WorkShowPresenter; I especially like that it has a named parameter of filtered_by which corresponds to the above view.

Looking at the definition of #grouped_presenters (see below), we’re calling #member_of_collection_presenters.

def grouped_presenters(filtered_by: nil, except: nil)
  # TODO: we probably need to retain collection_presenters (as parent_presenters)
  #       and join this with member_of_collection_presenters
  grouped = member_of_collection_presenters.group_by(&:model_name).transform_keys { |key| key.to_s.underscore }
  grouped.select! { |obj| obj.downcase == filtered_by } unless filtered_by.nil?
  grouped.except!(*except) unless except.nil?
  grouped
end

Now let’s look at #member_of_collection_presenters:

# Get presenters for the collections this work is a member of via the member_of_collections association.
# @return [Array<CollectionPresenter>] presenters
def member_of_collection_presenters
  PresenterFactory.build_for(ids: member_of_authorized_parent_collections,
			     presenter_class: collection_presenter_class,
			     presenter_args: presenter_factory_arguments)
end

What’s I liked about this is that we’re passing in some ids of authorized parent collections. This looks really promising!

Let’s look at #member_of_authorized_parent_collections:

def member_of_authorized_parent_collections
  # member_of_collection_ids with current_ability access
  @member_of ||= Hyrax::CollectionMemberService.run(solr_document, current_ability).map(&:id)
end

I definitely like the comment: “member_of_collection_ids with current_ability access”. And in this query we have two things:

solr_document
The SOLR document representation of the current work (after all we’re in the Hyrax::WorkShowPresenter).
current_ability
The instance of the Ability for the current user.

With that class we can answer the question: What are the collections authorized for a given work and given current user (by way of current ability)?

At this point, I have enough of to feel comfortable answering the initial question.

Conclusion

I wanted to take the time to walk through one of the many available pathways when exploring a problem. Within an application, I often rely on Language Server Protocol (LSP 📖) (by way of the Eglot package) to jump to definitions. However, when working on two repositories, the jump is often not viable.

I instead use some connective command-line tools to help orient from one repository to the other.