Giter VIP home page Giter VIP logo

aspector's Introduction

Aspector

Aspector = ASPECT Oriented Ruby programming

Deprecated

Aspector is deprecated.

We recommend Ruby native Module#prepend functionality to build up aspects.

About

Aspector allows to use aspect oriented programming with Ruby.

Aspector allows adding additional behavior to existing code (an advice) without modifying the code itself, instead separately specifying which code is modified via a "pointcut" specification.

Highlights

  • Encapsulate logic as aspects and apply to multiple targets easily
  • Support before/before_filter/after/around advices
  • Work anywhere - inside/outside the target class, before/after methods are created
  • Use regexp matching to apply advices to multiple methods
  • Small codebase, intuitive API
  • Conditional aspects disabling/enabling
  • Standarized logging API
  • Aspects are applicable to both classes/modules and instances
  • Object extensions for easier usage

Example usages

Aspector should be used whenever you have a cross-cutting concerns, especially when they don't perform any business logic. For example use can use it to provide things like:

  • Logging
  • Monitoring
  • Performance benchmarking
  • Any type of transactions wrapping
  • Events producing for systems like Apache Kafka
  • Etc...

Installation

gem install aspector

or put it inside of your Gemfile:

gem 'aspector'

Examples

To see how to use Aspector, please review examples that are in examples directory.

If you need more detailed examples, please review files in spec/functionals and spec/units/advices.

Here's a simple example how Aspector can be used:

class ExampleClass
  def test
    puts 'test'
  end
end

aspect = Aspector do
  target do
    def do_this
      puts 'do_this'
    end
  end

  before :test, :do_this

  before :test do
    puts 'do_that'
  end
end

aspect.apply(ExampleClass)
element = ExampleClass.new
element.test
aspect.disable!
element.test

# Expected output
# do_this
# do_that
# test
# test

Configuration options

Aspector is really easy to use. After installation it doesn't require any additional configuration. You can however set two environments variables that are related to logging:

ENV variable name Description
ASPECTOR_LOGGER Any logger class you want to use (if you don't want to use Aspector standard logger)
ASPECTOR_LOG_LEVEL DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN

Aspector::Logger inherits from a standard Ruby logger. Log levels and the API are pretty standard. You can however use yor own:

ASPECTOR_LOGGER='MyApp::Logger' ASPECTOR_LOG_LEVEL='any log level' ruby aspected_stuff.rb

Default and apply options

Here are options that you can use when creating or applying a single aspect:

Option name Type Description
except Symbol, String, Regexp or Array of those Will apply aspect to all the methods except those listed
name String Advice name (really useful only for debugging)
methods Array of Symbol, String, Regexp Method names (or regexp for matching) to which we should apply given aspect
method Symbol, String, Regexp or Array of those Acts as methods but accepts a single name of method (or a single regexp)
existing_methods_only Boolean (true/false) - default: false Will apply aspect only to already defined methods
new_methods_only Boolean (true/false) - default: false Will apply aspect only to methods that were defined after aspect was applied
private_methods Boolean (true/false) - default: false Should the aspect be applied to private methods as well (public only by default)
class_methods Boolean (true/false) - default: false Should the aspect for instance methods of class methods of a given element
method_arg Boolean (true/false) - default: false Do we want to have access to base method arguments in the aspect method/block
interception_arg Boolean (true/false) - default: false Do we want to have access to the interception instance in the aspect method/block

Contributing to aspector

  • Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
  • Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
  • Fork the project
  • Start a feature/bugfix branch
  • Commit and push until you are happy with your contribution
  • Make sure to add specs for it. This is important so I don't break it in a future version unintentionally.
  • If it is a new functionality or feature please provide examples
  • Please benchmark any functionality that might have a performance impact
  • Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.

Copyright

Copyright (c) 2015 Guoliang Cao, Maciej Mensfeld. See LICENSE.txt for further details.

aspector's People

Contributors

gcao avatar mensfeld avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aspector's Issues

around - wrong number of arguments (2 for 1) (ArgumentError)

When attempting to implement something as simple as the around example, I receive:

wrong number of arguments (2 for 1) (ArgumentError)
/Users/kross/alienfast/cucumber_statistics/lib/cucumber_statistics/autoload.rb:15:in `block (2 levels) in <top (required)>'
/Users/kross/.rvm/gems/ruby-1.9.3-p448@acme/gems/aspector-0.13.1/lib/aspector/base.rb:259:in `block (2 levels) in recreate_method_with_advices'
/Users/kross/.rvm/gems/ruby-1.9.3-p448@acme/gems/aspector-0.13.1/lib/aspector/base.rb:255:in `catch'
/Users/kross/.rvm/gems/ruby-1.9.3-p448@acme/gems/aspector-0.13.1/lib/aspector/base.rb:255:in `block in recreate_method_with_advices'
/Users/kross/.rvm/gems/ruby-1.9.3-p448@acme/gems/cucumber-1.3.10/lib/cucumber/runtime.rb:46:in `run!'
/Users/kross/.rvm/gems/ruby-1.9.3-p448@acme/gems/cucumber-1.3.10/lib/cucumber/cli/main.rb:47:in `execute!'
/Users/kross/.rvm/gems/ruby-1.9.3-p448@acme/gems/cucumber-1.3.10/bin/cucumber:13:in `<top (required)>'

My code:

require 'aspector'

aspector(Cucumber::Cli::Configuration) do
  around :build_tree_walker do |proxy, &block|
    puts 'before(block)'
    proxy.call &block
    puts 'after(block)'
  end
end

:new_methods option

Default to true
If set to false, the aspect will be disabled after #apply

Raw advice for best performance

raw :test do |aspect, method|
  # self => target or context where methods are defined?

  alias_method method, :"#{method}_orig"
  define_method method do |*args, &block|
    send :"#{method}_orig"
  end
end

Doesn't work with overridden methods

let(:parent_klass) { ClassBuilder.build }
let(:aspect) do
  Aspector do
    before :exec do
      values << 'do_before'
    end
  end
end

context 'applying to overridden method' do
  let(:child_klass) do
    ClassBuilder.inherit(parent_klass) do
      def exec(_param = nil)
        values << 'exec-result-child'
      end
    end
  end

  subject { child_klass.new }

  it 'should apply a given code to the method' do
    aspect.apply(parent_klass)
    subject.exec
    expect(subject.values).to eq %w( do_before exec-result-child )
  end
end
expected: ["do_before", "exec-result"]
     got: ["exec-result-child"]

What do you think? Is it valid to expect that applied aspect takes effect on overridden method?

Major code cleanup

Reorganization and cleanup without changing any functionalities

This require a 100% code coverage #21

Aspector does not work with prepended methods

require './lib/aspector'

module Mod
  def test
    super
  end
end

class ARClass
  def test
  end
end

ARClass.prepend Mod

class Hooks < Aspector::Base
  before :test do
  end
end

Hooks.apply(ARClass)

ar = ARClass.new
ar.test
aspector/interception.rb:302:in `call'
aspector/interception.rb:302:in `block in recreate_method_with_advices'
from test.rb:5:in `test'
aspector/interception.rb:302:in `call'
aspector/interception.rb:302:in `block in recreate_method_with_advices'
from test.rb:5:in `test'
aspector/interception.rb:302:in `call'
aspector/interception.rb:302:in `block in recreate_method_with_advices'
 ... 6271 levels...
aspector/interception.rb:302:in `call'
aspector/interception.rb:302:in `block in recreate_method_with_advices'
from test.rb:5:in `test'
from test.rb:25:in `<main>'

warning: Passing trim_mode with the 3rd argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, trim_mode: ...) instead.

Facing warnings
gems/aspector-0.14.0/lib/aspector/interception.rb:229: warning: Passing trim_mode with the 3rd argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, trim_mode: ...) instead.
gems/aspector-0.14.0/lib/aspector/interception.rb:229: warning: Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments.

The syntax used for ERB.new in the interception.rb file is deprecated. The issue persists with the latest release 0.14.0 I see in your master this issue is fixed. Please release a new version of Gem with the fix provided.

Logging support

Add logging support

Logger levels: error, warn, info, debug, trace

Logger level defaults to info
Logger level can be set through environment variable (e.g. ASPECTOR_LOG_LEVEL=warn+TestAspect/debug)
Logger level can be set programatically
Logger level can be set per Aspect class (or even instance?)
After an aspect is applied, changing logger level has no effect
Warn on using raw advices (warnings can be disabled by setting that aspect's logger level to error)

We can save indexes of advices and simply refer them as "advice 1 (BEFORE)"

Log example

Aspector | INFO | TestAspect | define-advice |
Aspector | DEBUG | TestAspect | before-apply-to | A
Aspector | INFO | TestAspect | apply-to | A
Aspector | DEBUG | TestAspect | after-apply-to | A
Aspector | DEBUG | TestAspect | before-apply-to-method | A |
Aspector | DEBUG | TestAspect | apply-to-method | A | |
Aspector | DEBUG | TestAspect | after-apply-to-method | A |
Aspector | DEBUG | TestAspect | invoke-method | A |
Aspector | DEBUG | TestAspect | invoke-advice | A | |

Make logging optional and replaceable

Provide a default logger but make it possible to be replaced.

logger interface

initialize context, options = {}

level

level=

visible? level

log level, *args

log levels:
error - 50
warn - 40
info - 30
debug - 20
trace - 10

Maintenance

Hey, is this gem being maintained? If not I would love to take it over - we use aspector extensively and we would be able to keep it alive.

Deprecate aspector

Hey, I was thinking a lot and I think we should mark this library as unmaintained. Why? Because of prepend that allows you to easily build aspects and apply them.

Of course it would require a bit more work to do it, but still - it seems that neither me nor you have the time to maintain the full set of things in this lib (and there are no pull requests going on so no candidates)

enable/disable/enabled?

aspect can be enabled/disabled. aspect instance can be enabled/disabled independent of the aspect class.

Documentation on DSL keywords and available options?

Hi folks, interesting gem, thanks for sharing. I'm having a hard time understanding how to fully use it tho.

Is there documentation on the domain-specific language keywords?
For example, I've seen default, target, etc.

And for available options?
For example, method, methods, class_methods, private_methods, new_methods_only, except, method_arg, name, etc.

Thanks! ๐Ÿ˜„

prev_aspect, next_aspect

save reference to prev_aspect and next_aspect in current aspect instance
store method reference before applying aspect
disable/enable aspect by swapping method reference and redefine method if this is the last aspect applicable to method

Adding code climate

Once we're done with refactoring, we should add code climate to monitor quality of code.

Create instance of Aspect::Base and apply to multiple targets(classes/instances)

Then aspect instance can be used like a regular class, have instance variable, methods and can be called from advice logic, e.g.

class TestAspect < Aspector::Base
  attr_accessor :call_count

  def initialize
    @call_count = 0
  end

  before aspect_arg: true do |aspect|
    aspect.call_count += 1
  end
end

a = TestAspect.new
a.apply A, method: 'do_a'
a.apply B, methods: %w(do_this do_that)

Is there a way to use aspects as mixins (traits) ?

This gem is great!

Is there a standard way to apply aspects using Mixins?
Normally, when I code I tend to compose objects using traits and I was wondering if this is easily done also with aspector or if its impossible due to the execution model?

Any thoughts?

Thank you for your time and effort!

Is there a way to remove aspects?

Loving this gem. Is there a standard way to remove applied aspects? When I'm testing I want to reset my application to a pure state between tests and I'd like to remove all applied aspects as part of that reset.

Thoughts?

Thanks for your work on this!

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.