Gem Version Build Status Coverage Status Inline docs Maintainability MIT License Gem GitHub stars

logo

MetaPresenter is a Ruby gem for writing highly focused and testable Rails view presenter classes. For each controller/action pair you get a presenter class in app/presenters that you can use in your views with with presenter.method_name. This helps you decompose your helper logic into tight, easily testable classes. There's even a DSL for method delegation on objects to reduce boilerplate.

overlay-shape-clean-sm

Installation

  1. Add this line to your application's Gemfile:
gem 'meta_presenter'
  1. Bundle from the command line:
$ bundle
  1. Include MetaPresenter::Helpers in your controller or mailer:
class ApplicationController < ActionController::Base
  include MetaPresenter::Helpers
end
class ApplicationMailer < ActionMailer::Base
  include MetaPresenter::Helpers
end

Example

Say you have a PagesController with #home and #logs actions. Underneath app/presenters you can add a presenter class for each action (Pages::HomePresenter and Pages::LogsPresenter). We'll also create an ApplicationPresenter superclass for methods that can be used in any action throughout the app.

app/
  controllers/
    application_controller.rb
    pages_controller.rb
  presenters/
    application_presenter.rb
    pages_presenter.rb
    pages/
      home_presenter.rb
      logs_presenter.rb
  views
    pages
      home.html.erb
      logs.html.erb
spec/ (or test/)
  presenters/
    application_presenter_spec.rb
    pages_presenter_spec.rb
    pages/
      home_presenter_spec.rb
      logs_presenter_spec.rb

app/controllers/page_controller.rb

class ApplicationController < ActionController::Base
  include MetaPresenter::Helpers

  # Controller methods automatically become available in views and other presenters.
  # So this gives you presenter.current_user in views, and you can call `current_user`
  # within your presenters as well
  def current_user
    User.first
  end
end

app/controllers/dashboard_controller.rb

class ApplicationController < ActionController::Base
  def home
  end

  def logs
  end

  private
    # presenter.logs in views
    def logs
      Log.all
    end
end

app/presenters/application_presenter.rb

class ApplicationPresenter < MetaPresenter::BasePresenter
  # Makes presenter.page_title available in all of your app's views
  def page_title
    "My App"
  end

  # presenter.last_login_at in views
  def 
    # controller methods from within the same scope
    # as the presenter are directly available
    current_user.
  end
end

app/presenters/pages_presenter.rb

class PagesPresenter < ApplicationPresenter
  # Makes presenter.nav_items available for
  # all actions on PagesController
  def nav_items
    [
      {name: "Home", path: home_path},
      {name: "Logs", path: logs_path}
    ]
  end
end

app/presenters/pages/home_presenter.rb

class Pages::HomePresenter < PagesPresenter
  # presenter.email, presenter.id or any other
  # method not already defined will delegate to
  # the current_user
  delegate_all_to = :current_user

  # presenter.greeting in views
  def greeting
    "Hello, #{current_user.name}"
  end
end

app/views/pages/home.html.erb

<h1>Home</h1>
<p><%= presenter.greeting %></p>
<p>Last login <%= distance_of_time_in_words_to_now(presenter.last_login_at) %></p>

app/presenters/pages/logs_presenter.rb

class Pages::LogsPresenter < PagesPresenter
  # presenter.size and presenter.last will delegate to 
  # the controller's private `#logs`
  delegate :size, :last, to: :logs

  # presenter.log_text(log) in view
  def log_text(log)
    log.description
  end
end

app/views/pages/logs.html.erb

<h1>Logs</h1>
<p>Num logs: #{presenter.size}</p>
<p>Last log: #{presenter.log_text(presenter.last)}</p>
<ul>
  <% presenter.logs.each do |log| %>
    <li>
      <%= presenter.log_text(log) %>
    </li>
  <% end %>
</ul>

Aliasing the presenter methods

If you want to customize the presenter method you can specify a shorthand by adding an alias_method to your controller or mailer:

class ApplicationController < ActionController::Base
  including MetaPresenter

  # So convenient!
  alias_method :presenter, :pr
end

Requirements

MetaPresenter supports Ruby >= 2.1 and ActionPack/ActionMailer >= 3.0.12. If you'd like to help adding support for older versions please submit a pull request with passing specs.

Specs

To run the specs for the currently running Ruby version, run bundle install and then bundle exec rspec. To run specs for every supported version of ActionPack, run bundle exec appraisal install and then bundle exec appraisal rspec.

Gem release

Make sure the specs pass, bump the version number in meta_presenter.gemspec, build the gem with gem build meta_presenter.gemspec. Commit your changes and push to Github, then tag the commit with the current release number using Github's Releases interface (use the format vx.x.x, where x is the semantic version number). You can pull the latest tags to your local repo with git pull --tags. Finally, push the gem with gem push meta_presenter-version-number-here.gem.

TODO

  • create an example app and link to the repo for it in this README
  • proofread the README instructions to make sure everything is correct
  • optional rake meta_presenter:install that generates the scaffolding for you. Or, you can manually create the files you want.
  • add support for layout-level presenters
  • add Rails 6 support once it comes out (hopefully just have to add a gemfiles/actionpack6.gemfile and it will run with the Appraisal suite)

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b feature/my-new-feature) or bugfix branch (git checkout -b bugfix/my-helpful-bugfix)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin feature/my-new-feature)
  5. Make sure specs are passing (bundle exec rspec)
  6. Create new Pull Request

License

See the LICENSE file.