14 Jun 2010

Aspect4r Usage and Public API

Since most features I planned have been implemented, now is the time to document Aspect4r's public facing APIs.

Installation

gem install aspect4r

Usage

require 'aspect4r'

Add 'include Aspect4r' to class or module where advices will be defined.

API

When Aspect4r is included, four singleton methods are available for defining advices on target class/module: before, before_filter, after, around. They have same method signature below and process arguments similarly.

advice_type(methods, options={}, &advice_block)

  • methods: a list of methods that will be advised. If no block is given, then the last method contains the advice logic and is applied to other methods. For example, 'before :test1, :test2, :do_something' means run do_something before test1 and test2.
  • options [optional]: All advices can take method_name_arg and name option. Each type of advice can potentially take options only applicable to that type. Those options will be covered later in this document. If method_name_arg is true, then the advice method or block's first argument is the target method name so that advice logic can be customized for different methods, e.g.

before :test1, :test2, :method_name_arg => true do |method, *args|
    if method == 'test1'
    else
    end
end

name option is used to uniquely identifiy an advice on a method. A method can not have 2 advices with same name. Advice method name is used as advice name if name option is not set. Example:

before :test, :name => 'preprocess' do
end

  • advice_block [optional]: contains advice logic. If it is missing, methods should contain at least 2 methods, last one contains the advice logic.

before(methods, options={}, &advice_block)

Define an advice which runs before target methods. Currently there is no special option can be used for this type of device.

Normally, before advice will not halt execution of advices defined later and the wrapped method. However, there is a special wrapper class Aspect4r::ReturnThis. When the result of a before advice is an instance of ReturnThis, execution will stop and the result wrapped inside will be returned, e.g.

before :test do |input|
    return Aspect4r::ReturnThis.new('default') if input.nil?
end
instance.test(nil)   # => 'default'

before_filter(methods, options={}, &advice_block)

Define an advice which runs before target methods and if this advice returns false or nil, execution will halt. This is a special case of before advice. Currently there is no special option can be used for this type of device. Example:

before_filter :test do |input|
    input >= 0
end

after(methods, options={}, &advice_block)

Define an advice which runs after target methods. After advice can take a result_arg option which is set to true by default. When it is true, the result of wrapped method or previous after advice is passed as the first argument to the advice, also the result of the after advice will be used as the method result. When result_arg is set to false, result will not be passed into advice method, and the advice result will not be used as the method result. Example

after :test do |result, input|
    "<b>" + result + "</b>"    # => new result
end
after :test, :result_arg => false do |input|
    cleanup                           # will not change what test will return
end

around(methods, options={}, &advice_block)

Define an advice which runs around target methods. A proxy object which represents the wrapped method (and advices if any) is passed as the first argument to the advice method or block. The advice logic can decide whether and when the proxy is invoked. A special method a4r_invoke is provided to invoke the proxy object. Currently there is no special option can be used for this type of device. Example:

around :test do |proxy, input|
    return if input.nil?
    a4r_invoke proxy, input
end

Aspect4r::Classic

Because before, before_filter and after are common method names used by several Ruby libraries/frameworks (e.g. Rails, RSpec), to avoid confliction, Aspect4r provides a different set of methods (a4r_before, a4r_before_filter, a4r_after, a4r_around) with same functionalities. Here is how you use those instead.

class A
    include Aspect4r::Classic
    a4r_before :test do
    end
    a4r_after :test do
    end
end

Test

See here for a example. I'll add more detail here later.

With above explanation and the advice execution model detailed in my other post, plus examples in the code base, you should be able to grasp the idea and use Aspect4r to write better organized code. Any comments / suggestions to the project and my posts are very welcome.

13 Jun 2010

Aspect4r documentation: advice execution model

Aspect4r provides 4 methods to attach additional logic to a method: before, before_filter, after, around. As their names suggest, they are executed before or after or around the wrapped method. It is easy to understand when there is only one advice involved. When there are multiple advices (especially around advices) the execution order gets a little complicated. Below are the rules Aspect4r uses to determine the execution order.

When all advices' type are the same:

  • before/before_filter advices follow the order of definition. Before advices defined eariler runs before advices defined later. The first before advice runs first.
  • after advices follow the order of definition. After advices defined earlier runs before those defined later. The last after advice runs last.
  • around advices defined later wraps advices defined earlier.

When advices are mixed:

  • When there is no around advice involved, before and after advices won't interfere with each other because of their nature.
  • When around advices are mixed with before/after advices, around advice wraps ALL advices before it. This will change execution order of before advices defined before and after the around advice. Thus before advices defined before around advice run AFTER before advices defined after it.

Advice group:

  • a4r_group method is used to split all advices of a method into groups. Advices defined before a group are applied before any advice defined in that group. From the group's point of view, advices defined before it and the wrapped method can be considered blended together as the new inner method. This is useful when you separate advices into a sperate source file and do not want to interfere with any advices defined previously.

Let's see a few examples here.

Example 1(before + around + before advice):

Example 2(around + before + after + around advice):

Example 2(advice group):

10 Jun 2010

Aspect4r 0.8.1 is released

Aspect4r 0.8.1 is out. Feel free to give it a try and let me know your thoughts. Here are changes worth mentioning:

  • Define advices for singleton methods (also known as class methods)
  • Test advices and test barebone method without advices
  • Group advices (list of advices applied to one method is splitted into groups based on advices' group. This warrants a separate post)

Advices on singleton method example (same as advices on instance methods except you have to put the code inside "class << self" block)

Test advice example (complete example can be found here)

Before I blog more on this and document the API, please find a lot nice and working examples here: http://github.com/gcao/aspect4r/tree/master/examples

9 Jun 2010

AlignText.tmbundle

To scratch my own itch, I extended the Align Source command to take a pattern from input box and align text in any way I like. For example

Align

With input "\]   if", above code will be formatted to below

Align1

The bundle is located at  http://github.com/gcao/AlignText.tmbundle

The shortcut for align by pattern is Command+Alt+Ctrl+]. It only works for text selection.

Multiple patterns are separated by three spaces. If the first pattern starts with a space, it will not align by first pattern.

Remember to escape special characters like []()\.* etc with a preceding '\', otherwise selected text will be replaced with an error message. Do not panic though, just press Command+Z to undo and Command+Alt+Ctrl+] again to enter the correct pattern to format it.

Hope you find it useful. Feel free to clone and modify for your needs.

8 Jun 2010

Aspect4r 0.7.1 is released

This is the first blog about Aspect4r, a Ruby gem I have been working on.

Aspect4r adds a solid method wrapper to Ruby, makes it easy to do Aspect Oriented Programming with help of Ruby's powerful meta-programming functionality.

Install

gem install aspect4r

Usage

When included, aspect4r adds several class methods to the module/class: before/before_filter, after, around. If you have some common logic that spreads across several methods, you can extract them into advices and test separately. Even if some logic is not shared across, just to make your code clearer, you could retain the core logic in the method and move parameter validation, logging etc into advices.

Typical usage

Each advice can take a method name instead of a block

Advice can be customized to take optional method name argument (if advice logic partly depends on target method)

Use classic API methods to avoid method collision

Note

Advices are local to the class/module that defines them. They should be considered as part of target method implementation. To test advices, one could just test target methods. However, my goal is to make it easy to test advices. This part is not finalized yet. Any suggestions are welcome.

To do Aspect Oriented Programming, one needs to work on a higher level than being limited to one class/module. Means it should be possible to define advices in one module and apply those advices to a collection of classes/modules. I like the way that current implementation just works and is simple to understand. If we want to do the higher level AOP, something has to be sacrificed. This can probably be deferred until we see how well current approach works in practice.

Aspect4r is the first gem that I wrote and release to public. Any suggestions or comments are very welcome.

Other AOP gems/resources

Cuts: http://github.com/rubyworks/cuts

Cut-based AOP: http://github.com/rubyworks/cuts/blob/master/RCR.textile

Aquarium: http://aquarium.rubyforge.org/

A short example posted on StackOverflow: http://stackoverflow.com/questions/2135874/cross-cutting-logging-in-ruby

Credit

Trans: http://github.com/trans

12 Apr 2010

My first blog

There is not much to see today, come back soon :-)

Guoliang Cao's Space

Programming can be fun - Ruby proved that.