More improvements to presenters

Signed-off-by: Rémy Coutable <remy@rymai.me>
This commit is contained in:
Rémy Coutable 2017-01-10 17:41:04 -05:00
parent e950830ba6
commit 061bb6eb6e
16 changed files with 103 additions and 128 deletions

View file

@ -94,7 +94,7 @@ class Projects::BuildsController < Projects::ApplicationController
private private
def build def build
@build ||= project.builds.find_by!(id: params[:id]).present(current_user) @build ||= project.builds.find_by!(id: params[:id]).present(user: current_user)
end end
def build_path(build) def build_path(build)

View file

@ -53,7 +53,7 @@ class BasePolicy
def self.class_for(subject) def self.class_for(subject)
return GlobalPolicy if subject.nil? return GlobalPolicy if subject.nil?
if subject.class.ancestors.include?(Gitlab::View::Presenter::Base) if subject.class.try(:presenter?)
subject = subject.subject subject = subject.subject
end end

View file

@ -66,7 +66,7 @@ we gain the following benefits:
### Presenter definition ### Presenter definition
Every presenters should inherit from `Gitlab::View::Presenter::Simple`, which Every presenter should inherit from `Gitlab::View::Presenter::Simple`, which
provides a `.presents` method which allows you to define an accessor for the provides a `.presents` method which allows you to define an accessor for the
presented object. It also includes common helpers like `Gitlab::Routing` and presented object. It also includes common helpers like `Gitlab::Routing` and
`Gitlab::Allowable`. `Gitlab::Allowable`.
@ -76,7 +76,7 @@ class LabelPresenter < Gitlab::View::Presenter::Simple
presents :label presents :label
def text_color def text_color
LabelsHelper.text_color_for_bg(label.color) label.color.to_s
end end
def to_partial_path def to_partial_path
@ -95,7 +95,7 @@ class LabelPresenter < Gitlab::View::Presenter::Delegated
def text_color def text_color
# color is delegated to label # color is delegated to label
LabelsHelper.text_color_for_bg(color) color.to_s
end end
def to_partial_path def to_partial_path
@ -132,7 +132,7 @@ and then in the controller:
```ruby ```ruby
class Projects::LabelsController < Projects::ApplicationController class Projects::LabelsController < Projects::ApplicationController
def edit def edit
@label = @label.present(current_user) @label = @label.present(user: current_user)
end end
end end
``` ```

View file

@ -1,17 +0,0 @@
module Ci
class Build
class Presenter < Gitlab::View::Presenter::Delegated
presents :build
def erased_by_user?
# Build can be erased through API, therefore it does not have
# `erase_by` user assigned in that case.
erased? && erased_by
end
def erased_by_name
erased_by.name if erased_by
end
end
end
end

View file

@ -0,0 +1,15 @@
module Ci
class BuildPresenter < Gitlab::View::Presenter::Delegated
presents :build
def erased_by_user?
# Build can be erased through API, therefore it does not have
# `erased_by` user assigned in that case.
erased? && erased_by
end
def erased_by_name
erased_by.name if erased_by_user?
end
end
end

View file

@ -1,7 +0,0 @@
module Ci
class Variable
class Presenter < Gitlab::View::Presenter::Simple
presents :variable
end
end
end

View file

@ -9,13 +9,15 @@ module Gitlab
attr_reader :subject attr_reader :subject
def can?(user, action) def can?(user, action, overriden_subject = nil)
super(user, action, subject) super(user, action, overriden_subject || subject)
end end
private
class_methods do class_methods do
def presenter?
true
end
def presents(name) def presents(name)
define_method(name) { subject } define_method(name) { subject }
end end

View file

@ -8,13 +8,15 @@ module Gitlab
end end
def fabricate! def fabricate!
presenter_class.new(@subject, @attributes) presenter_class.new(subject, attributes)
end end
private private
attr_reader :subject, :attributes
def presenter_class def presenter_class
@subject.class.const_get('Presenter') "#{subject.class.name}Presenter".constantize
end end
end end
end end

View file

@ -6,32 +6,45 @@ describe Gitlab::View::Presenter::Base do
Struct.new(:subject).include(described_class) Struct.new(:subject).include(described_class)
end end
subject do describe '.presenter?' do
presenter_class.new(project) it 'returns true' do
presenter = presenter_class.new(project)
expect(presenter.class).to be_presenter
end
end end
describe '.presents' do describe '.presents' do
it 'exposes #subject with the given keyword' do it 'exposes #subject with the given keyword' do
presenter_class.presents(:foo) presenter_class.presents(:foo)
presenter = presenter_class.new(project)
expect(subject.foo).to eq(project) expect(presenter.foo).to eq(project)
end end
end end
describe '#can?' do describe '#can?' do
let(:project) { create(:empty_project) }
context 'user is not allowed' do context 'user is not allowed' do
it 'returns false' do it 'returns false' do
expect(subject.can?(nil, :read_project)).to be_falsy presenter = presenter_class.new(build_stubbed(:empty_project))
expect(presenter.can?(nil, :read_project)).to be_falsy
end end
end end
context 'user is allowed' do context 'user is allowed' do
let(:project) { create(:empty_project, :public) }
it 'returns true' do it 'returns true' do
expect(subject.can?(nil, :read_project)).to be_truthy presenter = presenter_class.new(build_stubbed(:empty_project, :public))
expect(presenter.can?(nil, :read_project)).to be_truthy
end
end
context 'subject is overriden' do
it 'returns true' do
presenter = presenter_class.new(build_stubbed(:empty_project, :public))
expect(presenter.can?(nil, :read_project, build_stubbed(:empty_project))).to be_falsy
end end
end end
end end

View file

@ -1,33 +1,29 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::View::Presenter::Delegated do describe Gitlab::View::Presenter::Delegated do
let(:project) { double(:project, foo: 'bar') } let(:project) { double(:project, bar: 'baz') }
let(:presenter_class) do let(:presenter_class) do
Class.new(described_class) Class.new(described_class)
end end
subject do
presenter_class.new(project)
end
it 'includes Gitlab::View::Presenter::Base' do it 'includes Gitlab::View::Presenter::Base' do
expect(described_class).to include(Gitlab::View::Presenter::Base) expect(described_class).to include(Gitlab::View::Presenter::Base)
end end
describe '#initialize' do describe '#initialize' do
subject do
presenter_class.new(project, user: 'user', foo: 'bar')
end
it 'takes arbitrary key/values and exposes them' do it 'takes arbitrary key/values and exposes them' do
expect(subject.user).to eq('user') presenter = presenter_class.new(project, user: 'user', foo: 'bar')
expect(subject.foo).to eq('bar')
expect(presenter.user).to eq('user')
expect(presenter.foo).to eq('bar')
end end
end end
describe 'delegation' do describe 'delegation' do
it 'does not forward missing methods to subject' do it 'forwards missing methods to subject' do
expect(subject.foo).to eq('bar') presenter = presenter_class.new(project)
expect(presenter.bar).to eq('baz')
end end
end end
end end

View file

@ -1,42 +1,38 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::View::Presenter::Factory do describe Gitlab::View::Presenter::Factory do
let(:variable) { create(:ci_variable) } let(:build) { Ci::Build.new }
describe '#initialize' do describe '#initialize' do
context 'without optional parameters' do context 'without optional parameters' do
subject do
described_class.new(variable)
end
it 'takes a subject and optional params' do it 'takes a subject and optional params' do
expect { subject }.not_to raise_error presenter = described_class.new(build)
expect { presenter }.not_to raise_error
end end
end end
context 'with optional parameters' do context 'with optional parameters' do
subject do
described_class.new(variable, user: 'user')
end
it 'takes a subject and optional params' do it 'takes a subject and optional params' do
expect { subject }.not_to raise_error presenter = described_class.new(build, user: 'user')
expect { presenter }.not_to raise_error
end end
end end
end end
describe '#fabricate!' do describe '#fabricate!' do
subject do
described_class.new(variable, user: 'user', foo: 'bar').fabricate!
end
it 'exposes given params' do it 'exposes given params' do
expect(subject.user).to eq('user') presenter = described_class.new(build, user: 'user', foo: 'bar').fabricate!
expect(subject.foo).to eq('bar')
expect(presenter.user).to eq('user')
expect(presenter.foo).to eq('bar')
end end
it 'detects the presenter based on the given subject' do it 'detects the presenter based on the given subject' do
expect(subject).to be_a(Ci::Variable::Presenter) presenter = described_class.new(build).fabricate!
expect(presenter).to be_a(Ci::BuildPresenter)
end end
end end
end end

View file

@ -6,28 +6,24 @@ describe Gitlab::View::Presenter::Simple do
Class.new(described_class) Class.new(described_class)
end end
subject do
presenter_class.new(project)
end
it 'includes Gitlab::View::Presenter::Base' do it 'includes Gitlab::View::Presenter::Base' do
expect(described_class).to include(Gitlab::View::Presenter::Base) expect(described_class).to include(Gitlab::View::Presenter::Base)
end end
describe '#initialize' do describe '#initialize' do
subject do
presenter_class.new(project, user: 'user', foo: 'bar')
end
it 'takes arbitrary key/values and exposes them' do it 'takes arbitrary key/values and exposes them' do
expect(subject.user).to eq('user') presenter = presenter_class.new(project, user: 'user', foo: 'bar')
expect(subject.foo).to eq('bar')
expect(presenter.user).to eq('user')
expect(presenter.foo).to eq('bar')
end end
end end
describe 'delegation' do describe 'delegation' do
it 'does not forward missing methods to subject' do it 'does not forward missing methods to subject' do
expect { subject.foo }.to raise_error(NoMethodError) presenter = presenter_class.new(project)
expect { presenter.foo }.to raise_error(NoMethodError)
end end
end end
end end

View file

@ -1,11 +1,11 @@
require 'spec_helper' require 'spec_helper'
describe Presentable do describe Presentable do
let(:build) { create(:ci_build) } let(:build) { Ci::Build.new }
describe '#present' do describe '#present' do
it 'returns a presenter' do it 'returns a presenter' do
expect(build.present).to be_a(Ci::Build::Presenter) expect(build.present).to be_a(Ci::BuildPresenter)
end end
it 'takes optional attributes' do it 'takes optional attributes' do

View file

@ -1,7 +1,7 @@
require 'spec_helper' require 'spec_helper'
describe BasePolicy, models: true do describe BasePolicy, models: true do
let(:build) { create(:ci_build) } let(:build) { Ci::Build.new }
describe '.class_for' do describe '.class_for' do
it 'detects policy class based on the subject ancestors' do it 'detects policy class based on the subject ancestors' do
@ -9,7 +9,7 @@ describe BasePolicy, models: true do
end end
it 'detects policy class for a presented subject' do it 'detects policy class for a presented subject' do
presentee = Ci::Build::Presenter.new(build) presentee = Ci::BuildPresenter.new(build)
expect(described_class.class_for(presentee)).to eq(Ci::BuildPolicy) expect(described_class.class_for(presentee)).to eq(Ci::BuildPolicy)
end end

View file

@ -1,11 +1,11 @@
require 'spec_helper' require 'spec_helper'
describe Ci::Build::Presenter do describe Ci::BuildPresenter do
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
let(:pipeline) { create(:ci_pipeline, project: project) } let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) } let(:build) { create(:ci_build, pipeline: pipeline) }
subject do subject(:presenter) do
described_class.new(build) described_class.new(build)
end end
@ -15,60 +15,62 @@ describe Ci::Build::Presenter do
describe '#initialize' do describe '#initialize' do
it 'takes a build and optional params' do it 'takes a build and optional params' do
expect { subject }.not_to raise_error expect { presenter }.not_to raise_error
end end
it 'exposes build' do it 'exposes build' do
expect(subject.build).to eq(build) expect(presenter.build).to eq(build)
end end
it 'forwards missing methods to build' do it 'forwards missing methods to build' do
expect(subject.ref).to eq('master') expect(presenter.ref).to eq('master')
end end
end end
describe '#erased_by_user?' do describe '#erased_by_user?' do
it 'takes a build and optional params' do it 'takes a build and optional params' do
expect(subject).not_to be_erased_by_user expect(presenter).not_to be_erased_by_user
end end
end end
describe '#erased_by_name' do describe '#erased_by_name' do
context 'when build is not erased' do context 'when build is not erased' do
before do before do
expect(build).to receive(:erased_by).and_return(nil) expect(presenter).to receive(:erased_by_user?).and_return(false)
end end
it 'returns nil' do it 'returns nil' do
expect(subject.erased_by_name).to be_nil expect(presenter.erased_by_name).to be_nil
end end
end end
context 'when build is erased' do context 'when build is erased' do
before do before do
expect(build).to receive(:erased_by).twice. expect(presenter).to receive(:erased_by_user?).and_return(true)
expect(build).to receive(:erased_by).
and_return(double(:user, name: 'John Doe')) and_return(double(:user, name: 'John Doe'))
end end
it 'returns the name of the eraser' do it 'returns the name of the eraser' do
expect(subject.erased_by_name).to eq('John Doe') expect(presenter.erased_by_name).to eq('John Doe')
end end
end end
end end
describe 'quack like a Ci::Build permission-wise' do describe 'quack like a Ci::Build permission-wise' do
context 'user is not allowed' do context 'user is not allowed' do
let(:project) { create(:empty_project, public_builds: false) } let(:project) { build_stubbed(:empty_project, public_builds: false) }
it 'returns false' do it 'returns false' do
expect(subject.can?(nil, :read_build)).to be_falsy expect(presenter.can?(nil, :read_build)).to be_falsy
end end
end end
context 'user is allowed' do context 'user is allowed' do
let(:project) { create(:empty_project, :public) } let(:project) { build_stubbed(:empty_project, :public) }
it 'returns true' do it 'returns true' do
expect(subject.can?(nil, :read_build)).to be_truthy expect(presenter.can?(nil, :read_build)).to be_truthy
end end
end end
end end

View file

@ -1,23 +0,0 @@
require 'spec_helper'
describe Ci::Variable::Presenter do
let(:variable) { double(:variable) }
subject do
described_class.new(variable)
end
it 'inherits from Gitlab::View::Presenter::Simple' do
expect(described_class.superclass).to eq(Gitlab::View::Presenter::Simple)
end
describe '#initialize' do
it 'takes a variable and optional params' do
expect { subject }.not_to raise_error
end
it 'exposes variable' do
expect(subject.variable).to eq(variable)
end
end
end