README.md in keynote-0.0.1 vs README.md in keynote-0.1.0

- old
+ new

@@ -1,8 +1,246 @@ -## Keynote +# Keynote -Presenters for Rails. +*Flexible presenters for Rails.* -### TODO +[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/rf-/keynote) -* Generators. -* Documentation. +A presenter is an object that encapsulates view logic. Like Rails helpers, +presenters help you keep complex logic out of your templates. + +Keynote provides a consistent interface for defining and instantiating +presenters. + +## Usage + +### The basic idea + +A simple case is making a presenter that's named after a model class and holds +helper methods related to that model. + +``` ruby +# app/presenters/user_presenter.rb + +class UserPresenter < Keynote::Presenter + presents :user + + def display_name + "#{user.first_name} #{user.last_name}" + end + + def profile_link + link_to user, display_name, data: {user_id: user.id} + end +end +``` + +You can then instantiate it by calling the `present` method (aliased to `k`) in +a view, helper, controller, or another presenter. + +``` erb +<%# app/layouts/_header.html.erb %> + +<div id="header"> + ... + <div class="profile_link"> + <%= k(current_user).profile_link %> + </div> +</div> +``` + +If you pass anything other than a symbol as the first parameter of +`present`/`k`, Keynote will assume you want to instantiate a presenter named +after the class of that object -- in this case, the model is a `User`, so +Keynote looks for a class called `UserPresenter`. + +### Generating HTML + +To make it easier to generate slightly more complex chunks of HTML, Keynote +includes a modified version of Magnus Holm's [Rumble](https://github.com/judofyr/rumble) +library. Rumble gives us a simple block-based syntax for generating HTML +fragments. Here's a small example: + +``` ruby +build_html do + div :id => :content do + h1 'Hello World', :class => :main + end +end +``` + +Becomes: + +``` html +<div id="content"> + <h1 class="main">Hello World</h1> +</div> +``` + +You can use tag helpers like `div`, `span`, and `a` only within a block passed +to the `build_html` method. The `build_html` method returns a safe string. See +[the documentation for `Keynote::Rumble`](http://rubydoc.info/gems/keynote/Keynote/Rumble) +for more information. + +### A more complex example + +Let's add to our original example by introducing a named presenter. In addition +to `UserPresenter`, which has general-purpose methods for displaying the User +model, we'll create `HeaderPresenter`, which has methods that are specific to +the `layouts/header` partial. + +``` ruby +# app/presenters/header_presenter.rb + +class HeaderPresenter < Keynote::Presenter + presents :user + + def profile_or_login_link + if logged_in? # defined in a helper + profile_link + else + login_link + end + end + + def profile_link + build_html do + div class: 'profile_link' do + k(user).profile_link + end + end + end + + def login_link + build_html do + div class: 'login_link' do + link_to 'Log In', login_url + end + end + end +end +``` + +``` erb +<%# app/layouts/_header.html.erb %> + +<% header = present(:header, current_user) %> + +<div id="header"> + ... + <%= header.profile_or_login_link %> +</div> +``` + +We've avoided putting a conditional in the template, and we've also avoided +exposing the `profile_or_login_link` method to other parts of the app that +shouldn't need to care about it. It's located in a class that's specific to +this context. + +### Delegating to models + +If you want to delegate some calls on the presenter to one of the presenter's +underlying objects, it's easy to do it explicitly with ActiveSupport's +`delegate` API. + +``` ruby +# app/presenters/user_presenter.rb + +class UserPresenter < Keynote::Presenter + presents :user + delegate :first_name, :last_name, :to => :user + + def display_name + "#{first_name} #{last_name}" + end +end +``` + +You can also generate prefixed methods like `user_first_name` by passing +`:prefix => true` to the `delegate` method. + +## Rationale + +### Why use presenters or decorators at all? + +The main alternative is to use helpers. Helpers are fine for many use cases -- +Rails' built-in tag and form helpers are great. They have some drawbacks, +though: + +* Every helper method you write gets mixed into the same view object as the + built-in Rails helpers, URL generators, and all the other junk that comes + along with `ActionView::Base`. In a freshly-generated Rails project: + + ```ruby + >> ApplicationController.new.view_context.public_methods.count + => 318 + >> ApplicationController.new.view_context.private_methods.count + => 119 + ``` + +* Helpers can't have state that isn't "global" relative to the view, which + can make it hard to write helpers that work together. + +* By default, every helper is available in every view. This makes it difficult + to set boundaries between different parts of your app and organize your view + code cleanly. + +### Why not use decorators? + +The biggest distinction between Keynote and similar libraries like +[Draper](https://github.com/drapergem/draper) and +[DisplayCase](https://github.com/avdi/display-case) is that Keynote +presenters aren't decorators -- undefined method calls don't +fall through to an underlying model. + +Applying the Decorator pattern to generating views is a reasonable thing to do. +However, this practice also has some downsides. + +* Decorators make the most sense when there's exactly one object that's + relevant to the methods you want to encapsulate. They're less helpful when + you want to do things like define a class whose responsibility is to help + render a specific part of your user interface, which may involve bringing in + data from multiple models or collections. + +* When reading code that uses decorators, it often isn't obvious if a given + method is defined on the decorator or the underlying model, especially when + the decorator is applied in the controller instead of the view. + +* Passing decorated models between controllers and views can make it unclear + whether a view (especially a nested partial) depends on a model having some + specific decorator applied to it. This makes refactoring view and decorator + code harder than it needs to be. + +## Generators + +Keynote doesn't automatically generate presenters when you generate models or +resources. To generate a presenter, you can use the `presenter` generator, +like so: + +``` bash +$ rails g presenter FooBar foo bar + create app/presenters/foo_bar_presenter.rb + create spec/presenters/foo_bar_presenter_spec.rb +``` + +That project uses RSpec, but the generator can also create test files for +Test::Unit or MiniTest::Rails if applicable. + +## Compatibility + +Keynote is supported on Rails 3.0, 3.1, 3.2, and 4.0. Keynote presenters are +testable with Test::Unit, RSpec, and MiniTest::Rails. + +If you find problems with any of the above integrations, please open an issue. + +## Development + +You can run Keynote's tests across all supported versions of Rails as follows: + +``` bash +$ bundle install +$ rake appraisal:gemfiles +$ rake appraisal:install +$ rake appraisal +``` + +Feel free to submit pull requests according to the usual conventions for Ruby +projects.