workarea-commerce / rails-decorators Goto Github PK
View Code? Open in Web Editor NEWRails::Decorators provides a clean, familiar API for decorating the behavior of a Rails engine.
License: MIT License
Rails::Decorators provides a clean, familiar API for decorating the behavior of a Rails engine.
License: MIT License
Previously, mixins (defined as modules that get included somewhere)
could not be decorated because of complex load order scenarios that we
didn't want to tackle in a "v1" of this gem. However, this has always
been a bit of a wart in codebases trying to decorate mixed in code, and
for some reason can't keep track of the classes it's being mixed into at
any point in time.
This is a proposal for a change to rails-decorators
that would allow one
to decorate modules. The purpose of this issue is to discuss, openly, the
upsides and downsides to such an approach, and whether it can be done
and/or should be done.
Suppose you want to decorate the Workarea::ByDay
module. Currently,
this is how it would have to be accomplished:
# in app/models/workarea/metrics/by_day.decorator
module Workarea
module Metrics
decorate CategoryByDay, CountryByDay, DiscountByDay, MenuByDay, ProductByDay, SalesByDay, SearchByDay, SegmentByDay, SkuByDay, TenderByDay, TrafficReferrerByDay do
# your code here
end
end
end
This would also suffer from the problem of needing to know all of the
classes that the module has been included into, which can change
depending on the plugin(s) you have installed.
Here's an example of what we need to do in order to reopen a module in order to add methods to it: workarea-commerce/workarea-api@69f68e7
This would be more clear and less error-prone if it could be shortened to the following syntax:
# in app/models/workarea/metrics/by_day.decorator
module Workarea
module Metrics
decorate ByDay do
# your code here
end
end
end
To accomplish this, modules will need to be "tracked" at all times for
which classes they are mixed into. This is similar to
Module#ancestors
, but that collection can also include other modules that
are mixed into the module, so it's not as accurate in determining where
a module needs to be included when it's actually in use. We will need a
new collection on Module
called #mixed_into
that will be appended to
each time the .included
callback is run on a given module (which is
every time it's included in the codebase).
class Module
def mixed_into
@mixed_into ||= []
end
def included(base)
mixed_into << base
super if defined? super
end
end
Given that we have the list of classes the module is included into, we
can easily iterate over and apply decorations to each class that the
module has been included in, simulating a multiple-class decoration as
demonstrated above. To do this, the target is first checked for whether
it is indeed a mixin, and if so, a slightly different decoration path is
taken. In addition to decorating the target, the objects that the target
is mixed into are decorated, which copies the method definitions into
each object separately. On top of all that, the module is extended with
an #append_features
callback that will run the decorations whenever
the module is subsequently included. This is done in case of an
autoloading scenario when the decorator loads before a module is
included.
Here's a rundown of what I believe are the major upsides and downsides
of this approach:
decorate $A_MILLION_CLASSES
iswith:
inGiven the following base code:
module Hammer
def self.cant_touch_this!
"what's the time? hammer #{Time.now}".
end
end
and my decorator:
decorate Hammer do
class_methods do
def cant_touch_this!
"what's the time? hammer time"
end
end
end
I get an error indicating that the Hammer
module cannot be decorated, but the reasoning behind preventing me from doing this seems like it doesn't apply to my use case. Class methods should always be safe to decorate, and the error is only applicable when you try to define instance methods as a part of your decorator.
This causes the same module name, which introduces conflicting modules that don't work. E.g. decorating both Address
and Shipping::Address
which inherits from it. See if we can name the modules in a way to allow this to work without needing to specify uniqueness using the with
option.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.