This commit is contained in:
Les Hill 2011-07-30 11:25:45 -07:00
parent bd1caf1976
commit a0d9c13772
2 changed files with 56 additions and 56 deletions

View File

@ -1,9 +1,9 @@
module Draper module Draper
class Base class Base
require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/class/attribute'
class_attribute :denied, :allowed, :model_class class_attribute :denied, :allowed, :model_class
attr_accessor :model attr_accessor :model
DEFAULT_DENIED = Object.new.methods << :method_missing DEFAULT_DENIED = Object.new.methods << :method_missing
FORCED_PROXY = [:to_param] FORCED_PROXY = [:to_param]
self.denied = DEFAULT_DENIED self.denied = DEFAULT_DENIED
@ -11,24 +11,24 @@ module Draper
def initialize(input) def initialize(input)
input.inspect input.inspect
self.class.model_class = input.class if model_class.nil? self.class.model_class = input.class if model_class.nil?
@model = input @model = input
build_methods build_methods
end end
def self.find(input) def self.find(input)
self.new(model_class.find(input)) self.new(model_class.find(input))
end end
def self.decorates(input) def self.decorates(input)
self.model_class = input.to_s.classify.constantize self.model_class = input.to_s.classify.constantize
end end
def self.denies(*input_denied) def self.denies(*input_denied)
raise ArgumentError, "Specify at least one method (as a symbol) to exclude when using denies" if input_denied.empty? raise ArgumentError, "Specify at least one method (as a symbol) to exclude when using denies" if input_denied.empty?
raise ArgumentError, "Use either 'allows' or 'denies', but not both." if self.allowed? raise ArgumentError, "Use either 'allows' or 'denies', but not both." if self.allowed?
self.denied += input_denied self.denied += input_denied
end end
def self.allows(*input_allows) def self.allows(*input_allows)
raise ArgumentError, "Specify at least one method (as a symbol) to allow when using allows" if input_allows.empty? raise ArgumentError, "Specify at least one method (as a symbol) to allow when using allows" if input_allows.empty?
raise ArgumentError, "Use either 'allows' or 'denies', but not both." unless (self.denied == DEFAULT_DENIED) raise ArgumentError, "Use either 'allows' or 'denies', but not both." unless (self.denied == DEFAULT_DENIED)
@ -38,7 +38,7 @@ module Draper
def self.decorate(input) def self.decorate(input)
input.respond_to?(:each) ? input.map{|i| new(i)} : new(input) input.respond_to?(:each) ? input.map{|i| new(i)} : new(input)
end end
def helpers def helpers
@helpers ||= ApplicationController::all_helpers @helpers ||= ApplicationController::all_helpers
end end
@ -47,16 +47,16 @@ module Draper
def self.lazy_helpers def self.lazy_helpers
self.send(:include, Draper::LazyHelpers) self.send(:include, Draper::LazyHelpers)
end end
def self.model_name def self.model_name
ActiveModel::Name.new(model_class) ActiveModel::Name.new(model_class)
end end
def to_model def to_model
@model @model
end end
private private
def select_methods def select_methods
specified = self.allowed || (model.public_methods - denied) specified = self.allowed || (model.public_methods - denied)
(specified - self.public_methods) + FORCED_PROXY (specified - self.public_methods) + FORCED_PROXY
@ -69,7 +69,7 @@ module Draper
model.send method, *args, &block model.send method, *args, &block
end end
end end
end end
end end
end end
end end

View File

@ -3,7 +3,7 @@ require 'draper'
describe Draper::Base do describe Draper::Base do
subject{ Draper::Base.new(source) } subject{ Draper::Base.new(source) }
let(:source){ Product.new } let(:source){ Product.new }
context(".lazy_helpers") do context(".lazy_helpers") do
it "makes Rails helpers available without using the h. proxy" do it "makes Rails helpers available without using the h. proxy" do
@ -11,7 +11,7 @@ describe Draper::Base do
subject.send(:pluralize, 5, "cat").should == "5 cats" subject.send(:pluralize, 5, "cat").should == "5 cats"
end end
end end
context(".model_name") do context(".model_name") do
it "should return an ActiveModel::Name instance" do it "should return an ActiveModel::Name instance" do
Draper::Base.model_name.should be_instance_of(ActiveModel::Name) Draper::Base.model_name.should be_instance_of(ActiveModel::Name)
@ -23,14 +23,14 @@ describe Draper::Base do
ProductDecorator.new(source).model_class == Product ProductDecorator.new(source).model_class == Product
end end
end end
context(".model / .to_model") do context(".model / .to_model") do
it "should return the wrapped object" do it "should return the wrapped object" do
subject.to_model.should == source subject.to_model.should == source
subject.model.should == source subject.model.should == source
end end
end end
context("selecting methods") do context("selecting methods") do
it "echos the methods of the wrapped class except default exclusions" do it "echos the methods of the wrapped class except default exclusions" do
source.methods.each do |method| source.methods.each do |method|
@ -39,41 +39,41 @@ describe Draper::Base do
end end
end end
end end
it "should not override a defined method with a source method" do it "should not override a defined method with a source method" do
DecoratorWithApplicationHelper.new(source).length.should == "overridden" DecoratorWithApplicationHelper.new(source).length.should == "overridden"
end end
it "should always proxy to_param" do it "should always proxy to_param" do
source.send :class_eval, "def to_param; 1; end" source.send :class_eval, "def to_param; 1; end"
Draper::Base.new(source).to_param.should == 1 Draper::Base.new(source).to_param.should == 1
end end
it "should not copy the .class, .inspect, or other existing methods" do it "should not copy the .class, .inspect, or other existing methods" do
source.class.should_not == subject.class source.class.should_not == subject.class
source.inspect.should_not == subject.inspect source.inspect.should_not == subject.inspect
source.to_s.should_not == subject.to_s source.to_s.should_not == subject.to_s
end end
end end
it "should wrap source methods so they still accept blocks" do it "should wrap source methods so they still accept blocks" do
subject.block{"marker"}.should == "marker" subject.block{"marker"}.should == "marker"
end end
context ".find" do context ".find" do
it "should lookup the associated model when passed an integer" do it "should lookup the associated model when passed an integer" do
pd = ProductDecorator.find(1) pd = ProductDecorator.find(1)
pd.should be_instance_of(ProductDecorator) pd.should be_instance_of(ProductDecorator)
pd.model.should be_instance_of(Product) pd.model.should be_instance_of(Product)
end end
it "should lookup the associated model when passed a string" do it "should lookup the associated model when passed a string" do
pd = ProductDecorator.find("1") pd = ProductDecorator.find("1")
pd.should be_instance_of(ProductDecorator) pd.should be_instance_of(ProductDecorator)
pd.model.should be_instance_of(Product) pd.model.should be_instance_of(Product)
end end
end end
context ".decorate" do context ".decorate" do
it "should return a collection of wrapped objects when given a collection of source objects" do it "should return a collection of wrapped objects when given a collection of source objects" do
sources = [Product.new, Product.new] sources = [Product.new, Product.new]
@ -82,106 +82,106 @@ describe Draper::Base do
output.size.should == sources.size output.size.should == sources.size
output.each{ |decorated| decorated.should be_instance_of(Draper::Base) } output.each{ |decorated| decorated.should be_instance_of(Draper::Base) }
end end
it "should return a single wrapped object when given a single source object" do it "should return a single wrapped object when given a single source object" do
output = Draper::Base.decorate(source) output = Draper::Base.decorate(source)
output.should be_instance_of(Draper::Base) output.should be_instance_of(Draper::Base)
end end
end end
describe "a sample usage with denies" do describe "a sample usage with denies" do
let(:subject_with_denies){ DecoratorWithDenies.new(source) } let(:subject_with_denies){ DecoratorWithDenies.new(source) }
it "should proxy methods not listed in denies" do it "should proxy methods not listed in denies" do
subject_with_denies.should respond_to(:hello_world) subject_with_denies.should respond_to(:hello_world)
end end
it "should not echo methods specified with denies" do it "should not echo methods specified with denies" do
subject_with_denies.should_not respond_to(:goodnight_moon) subject_with_denies.should_not respond_to(:goodnight_moon)
end end
it "should not clobber other decorators' methods" do it "should not clobber other decorators' methods" do
subject.should respond_to(:hello_world) subject.should respond_to(:hello_world)
end end
it "should not allow method_missing to circumvent a deny" do it "should not allow method_missing to circumvent a deny" do
expect{subject_with_denies.title}.to raise_error(NoMethodError) expect{subject_with_denies.title}.to raise_error(NoMethodError)
end end
end end
describe "a sample usage with allows" do describe "a sample usage with allows" do
let(:subject_with_allows){ DecoratorWithAllows.new(source) } let(:subject_with_allows){ DecoratorWithAllows.new(source) }
it "should echo the allowed method" do it "should echo the allowed method" do
subject_with_allows.should respond_to(:upcase) subject_with_allows.should respond_to(:upcase)
end end
it "should echo _only_ the allowed method" do it "should echo _only_ the allowed method" do
subject_with_allows.should_not respond_to(:downcase) subject_with_allows.should_not respond_to(:downcase)
end end
end end
describe "invalid usages of allows and denies" do describe "invalid usages of allows and denies" do
let(:blank_allows){ let(:blank_allows){
class DecoratorWithInvalidAllows < Draper::Base class DecoratorWithInvalidAllows < Draper::Base
allows allows
end end
} }
let(:blank_denies){ let(:blank_denies){
class DecoratorWithInvalidDenies < Draper::Base class DecoratorWithInvalidDenies < Draper::Base
denies denies
end end
} }
let(:using_allows_then_denies){ let(:using_allows_then_denies){
class DecoratorWithAllowsAndDenies < Draper::Base class DecoratorWithAllowsAndDenies < Draper::Base
allows :hello_world allows :hello_world
denies :goodnight_moon denies :goodnight_moon
end end
} }
let(:using_denies_then_allows){ let(:using_denies_then_allows){
class DecoratorWithDeniesAndAllows < Draper::Base class DecoratorWithDeniesAndAllows < Draper::Base
denies :goodnight_moon denies :goodnight_moon
allows :hello_world allows :hello_world
end end
} }
it "should raise an exception for a blank allows" do it "should raise an exception for a blank allows" do
expect {blank_allows}.should raise_error(ArgumentError) expect {blank_allows}.should raise_error(ArgumentError)
end end
it "should raise an exception for a blank denies" do it "should raise an exception for a blank denies" do
expect {blank_denies}.should raise_error(ArgumentError) expect {blank_denies}.should raise_error(ArgumentError)
end end
it "should raise an exception for calling allows then denies" do it "should raise an exception for calling allows then denies" do
expect {using_allows_then_denies}.should raise_error(ArgumentError) expect {using_allows_then_denies}.should raise_error(ArgumentError)
end end
it "should raise an exception for calling denies then allows" do it "should raise an exception for calling denies then allows" do
expect {using_denies_then_allows}.should raise_error(ArgumentError) expect {using_denies_then_allows}.should raise_error(ArgumentError)
end end
end end
context "in a Rails application" do context "in a Rails application" do
let(:decorator){ DecoratorWithApplicationHelper.decorate(Object.new) } let(:decorator){ DecoratorWithApplicationHelper.decorate(Object.new) }
it "should have access to ApplicationHelper helpers" do it "should have access to ApplicationHelper helpers" do
decorator.uses_hello_world == "Hello, World!" decorator.uses_hello_world == "Hello, World!"
end end
it "should be able to use the content_tag helper" do it "should be able to use the content_tag helper" do
decorator.sample_content.to_s.should == "<span>Hello, World!</span>" decorator.sample_content.to_s.should == "<span>Hello, World!</span>"
end end
it "should be able to use the link_to helper" do it "should be able to use the link_to helper" do
decorator.sample_link.should == "<a href=\"/World\">Hello</a>" decorator.sample_link.should == "<a href=\"/World\">Hello</a>"
end end
it "should be able to use the pluralize helper" do it "should be able to use the pluralize helper" do
decorator.sample_truncate.should == "Once..." decorator.sample_truncate.should == "Once..."
end end
end end
end end