From 463f18c207274d43044431dd2ff44bae93257257 Mon Sep 17 00:00:00 2001 From: Marcin Kulik Date: Sat, 1 Feb 2014 01:16:28 +0100 Subject: [PATCH] Refactor user page to use a presenter object --- app/controllers/users_controller.rb | 15 +-- app/decorators/user_decorator.rb | 6 +- app/models/user.rb | 11 ++ app/presenters/user_presenter.rb | 61 +++++++++ app/views/users/show.html.slim | 36 +++--- spec/decorators/user_decorator_spec.rb | 12 +- spec/models/user_spec.rb | 18 +++ spec/presenters/user_presenter_spec.rb | 167 +++++++++++++++++++++++++ 8 files changed, 297 insertions(+), 29 deletions(-) create mode 100644 app/presenters/user_presenter.rb create mode 100644 spec/presenters/user_presenter_spec.rb diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 89387fc..1083da9 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,23 +1,16 @@ class UsersController < ApplicationController - PER_PAGE = 15 - before_filter :ensure_authenticated!, :only => [:edit, :update] + attr_reader :user + def new @user = build_user end def show - @user = User.find_by_nickname!(params[:nickname]).decorate - - collection = @user.asciicasts. - includes(:user). - order("created_at DESC"). - page(params[:page]). - per(PER_PAGE) - - @asciicasts = PaginatingDecorator.new(collection) + user = User.find_by_nickname!(params[:nickname]) + render locals: { page: UserPresenter.build(user, current_user) } end def create diff --git a/app/decorators/user_decorator.rb b/app/decorators/user_decorator.rb index 9824669..76fc92b 100644 --- a/app/decorators/user_decorator.rb +++ b/app/decorators/user_decorator.rb @@ -9,7 +9,7 @@ class UserDecorator < ApplicationDecorator wrap_with_link(avatar_image_tag) end - def fullname_and_nickname + def full_name if model.name.present? "#{model.name} (#{model.nickname})" else @@ -17,6 +17,10 @@ class UserDecorator < ApplicationDecorator end end + def joined_at + created_at.strftime("%b %-d, %Y") + end + private def wrap_with_link(html) diff --git a/app/models/user.rb b/app/models/user.rb index 051933c..59f17b4 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -50,6 +50,17 @@ class User < ActiveRecord::Base asciicasts.where('id <> ?', asciicast.id).order('RANDOM()').limit(limit) end + def editable_by?(user) + user && user.id == id + end + + def paged_asciicasts(page, per_page) + asciicasts. + includes(:user). + order("created_at DESC"). + paginate(page, per_page) + end + private def generate_auth_token diff --git a/app/presenters/user_presenter.rb b/app/presenters/user_presenter.rb new file mode 100644 index 0000000..dad1073 --- /dev/null +++ b/app/presenters/user_presenter.rb @@ -0,0 +1,61 @@ +class UserPresenter + + PER_PAGE = 15 + + attr_reader :user, :current_user, :page, :per_page + + def self.build(user, current_user, page = nil, per_page = nil) + new(user.decorate, current_user, page || 1, per_page || PER_PAGE) + end + + def initialize(user, current_user, page, per_page) + @user = user + @current_user = current_user + @page = page + @per_page = per_page + end + + def title + "#{user.nickname}'s profile".html_safe + end + + def user_full_name + user.full_name + end + + def user_joined_at + user.joined_at + end + + def user_avatar_image_tag + user.avatar_image_tag + end + + def show_settings? + user.editable_by?(current_user) + end + + def asciicast_count_text(h) + count = h.pluralize(user.asciicast_count, 'asciicast') + "#{count} by #{user.nickname}" + end + + def user_nickname + user.nickname + end + + def asciicasts + @asciicasts ||= get_asciicasts + end + + def current_users_profile? + current_user && current_user == user + end + + private + + def get_asciicasts + PaginatingDecorator.new(user.paged_asciicasts(page, per_page)) + end + +end diff --git a/app/views/users/show.html.slim b/app/views/users/show.html.slim index 524c2a3..8f21b92 100644 --- a/app/views/users/show.html.slim +++ b/app/views/users/show.html.slim @@ -1,27 +1,33 @@ -- content_for(:title, "#{@user.nickname}'s profile".html_safe) +- content_for(:title, page.title) .profile-page section.cinema .container - span.user-avatar = @user.avatar_image_tag + span.user-avatar = page.user_avatar_image_tag h1 - = @user.fullname_and_nickname - small - ' Joined on #{@user.created_at.strftime("%b %-d, %Y")} + = page.user_full_name + small = "Joined on #{page.user_joined_at}" - - if current_user == @user + - if page.show_settings? p.actions = link_to 'Settings', edit_user_path .container - h2 #{pluralize @user.asciicast_count, 'asciicast'} by #{@user.nickname} + h2 = page.asciicast_count_text(self) .row.asciicast-list .col-md-12 - = render 'asciicasts/previews', asciicasts: @asciicasts, per_row: 3 - = paginate @asciicasts - - - if @asciicasts.size == 0 - - if current_user && current_user.id == @user.id - ' You haven't recorded anything yet. - = link_to 'Record now', docs_path('getting-started') + - if page.asciicasts.present? + = render 'asciicasts/previews', asciicasts: page.asciicasts, per_row: 3 + = paginate page.asciicasts + - else + - if page.current_users_profile? + p + ' Seems like you haven't recorded anything yet. + = link_to 'Record now', docs_path('getting-started') + ' . + p + ' If you have already recorded an asciicast but you don't see it + here then associate it with your account by running + code asciinema auth + ' in your terminal. - else - ' #{@user.nickname} hasn't recorded anything yet. + ' #{page.user_nickname} hasn't recorded anything yet. diff --git a/spec/decorators/user_decorator_spec.rb b/spec/decorators/user_decorator_spec.rb index 30b346e..1b82628 100644 --- a/spec/decorators/user_decorator_spec.rb +++ b/spec/decorators/user_decorator_spec.rb @@ -65,8 +65,8 @@ describe UserDecorator do end end - describe '#fullname_and_nickname' do - subject { decorator.fullname_and_nickname } + describe '#full_name' do + subject { decorator.full_name } let(:user) { double('user', nickname: 'sickill', name: name) } @@ -83,4 +83,12 @@ describe UserDecorator do end end + describe '#joined_at' do + subject { decorator.joined_at } + + let(:user) { double('user', created_at: Time.parse('1970-01-01')) } + + it { should eq('Jan 1, 1970') } + end + end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 5683c71..464d4fc 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -136,4 +136,22 @@ describe User do end end + describe '#editable_by?' do + subject { user.editable_by?(other) } + + let(:user) { create(:user) } + + context "when it's the same user" do + let(:other) { user } + + it { should be(true) } + end + + context "when it's a different user" do + let(:other) { create(:user) } + + it { should be(false) } + end + end + end diff --git a/spec/presenters/user_presenter_spec.rb b/spec/presenters/user_presenter_spec.rb new file mode 100644 index 0000000..a192f38 --- /dev/null +++ b/spec/presenters/user_presenter_spec.rb @@ -0,0 +1,167 @@ +require 'spec_helper' + +describe UserPresenter do + + describe '.build' do + subject { described_class.build(user, current_user, page, per_page) } + + let(:user) { double('user', decorate: decorated_user) } + let(:decorated_user) { double('decorated_user') } + let(:current_user) { double('current_user') } + let(:page) { 2 } + let(:per_page) { 5 } + + it "builds presenter instance with given user decorated" do + expect(subject.user).to be(decorated_user) + end + + it "builds presenter instance with given current_user" do + expect(subject.current_user).to be(current_user) + end + + it "builds presenter instance with given page" do + expect(subject.page).to eq(2) + end + + context "when page is nil" do + let(:page) { nil } + + it "builds presenter instance with page = 1" do + expect(subject.page).to eq(1) + end + end + + it "builds presenter instance with given per_page" do + expect(subject.per_page).to eq(5) + end + + context "when per_page is nil" do + let(:per_page) { nil } + + it "builds presenter instance with per_page = PER_PAGE" do + expect(subject.per_page).to eq(described_class::PER_PAGE) + end + end + end + + let(:presenter) { described_class.new(user, current_user, page, per_page) } + let(:user) { double('user', nickname: 'cartman') } + let(:current_user) { double('current_user') } + let(:page) { 2 } + let(:per_page) { 5 } + + let(:view_context) { + controller = ApplicationController.new + controller.request = ActionController::TestRequest.new + controller.view_context + } + + describe '#title' do + subject { presenter.title } + + it { should eq("cartman's profile") } + end + + describe '#user_full_name' do + subject { presenter.user_full_name } + + before do + allow(user).to receive(:full_name) { 'E.C.' } + end + + it { should eq('E.C.') } + end + + describe '#user_joined_at' do + subject { presenter.user_joined_at } + + before do + allow(user).to receive(:joined_at) { 'Jan 1, 1970' } + end + + it { should eq('Jan 1, 1970') } + end + + describe '#user_avatar_image_tag' do + subject { presenter.user_avatar_image_tag } + + before do + allow(user).to receive(:avatar_image_tag) { '' } + end + + it { should eq('') } + end + + describe '#show_settings?' do + subject { presenter.show_settings? } + + before do + allow(user).to receive(:editable_by?).with(current_user) { :right } + end + + it { should eq(:right) } + end + + describe '#asciicast_count_text' do + subject { presenter.asciicast_count_text(view_context) } + + before do + allow(user).to receive(:asciicast_count) { 3 } + end + + it { should eq('3 asciicasts by cartman') } + end + + describe '#user_nickname' do + subject { presenter.user_nickname } + + it { should eq('cartman') } + end + + describe '#asciicasts' do + subject { presenter.asciicasts } + + let(:collection) { [asciicast] } + let(:asciicast) { double('asciicast', decorate: double(title: 'quux')) } + + before do + allow(user).to receive(:paged_asciicasts) { collection } + end + + it "gets user's asciicasts paged" do + subject + + expect(user).to have_received(:paged_asciicasts).with(2, 5) + end + + it "wraps the asciicasts with paginating decorator" do + expect(subject).to respond_to(:current_page) + expect(subject).to respond_to(:total_pages) + expect(subject).to respond_to(:limit_value) + expect(subject.first.title).to eq('quux') + end + end + + describe '#current_users_profile?' do + subject { presenter.current_users_profile? } + + context "when current_user is the same user" do + let(:current_user) { user } + + it { should be(true) } + end + + context "when current_user is a different user" do + let(:current_user) { double('other_user') } + + it { should be(false) } + end + + context "when current_user is nil" do + let(:current_user) { nil } + + it { should be_falsy } + end + end + +end