Understanding the Presenters Design Pattern

  • Primary Goal: Isolate views from business logic code.

Characteristics:

  • Presenters are used to hide implementation details from views. They serve as a go-between for controllers and views.
  • Presenters provide access to controller instance variables.
  • @presenter should be the only instance variable accessed in the view.
  • @presenter can be passed as presenter to partials rendered from the primary view.
  • The same presenter can be shared by multiple views and partials.
  • Presenters are not used for new/edit forms. Instead, Form objects are used. Form objects work similarly to Presenters, but include some additional features for dealing with forms, such as specifying which fields should be required.

This documentation uses object show page presenters to highlight some characteristics of presenters. Show page presenters are the most common to customize with overrides ond extensions.

A deeper dive into some of the show page presenters methods

Show pages are views that display information for a single object in the repository. Show page presenters you may want to explore are…

Delegate retrieval to solr_document

Access to property values is provided by the solr document. To allow presenters to surface these values, the metadata access methods are delegated to solr_document. All the show page presenters use delegation in this way.

Example Default

actual default for works

    # Metadata Methods
    delegate :title, :date_created, :description,
             :creator, :contributor, :subject, :publisher, :language, :embargo_release_date,
             :lease_expiration_date, :license, :source, :rights_statement, :thumbnail_id, :representative_id,
             :rendering_ids, :member_of_collection_ids, to: :solr_document

Example Extension

The original delegations still exist. The following is an example of adding more delegations for custom metadata fields.

  # Custom Metadata Methods
  delegate :contact_email, :contact_phone, :department, to: :solr_document

Specify which properties to show

For works, this is not determined by the presenter. It is determined by app/views/curation_concerns/base/_attribute_rows.html.erb. For more information on updating _attribute_rows.html.erb, see the Customizing Metadata tutorial’s lesson on Modifying the Show Page.

For collections, this is determined by the terms method in the presenter.

Example Default

actual default for collections

    # Terms is the list of fields displayed by
    # app/views/collections/_show_descriptions.html.erb
    def self.terms
      [:total_items, :size, :resource_type, :creator, :contributor, :keyword, :license, :publisher, :date_created, :subject,
       :language, :identifier, :based_near, :related_url]
    end

Example Override

In this case, the terms method is completely overridden and only the terms defined in this self.terms will be displayed. In this example, a few of the default properties are included and custom properties are added. Any default property not included in this list will not be shown on the collection show page.

    # Terms is the list of fields displayed by
    # app/views/collections/_show_descriptions.html.erb
    def self.terms
      [:total_items, :size, :contact_email, :contact_phone, :department, :keyword]
    end

Hiding terms with blank values

By default, terms with blank values are hidden.

  • For works, I do not know if you can change this.
  • For collections, the hiding of terms with blank values is controlled by the terms_with_values method.

Example Default

The default code for object presenters excludes display of terms with nil values in method…

actual default for collections

  def terms_with_values
    self.class.terms.select { |t| self[t].present? }
  end

Example Override

If you want terms without values to be displayed, override this method with…

  def terms_with_values
    # Return all terms even if they don't have values
    self.class.terms
  end

If your data has blank strings, you can ignore the blank string values too by overriding this method with…

  def terms_with_values
    # Ignore missing and blank values
    self.class.terms.select { |t| self[t].present? && self[t] != [""] }
  end

Example methods designed to isolate view from business logic code

Example work presenter method

Method in work presenter

app/presenters/hyrax/work_show_presenter.rb

    def work_featurable?
      user_can_feature_works? && solr_document.public?
    end

Used in view

app/views/hyrax/base/_show_actions.html.erb

  <% if presenter.work_featurable? %>
      <%= link_to "Feature", hyrax.featured_work_path(presenter, format: :json),
          data: { behavior: 'feature' },
          class: presenter.display_unfeature_link? ? 'btn btn-default collapse' : 'btn btn-default' %>

      <%= link_to "Unfeature", hyrax.featured_work_path(presenter, format: :json),
          data: { behavior: 'unfeature' },
          class: presenter.display_feature_link? ? 'btn btn-default collapse' : 'btn btn-default' %>
  <% end %>

Additional work presenter methods used to isolate business logic

Example collection presenter method

Method in collection presenter

app/presenters/hyrax/collection_presenter.rb

    def collection_type_badge
      content_tag(:span, collection_type.title, class: "label", style: "background-color: " + collection_type.badge_color + ";")
    end

Used in view

    <div class="collection-title-row-content">
      <h3 class="collection-title">
        <% # List multiple titles %>
        <% presenter.title.each_with_index do |title, index| %>
          <span><%= title %></span>
        <% end %>
      </h3>
      <%= presenter.permission_badge %>
      <%= presenter.collection_type_badge %>
    </div>

Also used in views…

Additional collection presenter methods used to isolate business logic

Overriding and Custom Presenter Methods

  • To limit future refactoring of customized code, it is recommended that you use the Module#Prepend pattern to override and extend presenter classes. The examples here use this approach.

On occasion you may want to change the behavior of an existing presenter method. For example, in a local app, we wanted to limit the total_items method to return a count of a specific type of work only. We used class prepending to make the override, so our changed looks like…

# Based on the Module#prepend pattern in ruby which is used in some Hyrax.
# Uses the to_prepare Rails hook in application.rb to inject this module to override Hyrax::AdminSetPresenter module
module PrependedPresenters::AdminSetPresenter
  # Override Hyrax::AdminSetPresenter#total_items method
  # and filter query to only include publications in count
  def total_items
    ActiveFedora::SolrService.count("{!field f=isPartOf_ssim}#{id}", fq: "has_model_ssim:Publication")
  end
end

You may also want to add custom methods to presenters. This is also done by adding the new method to the presenter prepend class and then adjusting the view to use the new presenter method.

Presenters used in context

See Customizing Metadata tutorial’s lesson on Modifying the Show Page which covers changes required beyond modifying presenters to customize how data is shown on an object’s show page.