Write active decorator DRY
Closed this issue · 5 comments
On writing my UserDecorator
, I find it overlap a lot. Just decorate the datetime
but I have to repeat writing decorator a lot.
# frozen_string_literal: true
module UserDecorator
def created_at_datetime
created_at&.strftime '%Y/%m/%m %H:%M:%S'
end
def confirmed_at_datetime
confirmed_at&.strftime '%Y/%m/%m %H:%M:%S'
end
def locked_at_datetime
locked_at&.strftime '%Y/%m/%m %H:%M:%S'
end
def current_sign_in_at_datetime
current_sign_in_at&.strftime '%Y/%m/%m %H:%M:%S'
end
def last_sign_in_at_datetime
last_sign_in_at&.strftime '%Y/%m/%m %H:%M:%S'
end
end
And guess what, on my AdminDecorator
I have the exact same fields. Do I have to copy all of these again to AdminDecorator?
Any advice guys?
I could use some meta code like these but I can't solve the exact same fields at AdminDecorator
:
module UserDecorator
DATETIME_DECORATOR_FIELDS = %w[created_at confirmed_at locked_at current_sign_in last_sign_in_at].freeze
DATETIME_DECORATOR_FIELDS.each do |field|
define_method("#{field}_datetime") do |format = nil|
return send(field)&.strftime(format) if format
send(field)&.strftime '%Y/%m/%m %H:%M:%S'
end
end
end
@truongnmt Decorators are basically just plain Ruby modules. And active_decorator itself doesn't provide any special utility for extending the application code.
So, your question is actually a general Ruby/Rails question that should not be posted here.
Anyway, as you answered yourself, using basic Ruby metaprogramming technique would effectively reduce your code.
Another solution I could come up with is to extend Active Record (Active Model) attribute API.
In this case, simply overriding _read_attribute
in your decorator like the following would do the magic (but please don't forget to leave a detailed comment for your teammates!).
def _read_attribute(attr_name, &block)
if @attributes[attr_name].type.type == :datetime
super&.strftime '%Y/%m/%m %H:%M:%S'
else
super
end
end
Nice idea! Thanks a lot!
By the way, I just ask at SO, so I gonna share here:
https://stackoverflow.com/questions/56844902/write-active-decorator-dry
@amatsuda Sorry for bring this issue back but I have a question.
I'm writing method_missing
for above problem, basically I write in DatetimeDecorator and another decorator just extend that file:
module DatetimeDecorator
DEFAULT_FORMAT = '%Y/%m/%d %H:%M:%S'
def method_missing(method, *args, &block)
attribute = method.to_s.sub(/_datetime\z/, '')
super unless respond_to?(attribute)
public_send(attribute).strftime(args[0] || DEFAULT_FORMAT)
end
def respond_to_missing?(method, include_private = false)
attribute = method.to_s.sub(/_datetime\z/, '')
respond_to?(attribute) || super
end
end
module UserDecorator
extend DatetimeDecorator
end
module AdminDecorator
extend DatetimeDecorator
end
Usage is: user.created_at_datetime
However on calling above, it's not run into my method_missing
, instead it jump into ActiveDecorator::Helpers#method_missing
🤔
Oh actually not extend
, include
is the correct way to do. However I don't know why yet @@