Pivotal Labs

Main menu

Skip to primary content
Skip to secondary content
  • About
  • Case Studies
  • Team
    • Executives
    • Locations
      • San Francisco (HQ)
      • Boston
      • Boulder
      • Denver
      • London
      • Los Angeles
      • New York
  • Community
    • Blogs
    • Tech Talks
    • Events
  • Careers
    • Lifestyle
    • Principles & Practices
    • Benefits
    • FAQ
    • Apply
  • Tools
  • Contact
    • Press Room
    • Press Releases
    • In The News
    • Press Kit
  • All
  • Labs
  • Standup
  • Tracker
Pivotal Labs

Presenters and Logical APIs

Pivotal Labs
Tuesday, September 21, 2010

MVC in Rails and Thick Views

The default way of using ActionController and ERB in Rails is to pass models into your views, and let the views figure out what to render and where. There’s been some pushback in the community on this, people are talking about getting rid of “thick views” with logicless templates like Mustache. Mustache is awesome, but if thick views are the problem, we don’t need to go that far if we just change the way we think about controllers a little bit.

Thick views are a controller problem

Consider this ERB, using a loop instead of partials for illustration:

<%@posts.each do |post|%>
  <%=post.author.name%>
<%end%>

If we write our controller like this, the controller will do two queries:

@posts = Post.all(:include => :author)

If we write our controller like this, it will do only one query in the controller:

@posts = Post.all

And the view will do as many queries as there are posts. I’m sure you’ve seen code like this in projects you have worked on. In the standard Rails model, we pass ActiveRecord objects to our views, they might or might not be pre-populated, and thin views can be hard to tell from thick views.

We can explicitly test this by mocking Post and counting queries, but that is a lot of extra work, and you have to remember to do it everywhere.

APIs are unit testable

In contrast to standard controller tests, the result of a JSON API call can be declaratively described. The difference between

{:posts => [:id => 4, :title => "Jerusalem", :author_id => 33]}

and

{:posts => [:id => 4, :title => "Jerusalem", :author => {:name => "Josephus, :id => 33}]}

Is pretty clear, and easy to assert on. If we TDD our controller methods this way it protects us from a lot of the n-query problems in views.

API work comes too late

I’ve seen serveral teams experience pain because Rails does not make it easy to develop a JSON API alongside your HTTP controllers. Rails 3 is better than Rails 2 on this, but you still have to handle the cases separately, particularly as things get more complex.

Twitter is a good example of this. The HTML endpoint recently started consuming its own JSON API over HTTP for generating pages:

One of the most important architectural changes is that Twitter.com is now a client of our own API. It fetches data from the same endpoints that the mobile site, our apps for iPhone, iPad, Android, and every third-party application use. This shift allowed us to allocate more resources to the API team, generating over 40 patches. In the initial page load and every call from the client, all data is now fetched from a highly optimized JSON fragment cache.

This is a very cool pattern in general for a web application. Consuming your own API keeps your business logic in one place, separate from your display logic. And caching on JSON API calls can be easier than caching after building HTML. Obviously you don’t want to run two processes when you’re getting started, but we can take inspiration from this as we organize our code.

The Presenter pattern

A Presenter is a controller that delivers data rather than objects. People like Martin Fowler have been talking about this for a while, and Twitter recently announced a major technical change along these lines.

From the Wikipedia article on “Presenter First”, which is mostly aimed at desktop design, but is applicable to the web, too:

When used in GUI applications, this approach allows the presentation logic and business logic of the application to be developed in a test first manner decoupled from on-screen widgets. Thus, the vast majority of the application programming can be tested via unit tests in an automated test suite. In so doing, the reliance on GUI testing tools to perform extensive system testing can be reduced to verifying basic GUI operation or eliminated entirely.

Unlike standard Rails TDD, if you construct your page data as an API call, your controller tests can now assert directly on that data, instead of the objects that your templates can query with. Testing on a data dictionary is more declarative, and it helps you write thinner views.

Make your view a logical client of your API

We can use the Presenter pattern in Rails by having our controllers call the methods that generate our JSON API at the moment right before serialization to a string. That way there is only one code path that does business logic, which solves the divergence problem.

Vanna

Vanna is an exploration of how MVP can work in Rails. It’s not fully functional (in particular it needs a way to do non-200 returns), but it is enough to illustrate the point. Mixing Vanna into ActionController::Metal restructures controller flow to mimic JSON API calls. Your controllers wind up looking like this:

class VillainsController < ApplicationController
  def index
    {:main => {"villains" => Villain.all}}
  end

  def show(opts = params)
    villain = Villain.named(opts["villain"]).first
    sidebar = catchphrases("villains" => villain["partners"])
    {:main => {:villain =>villain, :sidebar => sidebar}}
  end

  def catchphrases(opts=params)
    names = opts["villains"]
    Villain.named(names).map{|p| p["catchphrase"]}
  end
end

How does this differ from a standard Rails controller? For one thing there’s a silly “catchphrases” method, which is a placeholder for an API method that gives a set of fields for a set of records:

def catchphrases(opts=params)
  names = opts["villains"]
  Villain.named(names).map{|p| p["catchphrase"]}
end

This is structured like an API call. It expects to take in a set of keys, and return some subset of fields on the objects with those keys. Exactly the sort of thing an API does, but not something that you usually see in web apps in their early stages. There’s also an explicit (opts=params) in the signature. Let’s take a look at the show method to see why that is there:

def show(opts = params)
  villain = Villain.named(opts["villain"]).first
  sidebar = catchphrases("villains" => villain["partners"])
  {:villain =>villain, :sidebar => sidebar}
end

On the second line of the method we are explicitly calling one method in a controller from another, passing a params hash (suggested by Richard Crowley). We construct a data dictionary for these smaller calls and pass it through to our template.

Here’s what the view looks like:

<div id=main style="width:70%;float:left;border:black 5px solid;">
  <ul>
    <li><%=villain[:name]%></li>
    <li><%=villain[:catchphrase]%></li>
  </ul>
</div>
<div id=sidebar style="width:20%;float:right;border:black 5px solid;">
  <% sidebar.each do |catchphrase| %>
    <%=catchphrase%> <br/>
  <% end %>
</div>

This looks almost exactly like a standard Rails ERB template, except that the top level objects available to you are accessed as locals, not with an @.

API for free

Vanna makes all your controller methods available as API calls automatically. Because you are explicitly returning a data dictionary, there’s no need to have a different code path for your HTML and JSON. The only difference is whether you go to ERB, or call to_json on the dictionary. So now we can do our controller tests as API tests (the tests in Vanna itself are a little different):

 def test_catchphrases
   header "Accept", 'application/json'
   get "/villains/catchphrases?villains=luis"
   assert{ JSON(last_response.body) == ["Hungry like the volcano!"] }
 end

So OK, we can call our catchphrases method as a JSON call.

def test_show_has_catchphrases
  header "Accept", 'application/json'
  get "/villains/show?villain=luis"
  assert{ JSON(last_response.body)["main"]["sidebar"] == ["You're gonna get punted!"] }
end

And the same data is available inside the larger call which includes it.

def test_html_has_sidebar
  get "/villains/show?villain=luis"
  assert{last_response.body.match(/div id=sidebar/) != nil }
end

And the template actually renders out the sidebar.

Now we have a single data retrieval code path, that breaks off into HTML right before we actually render HTML.

The code for Vanna is on Github, but here’s the meat of it:

module Vanna
  def self.included(klass)
    raise "#{klass.name} does not inherit from  ActionController::Metal" unless  klass.ancestors.include?(ActionController::Metal)
    klass.send(:include,  AbstractController::Layouts)
    klass.send(:include,  AbstractController::Callbacks)
    klass.append_view_path "app/views"
    klass.class_eval("def logger; ActionController::Base.logger; end;")
  end

  def process_action(method_name, *args)
    run_callbacks(:process_action, method_name) do    dictionary = send_action(method_name, *args)    dictionary = @layout_pieces.merge(dictionary) if @layout_pieces  && dictionary.is_a?(Hash)    self.response_body = request.format.symbol == :json ?
 dictionary.to_json : html_render(dictionary)
    end
  end
  def html_render(dictionary)
    render(nil, :locals => dictionary)
  end
end

It’s an example more that a real tool – it’s a demonstration of a valuable way to think about building web applications.

Try it out

It’s extremely rough still, but it serves pages. Here’s the setup:

Gemfile

gem 'vanna', :git => 'git://github.com/MikeSofaer/vanna.git'

ApplicationController

Change ‘ActionController::Base’ to ‘Vanna::Base’

layouts/application.html.erb

Remove the javascript_include line. (and if you know what to do so this isn’t necessary, tell me.)

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Standup 2010-09-20: Your Weekend Was Short Because of GoGaRuCo Edition

Pivotal Labs
Monday, September 20, 2010

(Title: Standup 11/31/2000: Something Interesting from the post)

Ask for Help

“Storage free/DB-less ActiveRecord (in Rails 2)?”

This seems to be readily available in Rails 3. For those who would not like to upgrade their projects, the following options have been suggested:

  • ActiveHash & ActiveYAML by our very own Jeff Dean.
  • ActiveRecord::Base Without Tables
  • The initial query was for form validations, so the ActiveForm plugin may be of use here.

“Restricting ActiveRecord API?”

Sometimes you want extra assurance Bad Things are not being done by the wrong users. Possible solutions to lock permissions and such:

  • User plugin from our Socialitis project
  • A mysterious plugin known as Alcatraz, link not available at this time.

Interesting Things

  • Rails 3 controllers will run after_filters if you return false in the before_filter. This is deemed to be good behavior, but be aware of it.
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Will Read

Standup 2010-09-17: Why Aren't You at GoGaRuCo Edition

Will Read
Friday, September 17, 2010

Ask for Help

“How do I upgrade Passenger on the EngineYard Cloud with a unsupported account?”

Jump in to EY’s IRC to get ‘er done.

“Using the Vimeo gem, uploading fails with ‘Unknown upload error’”

No answer, but the gem hasn’t been updated in a while and the working theory is that it isn’t compatible with the new OAuth authentication. Suggestions included using HTTP Scoop to see what the traffic is doing and get more info.

“Ngnix 0.6 + Rails 3 isn’t sending the Content-Length header for 201 responses”

Since there’s no content length specified, some browsers grab on to the keep-alive and appear to take a very long time to load the page. In Rails 2.x the Content-Length is supplied. Does anyone know if this was an intentional choice in Ngnix/Rails 3?

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Davis W. Frank

Jasmine 1.0 Released

Davis W. Frank
Tuesday, September 14, 2010

Pivotal Labs and the Jasmine team are pleased to announce the 1.0 release of Jasmine, our JavaScript BDD framework.

This release is largely about stabilization. We’ve fixed some bugs, stabilized our API, and improved support for JRuby and Ruby 1.9. There are several deprecations (especially around asynchronous ‘waits’ blocks and some matchers) to go along with the fixes and features. See the release notes for full details.

This release is available in standalone and Ruby gem versions.

Our upcoming priorities include improving the documentation, properly supporting Rails 3 and RSpec 2 and a number of other environments, and looking at all of your pull requests and issue reports. Jasmine’s backlog lives in Pivotal Tracker.

Big thanks to the Jasmine community for the pull requests; the majority of changes between 0.11.1 and 1.0 are based on user-submitted patches.

See you on the mailing list and make sure to follow us on Twitter: @jasminebdd…

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Ryan Richard

Standup 9/10/10

Ryan Richard
Friday, September 10, 2010

Interesting Things

  • Using gem install with the soap4r gem doesn’t work. Fear not! It can be installed by downloading the tar file and manually unpacking it.

  • There is a node.js meetup at Dogpatch Labs tonight at 6:30.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Ryan Richard

Standup 9/9/10

Ryan Richard
Thursday, September 9, 2010

Helps

“Has anyone experienced fuzzy PDF rendering at some zoom levels on iOS?”

One project was having trouble with their iOS app which shows a PDF file. When you zoom the view, the text becomes fuzzy looking. The text in the PDF is only fuzzy at certain, seemingly nondeterministic, zoom levels. They are drawing the PDFs with CGContextDrawPDFPage and scaling with CGContextScaleCTM. Does anyone know what could cause this?

Interesting Things

  • There doesn’t seem to be any reliable way to know if an HTML checkbox got checked by simply looking in the DOM. There’s an attribute on the INPUT called “checked”, but this is only read during page load to set the initial value of the checkbox.

  • Interesting Amazon EC2 announcement:

    “Micro instances provide 613 MB of memory and support 32-bit and 64-bit platforms on both Linux and Windows. Micro instance pricing for On-Demand instances starts at $0.02 per hour for Linux and $0.03 per hour for Windows.”

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Ryan Richard

Standup 9/8/10

Ryan Richard
Wednesday, September 8, 2010

Helps

“Is anyone using Devise (authentication solution for Rails) with SSL for an app’s login page (on Heroku)?”

One project was having trouble with the redirects caused by this and is looking for help. Has anyone experienced trouble with this?

Interesting Things

  • There is a Ruby meetup tonight at 6:30 pm in San Francisco on the subject of unicode and internationalization in Ruby.
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Ryan Richard

Standup 9/7/10

Ryan Richard
Tuesday, September 7, 2010

Helps

“Using bundler vs. gemsets?”

These can be used separately or together depending on the needs of your project. Bundler checks dependencies to make sure you have the gems that your application needs, while RVM gemsets keep your gems separate to prevent conflicts (between Rails 2 and 3, or example).

“Anyone ever see IE fall over when parsing 500 KB of JSON?”

Try explicitly using eval()

Interesting Things

  • If you have any strange, unexplained errors with RVM, try upgrading to see if it solves them.
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Dan Podsedly

Aragorn: iPad app for Pivotal Tracker

Dan Podsedly
Monday, August 30, 2010

We’re excited to add a new entry to the 3rd party tools directory: Aragorn, the first Pivotal Tracker client for the iPad.

The first version of Aragorn supports read only views of your projects and stories within them. Thanks to @elight for writing the app!

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
JT Archie

Standup 08/26/2010 – Rails controls too much of cache

JT Archie
Thursday, August 26, 2010

Help

“Does Postgres have an equivalent MySql Myisam high write performance?”

A Pivot was wondering what settings in Postgres can be enabled for high performance INSERTs (willing to sacrafice SELECT performance on said table).

“Why is Paperclip giving a Stream close error when in development?”

When using Paperclip in development, any file upload results in a Stream IOError. Trying to replicate the problem with Paperclip on its own causes no problems, so it appears to be a conflicting library.

Interesting Things

  • An update to yesterday’s Rubymine font size issue. The file drawer can not be specifically updated, but the overall system font can be under Preferences > Appearance > Fontsize. Make sure to restart Rubymine.
  • Google Voice has been added as a service on to GMail. Make phone calls for free to anywhere in the US from your browser.
  • An update to yesterday’s Safari ‘Back’ button issue. Safari caches the previous ajax call (dubbed ajax zombie) when going back to a previous page. The fix had to do with changing Rails default “Cache-control” header to be “no-store”.
  • 0 Shares
  • Share on Facebook
  • Share on Twitter

Topics

  • agile (783)
  • rails (117)
  • testing (90)
  • ruby (85)
  • ruby on rails (71)
  • jobs (62)
  • javascript (59)
  • techtalk (44)
  • ironblogger (42)
  • rspec (39)
  • bloggerdome (34)
  • productivity (34)
  • activerecord (30)
  • rubymine (30)
  • git (29)
  • gogaruco (29)
  • nyc (27)
  • design (24)
  • mobile (23)
  • pivotal tracker (22)
  • process (21)
  • cucumber (21)
  • jasmine (19)
  • ios (18)
  • tracker ecosystem (17)
  • webos (17)
  • objective-c (17)
  • fun (16)
  • android (16)
  • palm (16)
  • ci (16)
  • "soft" ware (16)
  • bdd (15)
  • tdd (15)
  • cedar (15)
  • rails3 (14)
  • performance (14)
  • css (14)
  • gem (13)
  • mouse-free development (12)
  • selenium (12)
  • goruco (12)
  • bundler (12)
  • api (12)
  • keyboard (11)
  • meetup (11)
  • railsconf (11)
  • nyc-standup (11)
  • capybara (10)
  • mac (10)
Subscribe to agile Feed
  1. ←
  2. 1
  3. ...
  4. 34
  5. 35
  6. 36
  7. 37
  8. 38
  9. 39
  10. 40
  11. ...
  12. 79
  13. →
  • About
  • Case Studies
  • Team
  • Community
  • Careers
  • Tools
  • Contact
  • Labs
  • Events

Contact Us

contact@pivotallabs.com
+1 415-77-PIVOT
TwitterLinkedInFacebook

Pivotal Tracker

Tracker is the award-winning agile project management tool that enables real-time collaboration around a shared, prioritized backlog.
Visit pivotaltracker.com >