Introducing ActivePresenter: The presenter library you already know. 19

Posted by james
on Sunday, July 27

Presenters were a hot topic in the rails community last year. A lot of prominent bloggers wrote about using them, and the implementations they had come up with. Oddly, though, when I needed one a couple of weeks ago, I was unable to find a suitable implementation. Lots of articles — no code.

Let's answer the question on everybody's mind before we move on. Feel free to skip ahead if you already know the answer.

WTF is a presenter?!

In its simplest form, a presenter is an object that wraps up several other objects to display, and manipulate them on the front end. For example, if you have a form that needs to manipulate several models, you'd probably want to wrap them in a presenter.

Indeed, attribute_fu solves this problem for some cases. However, when you're dealing with unrelated models, or, really, any situation other than a parent saving its children, you're probably better off using a presenter.

Presenters take the multi model saving code out of your controller, and put it in to a nice object. Because presenter logic is encapsulated, it's reusable, and easy to test.

Want one?

ActivePresenter

Daniel and I wrote most of this on the train ride over to RubyFringe. It is an ultra-simple presenter base class that is designed to wrap ActiveRecord models (and, potentially, others that act like them).

ActivePresenter works just like an ActiveRecord model, except that it works with multiple models at the same time. Let me show you.

Imagine we've got a signup form that needs to create a new User, and a new Account. We'd create a presenter that looks like this.

class SignupPresenter < ActivePresenter::Base
  presents :user, :account
end

Then, we'd write a new action like this one:

def new
  @signup_presenter = SignupPresenter.new
end

And a form:

<%= error_messages_for :signup_presenter %>

<%- form_for @signup_presenter, :url => signup_url do |f| -%>
  <%= f.label :account_subdomain, "Subdomain" %>
  <%= f.text_field :account_subdomain %>
  <%= f.label :user_login, "Login" %>
  <%= f.text_field :user_login %>
<%- end -%>

A create action:

def create
  @signup_presenter = SignupPresenter.new(params[:signup_presenter])
  
  if @signup_presenter.save
    redirect_to dashboard_url
  else
    render :action => "new"
  end
end

Lastly, an update action:

def update
  @signup_presenter = SignupPresenter.new(:user => current_user, :account => current_account)
  
  if @signup_presenter.update_attributes(params[:signup_presenter])
    redirect_to dashboard_url
  else
    render :action => "edit"
  end
end

Seem familiar?

If you're using r_c, most of this comes for free with:

class SignupsController < ResourceController::Base
  model_name :signup_presenter
end

For more on complex forms in rails, and the presenter pattern, see my upcoming PeepCode screencast!

Organization

I have been sticking my presenters in app/presenters. If you want to do the same, you'll need to add a line like this to your environment.rb:

config.load_paths += %W( #{RAILS_ROOT}/app/presenters )

Get It!

As a gem:

$ sudo gem install active_presenter

As a rails gem dependency:

config.gem 'active_presenter'
Or get the source from github:
$ git clone git://github.com/giraffesoft/active_presenter.git

(or fork it at http://github.com/giraffesoft/active_presenter)

Also, check out the RDoc.

Comments

Leave a response

  1. Lucas HĂșngaroJuly 27, 2008 @ 08:28 PM

    Wow! Very nice. Congratulations once more!

  2. Piotr UsewiczJuly 28, 2008 @ 05:01 AM

    This is simply brilliant!

  3. Eric AndersonJuly 28, 2008 @ 08:46 AM

    The API looks good and I am sure the implementation is nice but I'm not sure I sold on the concept of a presenter. Seems like you are for the most part the developer is just implementing their controller in another object and then including a reference to the object in the controller. Almost like a mixin for controllers only.

    There might be a few cases where you would need the same forms in multiple controllers (i.e. the abstraction and reuse you provide with a presenter) but it seems that that would be a rare case. Is it worth the mental overhead of having this "presenter" concept for those few rare cases? My preference would be to just reuse the mixin concept people are already familiar with (i.e. implement the various REST actions on a module then mixin to multiple controllers). This would seem to give you the same reuse the presenter provides without the additional mental overhead of an entirely new concept.

    Just my two cents

  4. macournoyerJuly 28, 2008 @ 09:23 AM

    awesome work James & Daniel!

    @Eric, this is an implementation of the Presenter design pattern: http://blog.jayfields.com/2007/03/rails-presenter-pattern.html

  5. Mike SubelskyJuly 28, 2008 @ 09:57 AM

    hey James, great work!

    I led a BOF session at Railsconf 2008 on this topic, and there's definitely a big interest and need for this gem. In fact I registered "active_presenter" on RubyForge, but never got around to doing anything with it, so if you want to publish this gem there, I'd be glad to transfer the project to you. Just hit me up with an email at mike /-at-/ subelsky.com.

    -Mike

  6. Daniel HaranJuly 28, 2008 @ 10:17 AM

    @Eric Anderson: If you can avoid multi-object creation, then more power to you. This is indeed only for rare cases - although I think you confuse things by calling it a mixin. Check out the link macournoyer to get a better sense of what this encapsulates.

  7. Mathijs KwikJuly 28, 2008 @ 06:05 PM

    Will this gem also handle cases where I want to create/edit a single model with some 'child' models?

    So one project with (to leave javascript out of the equation) 10 tasks in a single form for creating and updating the tasks? What about deleting them?

  8. James GolickJuly 28, 2008 @ 08:19 PM

    @Mathijs: It doesn't yet, but we're hoping to have that functionality going soon.

  9. Swami AtmaJuly 29, 2008 @ 09:55 AM

    Hi James,

    Nice article.

    We met in Berlin last year. I need to send you an email. Can you email me to the address included in this form?

    Thanks.

  10. John CorriganJuly 29, 2008 @ 04:30 PM

    Hiya, I can't seem to grab this gem. I'm getting not found errors when I run the gem install command. Thoughts?

  11. James GolickJuly 29, 2008 @ 05:24 PM

    @John - No idea. I can run it ok. You're trying to download it from rubyforge?

  12. Geoff GarsideAugust 06, 2008 @ 12:19 PM

    This looks much nicer than the presenter plugin I slapped together, thanks.

  13. MichailAugust 20, 2008 @ 06:22 AM

    Thanks! It looks good. But what if there are equal attribute names? Like User.name and Account.name. What form will be in this case?

  14. RobAugust 29, 2008 @ 05:53 PM

    This looks great for multiple unrelated models or two models with a belongsto relationship, but doesn't handle hasmany scenarios, correct?

    <rant> Why are multi-model forms so fraking hard (no standard solution, gotta write extra code, little documentation on the solutions that exists) in Rails when WebObjects was doing this 10 years ago with keypaths? </rant>

  15. NateSeptember 09, 2008 @ 03:45 PM

    Awesome stuff. I do have a question though. After installing the plugin and then trying to run rake routes I get an error: uninitialized constant ActivePresenter.

    I installed from Github as a plugin. Also, when using the presenter to create a user and an account I'm getting some weird posting issues.

    so my AccountSignupPresenter presents :account, :user

    then on my form I have this: formfor @accountsignuppresenter, :url => accountspath

    for some reason everything looks great and works, but the form is posting to the accounts index action. It obviously should post to the accounts create action.

    Any help would be appreciated. Thanks again for the great work!

  16. Mark A. RichmanSeptember 12, 2008 @ 03:53 PM

    @Nate, have you tried something like this:

    :url => { :controller => 'accounts', :action => :create }

    I haven't tried active_presenter yet, but it does seem intriguing.

  17. MaxSeptember 18, 2008 @ 02:24 PM

    Hi, I've been using it and works really nice. Any plans to include a before_validation callback?

  18. James GolickSeptember 22, 2008 @ 08:22 AM

    @Max - Sure. If there's a need for one, I can definitely add it. Could you not put that validation in one of the models, though?

  19. Zach DennisOctober 04, 2008 @ 11:30 PM

    ActivePresenter seems to be doing two things: handling presentation logic for one or more models, and also handling save/update logic for more than one model. It's meddling in two distinct and completely separate responsibilities. This seems to encourages bad practice when it comes to dealing with either presentation logic and/or multiple model forms. Presenters should only be handling presentation logic. The save/update logic should be done in the model, or if it can't be done there in a domain Service/Manager object.

    CachingPresenter is a strict presentation-logic presenter. It does cool stuff with caching/memoization. Should it provide useful to James or anyone else seeing this post please check it out:

    • Github - http://github.com/zdennis/caching_presenter/

    • Docs - http://github.com/zdennis/caching_presenter/wikis/home

Comment






Clicky Web Analytics