Presenters: for a more object oriented view approach (and a better testing)!

I made this post more to express my admiration to the presenters pattern than to show something new.
Presenters makes the testing easier and the view more elegant.

This tutorial is a simple user CRUD, where the user informs name, email, date of birth and if the data should be private.
If the data should be private, the informations of email and date of birth will not be shown (will show a “Hidden” label instead).

Running sample

http://fpastore-02.herokuapp.com/

Final version repository

https://github.com/fpastore/02-presenters

Pre-requisites

Please, read first: Bootstrap Project for the Tutorials

User CRUD

First, lets create a simple scaffold:

#console
rails g scaffold User name:string email:string date_of_birth:date hide_information:boolean
# just to make it prettier
rails g bootstrap:themed users -f

Then run the migrations.

#console
rake db:migrate
rake db:test:prespare

Because there is no need to test rails generated scaffold, lets jump to the Decorator.

The Decorator

Decorator? Would not be presenters, are you sure?

Decorators are for general purpose, it’s an addition of functionalities to some class, while presenters are a bridge between the model and the view.

Shortly, we will use decorators as a bridge between model and view.

Draper Gem

The Draper gem makes simple to add Decorators to our models.
If you want to know more about drapper:

To use Draper we simple add it to Gemfile.

#Gemfile
gem "draper"

And create a decorator to our model.

#console
rails g decorator user

Factory Object

Before we start, lets create a factory of each User we will need to use in our tests.
It will be a simple user (that will show all his information) and a private user, that will hide his personal informations:

#spec/factories/users.rb
FactoryGirl.define do
  factory :user do
    name "Felipe"
    email "felipepastoree@gmail.com"
    date_of_birth "1990-01-01"
    hide_information false

    factory :private_user do
      hide_information true
    end
  end
end

Testing Decorators

With the decorator, rails generated the “user_decorator_spec.rb” where we will write our tests. 😀

The main reason because I like decorators is because I don’t need test all view, I can simple test each functionality separated.

#spec/decorators/user_decorator_spec.rb
require 'spec_helper'
describe UserDecorator do
  describe "Hides Information" do
    let(:user) {UserDecorator.new(FactoryGirl.create(:user))}
    let(:private_user) {UserDecorator.new(FactoryGirl.create(:private_user))}

    context "when user marks to hide information" do
      it "email should not be shown" do
        private_user.email.should_not == "felipepastoree@gmail.com"
      end
      it "date of birth should not be shown" do
        private_user.date_of_birth.should_not eql "1990-01-01".to_date
      end
    end
  end
end

To run the tests:

#console
bundle exec rspec spec

Test Status: Failing

Note the “UserDecorator.new” in the “let()” function.
It get our model and decorates it.

Then to let the tests pass:

#app/decorators/user_decorator.rb
class UserDecorator < Draper::Base
  decorates :user
  def email
    return nil if user.hide_information
    user.email
  end
  def date_of_birth
    return nil if user.hide_information
    user.date_of_birth
  end
end

Note that we use the same name to the function that the property we want to hide.
This is because the Decorator override the call to the attribute, so it will call our function, not the property.

Test Status: Passing

We don’t want to receive a nil response, we want to receive a “Hidden” label instead, so:

#spec/decorators/user_decorator_spec.rb
#... previous tests
      it "email should be replaced by Hidden" do
        private_user.email.should == "Hidden"
      end
      it"date of birth should be replaced by Hidden" do
        private_user.email.should == "Hidden"
      end
#...

And some modifications on the model:

#app/decorators/user_decorator.rb
#...
  def email
    return "Hidden" if user.hide_information
    user.email
  end
  def date_of_birth
    return "Hidden" if user.hide_information
    user.date_of_birth
  end
#...

Test Status: Passing

For a better user experience, lets wrap our “Hidden” label on a label tag.

First we adjust the tests:

#spec/decorators/user_decorator_spec.rb
#... previous tests
      it "email should be replaced by Hidden" do
        private_user.email.should include "Hidden"
      end
      it"date of birth should be replaced by Hidden" do
        private_user.email.should include "Hidden"
      end
#...

Test Status: Passing

Note that we changed “==” for “include”, because the output will not be just a raw string anymore.

Then we create the “hidden_tag” that will be our output when the field show be hidden.

#app/decorators/user_decorator.rb
#...
  def email
    return hidden_label if user.hide_information
    user.email
  end
  def date_of_birth
    return hidden_label if user.hide_information
    user.date_of_birth
  end

  private
  def hidden_label
    h.content_tag :span, "Hidden", :class => "label"
  end
#...

Before we finish, lets write tests for the non private case:

#app/decorators/user_decorator.rb
#...
    context "when user left hide information unmarked" do
      it "shows email" do
        user.email.should == "felipepastoree@gmail.com"
      end
      it "shows date of birth" do
        user.date_of_birth.should eql "1990-01-01".to_date
      end
    end
#...

Test Status: Passing

Now our decorator is ready to go, just left us to check the view if is displaying right.

Integration tests

Hidden fields should be hide on Show and Index views.
To start, integration tests:

#console
rails g integration_test user_hidden_information

On the “user_hidden_informations_spec.rb”:

#spec/requests/user_hidden_informations_spec.rb
require 'spec_helper'
describe "UserHiddenInformations" do
  context "when user marks to hide information" do
    before(:each) do
      @private_user = FactoryGirl.create(:private_user)
    end

    it "email and date of birth should not be shown on show page" do
      visit user_path(@private_user)
      %w[felipepastoree@gmail.com 1990].each do |hidden_record|
        page.should_not have_content(hidden_record)
      end
    end
    it "email and date of birth should not be shown on index page" do
      visit users_path
      %w[felipepastoree@gmail.com 1990].each do |hidden_record|
        page.should_not have_content(hidden_record)
      end
    end
    it "displays a label called hidden on show page instead" do
      visit user_path(@private_user)
      page.should have_content("Hidden")
    end
    it "displays a label called hidden on index page instead" do
      visit users_path
      page.should have_content("Hidden")
    end
  end
end

Test Status: Failing

To make them pass, we just need to decorate the user model on the controller. Easy.

#app/controllers/users_controller.rb
#...
  def index
    @users = UserDecorator.decorate(User.all)
  end

  def show
    @user = UserDecorator.new(User.find(params[:id]))
  end
#...

The “decorate” method, decores a list of elements, and the “new” method, decores just one element.

Test Status: Passing

Then, the tests for the non private user:

#spec/requests/user_hidden_informations_spec.rb
#...
  context "when user leave hide information blank" do
    before(:each) do
      @user = FactoryGirl.create(:user)
    end
    it "email and date of birth should be shown on show page" do
      visit user_path(@user)
      %w[felipepastoree@gmail.com 1990].each do |record|
        page.should have_content(record)
      end
    end
    it "email and date of birth should be shown on index page" do
      visit users_path
      %w[felipepastoree@gmail.com 1990].each do |record|
        page.should have_content(record)
      end
    end
  end
#...

If you want to see the end result:

#console
rails s

http://localhost:3000/users.

End.

Advertisements
Tagged , , ,

One thought on “Presenters: for a more object oriented view approach (and a better testing)!

  1. Bob Roberts says:

    Reblogged this on My Corner of the Web and commented:
    Presenters: Something I really need to start using. Nice read.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: