draper
draper copied to clipboard
How to decorate class methods?
Is there a preferred / recommended way to do that with (or without) draper?
I wanted to add some view-related class methods to all models without touching their definitions in app/models
. So I attempted to add such methods to ActiveRecord::BaseDecorator
and call them through decorator_class
. However, it didn't work as expected for models without decorator definitions:
class Foo < ActiveRecord::Base
end
class Bar < ActiveRecord::Base
end
class ActiveRecord::BaseDecorator < Draper::Decorator
def self.sorted_column_names
cols = object_class.column_names.sort
%w(id).each do |c|
cols.unshift(c) if cols.delete(c)
end
%w(created_at updated_at).each do |c|
cols.push(c) if cols.delete(c)
end
cols
end
def common_instance_method
# ....
end
end
class FooDecorator < ActiveRecord::BaseDecorator
def common_instance_method
# Override common_instance_method for Foo
end
end
irb(main):001:0> Foo.decorator_class
=> FooDecorator
irb(main):002:0> Foo.decorator_class.sorted_column_names
=> ["id", "foo_column", "created_at", "updated_at"]
irb(main):003:0> Bar.decorator_class
=> ActiveRecord::BaseDecorator
irb(main):004:0> Bar.decorator_class.sorted_column_names
NoMethodError: undefined method `abstract_class?' for Object:Class
...
I'd like to avoid defining decorator classes for all models explicitly.
I've written a monkey patch in config/initializers/draper_patch.rb
:
module Draper
module Decoratable
module ClassMethods
def decorator_class
prefix = respond_to?(:model_name) ? model_name : name
decorator_name = "#{prefix}Decorator"
decorator_name.constantize
rescue NameError => error
if superclass.respond_to?(:decorator_class)
klass = Class.new(superclass.decorator_class)
Object.const_set(decorator_name, klass)
else
raise unless error.missing_name?(decorator_name)
raise Draper::UninferrableDecoratorError.new(self)
end
end
end
end
end
It creates a derived decorator class on the fly when model has no decorator class with the inferred name (BarDecorator
), but its superclass has one (ActiveRecord::BaseDecorator
):
irb(main):001:0> BarDecorator
NameError: uninitialized constant BarDecorator
...
irb(main):002:0> Bar.decorator_class
=> BarDecorator
irb(main):003:0> Bar.decorator_class.sorted_column_names
=> ["id", "bar_column", "created_at", "updated_at"]
irb(main):004:0> Bar.decorator_class.superclass
=> ActiveRecord::BaseDecorator
Could you support this as the standard feature of draper?