* Let decorator_class infer anonymous class' decorator from superclass
* Replace constantize with safe_constantize on decorator's private methods
* Don't raise NameError on decorator private methods.
To prevent rescuing and checking if NameError is from internal decorator
private methods, we change to return nil on these private methods.
* Revise decorator inferred_object_class and collection_decorator_name
based on code review comments
Draper::Decorater provides the object method and we should delegate === to this
method only for these types.
Other classes could respond_to?(:object) so using is_a? clarifies not just any
class implementing object but only classes that are a Draper::Decorator.
Besides this, another major reason is is_a? is much faster than respond_to?
in ActiveRecord models because of the number of methods AR provides and the
layers of delegation. There is a cache specifically for respond_to? [1] because
it's so slow.
Even with this cache, some of our pages are 10% faster by using the is_a? change
from this patch.
The original implementation [2] used the method source, which was later renamed
to object. The tests seem to indicate the intention was to always have
this === delegation to source/object for decorated objects... which is what
Drapper::Decorator types are.
Test changes:
* verify decorated instances of Product and instances derived from
decorated instances of Product are === Product.
* verify unrelated decorated instances and non-decorators that
happen to implement object are not === Product.
[1] https://github.com/rails/rails/blob/4-2-stable/activemodel/lib/active_model/attribute_methods.rb#L343-L351
[2] ca116154f3
The previous behavior swallowed genuine NameErrors (as opposed to
failure to find the inferred class), for example when the user has
not updated the decorator base class from Draper::Base.
To use different decorators in different contexts (e.g. HTML rendering
vs. API representation), the ability to specify a decorator namespace is
added. For example:
> Product.new.decorate
=> #<ProductDecorator:0x0000010b6e47e8>
> Product.new.decorate(:namespace => HTML)
=> #<HTML::ProductDecorator:0x0000010b6e47e9>
In 30104b0 I had accidentally nuked the options but didn't realise
as there was no spec coverage.
I then discovered that, due to memoization, `#decorate` did not
use options after the first call:
product.decorate(role: :admin)
product.decorate(role: :user) # still has admin role!
I ditched the `#decorator` alias since `#decorate` seems far more
canonical, and dropped the block passing syntax, since this seems
orthogonal to the main purpose of the method.