GithubHelp home page GithubHelp logo

workarea-commerce / rails-decorators Goto Github PK

View Code? Open in Web Editor NEW
10.0 10.0 9.0 64 KB

Rails::Decorators provides a clean, familiar API for decorating the behavior of a Rails engine.

License: MIT License

Ruby 83.43% Shell 0.30% JavaScript 2.71% CSS 1.70% HTML 11.87%

rails-decorators's People

Contributors

bencrouse avatar eric-pigeon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

rails-decorators's Issues

Proposal: Decorating Modules

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.

Problem

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

Solution

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.

Evaluation

Here's a rundown of what I believe are the major upsides and downsides
of this approach:

Benefits

  • Shorter, terser syntax: Writing decorate $A_MILLION_CLASSES is
    hard to read and hard to maintain over time.
  • Future-proof code: Since you no longer have to worry about which
    exact objects the module is being mixed into, you don't have to keep
    that decorator up to date if other classes in the ecosystem use the
    module.
  • A clear pattern: This is clearly something people want, as
    evidenced by many examples over the years of long multi-decoration
    lines in lieu of being able to do this. This also did work at one
    point, and in my opinion, it just needed a bit more work to get the
    feature over the hill.
  • Backwards compatible: The changes proposed here will not be
    breaking, decorating modules should not interfere with the existing
    way we decorate classes.

Drawbacks

  • Unknown side effects: It's still possible to get into load-order
    issues if decorators are not named appropriately using with: in
    plugins. Applications may still use un-named decorators, however.
  • Rails helpers: In theory, this approach should also work for
    helper modules, but I haven't proven that yet.
  • Thread Safety: I don't have any way of proving thread-safety, but
    I'm not entirely sure this will be thread-safe in a development
    environment. Since production environments preload all the code, I'm
    pretty sure it won't be an issue in the "real world", but it's
    something to consider.

Decorating class methods defined within a module

Given 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.

Conflicting modules when decorating base and subclasses

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.