Raise UninferrableSourceErrors

This commit is contained in:
Andrew Haines 2012-12-02 12:20:42 +00:00
parent 6736fd7d87
commit 8ef5bf2f02
3 changed files with 61 additions and 48 deletions

View File

@ -48,4 +48,10 @@ module Draper
super("Could not infer a decorator for #{klass}.")
end
end
class UninferrableSourceError < NameError
def initialize(klass)
super("Could not infer a source for #{klass}.")
end
end
end

View File

@ -34,27 +34,25 @@ module Draper
alias_method :decorate, :new
end
UNKNOWN_SOURCE_ERROR_MESSAGE = "Cannot find source_class for %s. Use `decorates` to specify the source_class."
# Specify the class that this class decorates.
#
# @param [String, Symbol, Class] Class or name of class to decorate.
def self.decorates(klass)
@source_class = klass.kind_of?(Class) ? klass : klass.to_s.classify.constantize
@source_class = klass.to_s.classify.constantize
end
# Provides access to the class that this decorator decorates
#
# @return [Class]: The class wrapped by the decorator
# @return [Class] The source class corresponding to this
# decorator class
def self.source_class
@source_class ||= begin
raise NameError.new(UNKNOWN_SOURCE_ERROR_MESSAGE % "<unnamed decorator>") if name.nil? or name.chomp("Decorator").blank?
begin
name.chomp("Decorator").constantize
rescue NameError
raise NameError.new(UNKNOWN_SOURCE_ERROR_MESSAGE % "`#{name.chomp("Decorator")}`")
end
end
@source_class ||= inferred_source_class
end
# Checks whether this decorator class has a corresponding
# source class
def self.source_class?
source_class
rescue Draper::UninferrableSourceError
false
end
# Automatically decorates ActiveRecord finder methods, so that
@ -184,7 +182,7 @@ module Draper
end
def method_missing(method, *args, &block)
if allow?(method) && source.respond_to?(method)
if delegatable_method?(method)
self.class.define_proxy(method)
send(method, *args, &block)
else
@ -193,32 +191,41 @@ module Draper
end
def respond_to?(method, include_private = false)
super || (allow?(method) && source.respond_to?(method, include_private))
super || delegatable_method?(method)
end
def self.method_missing(method, *args, &block)
# We see if we have a valid source here rather than in the #send since we don't want to accidentally capture
# NameError raised by a method called on a valid #source_class
begin
source_class
rescue NameError
return super
if delegatable_method?(method)
source_class.send(method, *args, &block)
else
super
end
# If we pass the source_class invocation, then we can send the method on to the decorated class.
source_class.send(method, *args, &block)
end
def self.respond_to?(method, include_private = false)
super || begin
source_class.respond_to?(method)
rescue NameError
false
end
super || delegatable_method?(method)
end
private
def delegatable_method?(method)
allow?(method) && source.respond_to?(method)
end
def self.delegatable_method?(method)
source_class? && source_class.respond_to?(method)
end
def self.inferred_source_class
raise Draper::UninferrableSourceError.new(self) if name.nil? || name.chomp("Decorator").empty?
begin
name.chomp("Decorator").constantize
rescue NameError
raise Draper::UninferrableSourceError.new(self)
end
end
def self.define_proxy(method)
define_method(method) do |*args, &block|
source.send(method, *args, &block)

View File

@ -471,55 +471,55 @@ describe Draper::Decorator do
end
end
describe "#respond_to?" do
describe ".respond_to?" do
context "when using a decorator that cannot infer an underlying model" do
subject { Class.new(Draper::Decorator) }
it "should not throw an exception during respond_to? due to an inability to find the inferred decorated class" do
expect { subject.respond_to?(:fizzbuzz) }.to_not raise_error(NameError)
expect { subject.respond_to?(:fizzbuzz) }.not_to raise_error
end
it "should return false for methods that it can't find" do
subject.respond_to?(:fizzbuzz).should == false
subject.respond_to?(:fizzbuzz).should be_false
end
it "should return true for methods that it can find" do
subject.respond_to?(:denies_all).should == true
subject.respond_to?(:denies_all).should be_true
end
end
end
describe "#source_class" do
describe ".source_class" do
context "when called on an anonymous decorator" do
subject { -> { Class.new(Draper::Decorator).source_class } }
it { should raise_error(NameError, /Use `decorates` to specify the source_class/) }
subject { ->{ Class.new(Draper::Decorator).source_class } }
it { should raise_error Draper::UninferrableSourceError }
end
context "when called on a decorator that can't infer the class name" do
subject { -> { SpecificProductDecorator.source_class } }
it { should raise_error(NameError, /Use `decorates` to specify the source_class/) }
subject { ->{ SpecificProductDecorator.source_class } }
it { should raise_error Draper::UninferrableSourceError }
end
end
describe "#method_missing" do
describe ".method_missing" do
context "when called on an anonymous decorator" do
subject { lambda { Class.new(Draper::Decorator).fizzbuzz } }
it { should raise_error(NoMethodError) }
subject { ->{ Class.new(Draper::Decorator).fizzbuzz } }
it { should raise_error NoMethodError }
end
context "when called on an uninferrable decorator" do
subject { lambda { SpecificProductDecorator.fizzbuzz } }
it { should raise_error(NoMethodError) }
subject { ->{ SpecificProductDecorator.fizzbuzz } }
it { should raise_error NoMethodError }
end
context "when called on an inferrable decorator" do
context "for a method known to the inferred class" do
subject { lambda { ProductDecorator.model_name } }
it { should_not raise_error(NoMethodError) }
subject { ->{ ProductDecorator.model_name } }
it { should_not raise_error }
end
context "for a method unknown to the inferred class" do
subject { lambda { ProductDecorator.fizzbuzz } }
it { should raise_error(NoMethodError) }
subject { ->{ ProductDecorator.fizzbuzz } }
it { should raise_error NoMethodError }
end
end
end