├── var ├── title ├── name ├── created ├── version ├── organization ├── summary ├── repositories ├── authors ├── description ├── requirements ├── copyrights └── resources ├── lib ├── cuts.rb └── cuts │ ├── cut.rb │ └── aop.rb ├── work ├── deprecated │ ├── meta │ │ ├── license │ │ ├── name │ │ ├── version │ │ ├── created │ │ ├── collection │ │ ├── summary │ │ ├── contact │ │ ├── homepage │ │ ├── repository │ │ └── description │ ├── images │ │ ├── book.jpg │ │ ├── rgem.png │ │ ├── ruby.gif │ │ ├── source.png │ │ ├── license.png │ │ ├── menu_bg.jpg │ │ ├── menu_bkg.png │ │ ├── scissors.jpg │ │ ├── development.png │ │ └── download_arrow.gif │ ├── adverts │ │ ├── rdoc.html │ │ ├── tiger.html │ │ ├── amazon.html │ │ └── google.html │ ├── test_ccut.rb │ └── ccut.rb ├── sandbox │ ├── test_cut.rb │ ├── mike.rb │ ├── test_cut_join.rb │ ├── cuts3.rb │ └── cut.rb ├── consider │ └── test_cut_join.rb └── CUT-AOP.txt ├── Gemfile ├── .gitignore ├── .travis.yml ├── Rakefile ├── MANIFEST ├── Assembly ├── try └── aop.rb ├── test ├── test_aop.rb └── test_cut.rb ├── .ruby ├── HISTORY.rdoc ├── NOTICE.rdoc ├── README.rdoc ├── .gemspec └── RCR.textile /var/title: -------------------------------------------------------------------------------- 1 | Cuts -------------------------------------------------------------------------------- /var/name: -------------------------------------------------------------------------------- 1 | cuts 2 | -------------------------------------------------------------------------------- /var/created: -------------------------------------------------------------------------------- 1 | 2008-02-12 -------------------------------------------------------------------------------- /var/version: -------------------------------------------------------------------------------- 1 | 1.1.0 2 | -------------------------------------------------------------------------------- /var/organization: -------------------------------------------------------------------------------- 1 | Rubyworks 2 | -------------------------------------------------------------------------------- /var/summary: -------------------------------------------------------------------------------- 1 | Cut-based AOP for Ruby -------------------------------------------------------------------------------- /lib/cuts.rb: -------------------------------------------------------------------------------- 1 | require 'cuts/aop.rb' 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/license: -------------------------------------------------------------------------------- 1 | MIT 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/name: -------------------------------------------------------------------------------- 1 | cuts 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/version: -------------------------------------------------------------------------------- 1 | 1.0.0 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec 3 | -------------------------------------------------------------------------------- /work/deprecated/meta/created: -------------------------------------------------------------------------------- 1 | 2008-02-12 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/collection: -------------------------------------------------------------------------------- 1 | rubyworks 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/summary: -------------------------------------------------------------------------------- 1 | Cut-based AOP for Ruby 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/contact: -------------------------------------------------------------------------------- 1 | tigerops-community@rubyforge.org 2 | -------------------------------------------------------------------------------- /work/deprecated/meta/homepage: -------------------------------------------------------------------------------- 1 | http://rubyworks.github.com/cuts 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .reap/digest 2 | .yardoc 3 | log 4 | pkg 5 | tmp 6 | web 7 | -------------------------------------------------------------------------------- /work/deprecated/meta/repository: -------------------------------------------------------------------------------- 1 | git://github.com/rubyworks/cuts.git 2 | -------------------------------------------------------------------------------- /var/repositories: -------------------------------------------------------------------------------- 1 | --- 2 | upstream: git://github.com/rubyworks/cuts.git 3 | -------------------------------------------------------------------------------- /var/authors: -------------------------------------------------------------------------------- 1 | --- 2 | - Thomas Sawyer 3 | - Peter Vanbroekhoven 4 | -------------------------------------------------------------------------------- /var/description: -------------------------------------------------------------------------------- 1 | Cuts is an expiremental implementation of cut-based AOP for Ruby written in pure Ruby. -------------------------------------------------------------------------------- /var/requirements: -------------------------------------------------------------------------------- 1 | --- 2 | - detroit (build) 3 | - microtest (test) 4 | - ae (test) 5 | 6 | -------------------------------------------------------------------------------- /work/deprecated/images/book.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyworks/cuts/master/work/deprecated/images/book.jpg -------------------------------------------------------------------------------- /work/deprecated/images/rgem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyworks/cuts/master/work/deprecated/images/rgem.png -------------------------------------------------------------------------------- /work/deprecated/images/ruby.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyworks/cuts/master/work/deprecated/images/ruby.gif -------------------------------------------------------------------------------- /work/deprecated/images/source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyworks/cuts/master/work/deprecated/images/source.png -------------------------------------------------------------------------------- /var/copyrights: -------------------------------------------------------------------------------- 1 | --- 2 | - (c) 2006 Thomas Sawyer (BSD-2-Clause) 3 | - (c) 2006 Peter Vanbroekhoven (BSD-2-Clause) 4 | 5 | -------------------------------------------------------------------------------- /work/deprecated/images/license.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyworks/cuts/master/work/deprecated/images/license.png -------------------------------------------------------------------------------- /work/deprecated/images/menu_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyworks/cuts/master/work/deprecated/images/menu_bg.jpg -------------------------------------------------------------------------------- /work/deprecated/images/menu_bkg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyworks/cuts/master/work/deprecated/images/menu_bkg.png -------------------------------------------------------------------------------- /work/deprecated/images/scissors.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyworks/cuts/master/work/deprecated/images/scissors.jpg -------------------------------------------------------------------------------- /work/deprecated/images/development.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyworks/cuts/master/work/deprecated/images/development.png -------------------------------------------------------------------------------- /work/deprecated/meta/description: -------------------------------------------------------------------------------- 1 | Cuts is an expiremental implementation of cut-based 2 | AOP for Ruby written in pure Ruby. 3 | 4 | -------------------------------------------------------------------------------- /work/deprecated/images/download_arrow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyworks/cuts/master/work/deprecated/images/download_arrow.gif -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | rvm: 3 | - 1.8.7 4 | - 1.9.2 5 | - 1.9.3 6 | - rbx-2.0 7 | - jruby 8 | - ree 9 | script: "bundle exec ruby-test test/" 10 | 11 | -------------------------------------------------------------------------------- /var/resources: -------------------------------------------------------------------------------- 1 | --- 2 | home: http://rubyworks.github.com/cuts 3 | code: http://github.com/rubyworks/cuts 4 | mail: http://groups.google.com/groups/rubyworks-mailinglist 5 | 6 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | 2 | desc "run tests" 3 | 4 | task :test do 5 | $LOAD_PATH.unshift './lib' 6 | 7 | Dir['test/*'].each do |testfile| 8 | load testfile 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | #!mast .ruby .yardopts bin lib man spec test [A-Z]*.* 2 | .ruby 3 | lib/cuts/aop.rb 4 | lib/cuts/cut.rb 5 | lib/cuts.rb 6 | test/test_aop.rb 7 | test/test_cut.rb 8 | HISTORY.rdoc 9 | README.rdoc 10 | NOTICE.rdoc 11 | RCR.textile 12 | -------------------------------------------------------------------------------- /Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | gem: 3 | active : true 4 | 5 | github: 6 | gh_pages: web 7 | 8 | dnote: 9 | title: Developer's Notes 10 | labels: ~ 11 | output: log/NOTES.rdoc 12 | 13 | email: 14 | mailto: 15 | - ruby-talk@ruby-lang.org 16 | - rubyworks-mailinglist@googlegroups.com 17 | 18 | -------------------------------------------------------------------------------- /work/deprecated/adverts/rdoc.html: -------------------------------------------------------------------------------- 1 |
2 | 9 | 12 |
13 | -------------------------------------------------------------------------------- /work/deprecated/adverts/tiger.html: -------------------------------------------------------------------------------- 1 |
2 | 9 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /try/aop.rb: -------------------------------------------------------------------------------- 1 | require 'cuts/aop' 2 | 3 | class X 4 | def x; "x"; end 5 | def y; "y"; end 6 | def q; "<" + x + ">"; end 7 | end 8 | 9 | Xa = Aspect.new do 10 | join :x do |jp| 11 | jp == :x 12 | end 13 | 14 | def x(target); '{' + target.super + '}'; end 15 | end 16 | 17 | X.apply(Xa) 18 | 19 | 20 | @x1 = X.new 21 | 22 | @x1.class.assert == X 23 | 24 | meths = @x1.public_methods(false) 25 | meths.assert.include?("y") 26 | meths.assert.include?("q") 27 | meths.assert.include?("x") 28 | 29 | @x1.x.assert == "{x}" 30 | 31 | @x1.y.assert == "y" 32 | 33 | @x1.q.assert == "<{x}>" 34 | 35 | -------------------------------------------------------------------------------- /work/deprecated/adverts/amazon.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cuts</tile> 4 | <meta name="description" content="Cuts is cut-base AOP for Ruby"/> 5 | <meta name="keywords" content="Ruby, Cuts, AOP, Cut, Cut-based AOP, programming, agile"/> 6 | <style> 7 | body { border: 0; padding: 0; margin: 0; } 8 | .info { font-size: 0px; color: white; margin 0; padding: 0; } 9 | </style> 10 | </head> 11 | <body> 12 | 13 | <p class="info"> 14 | Cuts is cut-based AOP for the Ruby porgramming language. 15 | </p> 16 | 17 | <iframe src="http://rcm.amazon.com/e/cm?t=psytowerinfo-20&o=1&p=14&l=st1&mode=books&search=ruby&fc1=000000<1=&lc1=3366FF&bg1=FFFFFF&f=ifr" marginwidth="0" marginheight="0" width="160" height="600" border="0" frameborder="0" style="border:none;" scrolling="no"></iframe> 18 | 19 | </body> 20 | </html> 21 | 22 | -------------------------------------------------------------------------------- /work/deprecated/adverts/google.html: -------------------------------------------------------------------------------- 1 | <html> 2 | <head> 3 | <title>Cuts</tile> 4 | <meta name="description" content="Cuts is cut-base AOP for Ruby"/> 5 | <meta name="keywords" content="Ruby, Cuts, AOP, Cut, Cut-based AOP, programming, agile"/> 6 | <style> 7 | body { border: 0; padding: 0; margin: 0; } 8 | .info { font-size: 0px; color: white; margin 0; padding: 0; } 9 | </style> 10 | </head> 11 | <body> 12 | 13 | <p class="info"> 14 | Cuts is cut-based AOP for the Ruby porgramming language. 15 | </p> 16 | 17 | <script type="text/javascript"><!-- 18 | google_ad_client = "pub-1126154564663472"; 19 | /* TIGER SIDE 160x600 */ 20 | google_ad_slot = "7732254054"; 21 | google_ad_width = 160; 22 | google_ad_height = 600; 23 | //--> 24 | </script> 25 | <script type="text/javascript" 26 | src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> 27 | </script> 28 | 29 | </body> 30 | </html> 31 | 32 | -------------------------------------------------------------------------------- /test/test_aop.rb: -------------------------------------------------------------------------------- 1 | require 'microtest' 2 | require 'ae' 3 | require 'cuts' 4 | 5 | class AOPTest < MicroTest::TestCase 6 | 7 | class X 8 | def x; "x"; end 9 | def y; "y"; end 10 | def q; "<" + x + ">"; end 11 | end 12 | 13 | Xa = Aspect.new do 14 | join :x do |jp| 15 | jp == :x 16 | end 17 | 18 | def x(target); '{' + target.super + '}'; end 19 | end 20 | 21 | X.apply(Xa) 22 | 23 | def setup 24 | @x1 = X.new 25 | end 26 | 27 | def test_class 28 | @x1.class.assert == X 29 | end 30 | 31 | def test_public_methods 32 | meths = @x1.public_methods(false) 33 | meths = meths.map{ |m| m.to_s } 34 | meths.assert.include?("y") 35 | meths.assert.include?("q") 36 | meths.assert.include?("x") 37 | end 38 | 39 | def test_x 40 | @x1.x.assert == "{x}" 41 | end 42 | 43 | def test_y 44 | @x1.y.assert == "y" 45 | end 46 | 47 | def test_q 48 | @x1.q.assert == "<{x}>" 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /.ruby: -------------------------------------------------------------------------------- 1 | --- 2 | source: 3 | - var 4 | authors: 5 | - name: Thomas Sawyer 6 | email: transfire@gmail.com 7 | - name: Peter Vanbroekhoven 8 | copyrights: 9 | - holder: Thomas Sawyer 10 | year: '2006' 11 | license: BSD-2-Clause 12 | - holder: Peter Vanbroekhoven 13 | year: '2006' 14 | license: BSD-2-Clause 15 | replacements: [] 16 | alternatives: [] 17 | requirements: 18 | - name: detroit 19 | groups: 20 | - build 21 | development: true 22 | - name: microtest 23 | groups: 24 | - test 25 | development: true 26 | - name: ae 27 | groups: 28 | - test 29 | development: true 30 | dependencies: [] 31 | conflicts: [] 32 | repositories: 33 | - uri: git://github.com/rubyworks/cuts.git 34 | scm: git 35 | name: upstream 36 | resources: 37 | home: http://rubyworks.github.com/cuts 38 | code: http://github.com/rubyworks/cuts 39 | mail: http://groups.google.com/groups/rubyworks-mailinglist 40 | extra: {} 41 | load_path: 42 | - lib 43 | revision: 0 44 | created: '2008-02-12' 45 | summary: Cut-based AOP for Ruby 46 | title: Cuts 47 | version: 1.1.0 48 | name: cuts 49 | description: Cuts is an expiremental implementation of cut-based AOP for Ruby written 50 | in pure Ruby. 51 | organization: Rubyworks 52 | date: '2011-11-11' 53 | -------------------------------------------------------------------------------- /HISTORY.rdoc: -------------------------------------------------------------------------------- 1 | = Release History 2 | 3 | == 1.1.0 / 2011-10-24 4 | 5 | This release modernizes the build configuration and 6 | switches the license to BSD-2-Clause. At the same time 7 | it fixes a couple of issues with the aop.rb library 8 | when running Ruby 1.9+. 9 | 10 | Changes: 11 | 12 | * Modernize build system. 13 | * Fix stack error with aop.rb on Ruby 1.9+. 14 | 15 | 16 | == 1.0.0 / 2008-11-24 17 | 18 | This release completely overhauls how the the original 19 | version of cuts was implemented. 20 | 21 | Before, cuts where overriding the constant of the 22 | original class and redirecting class changes 23 | (define_method, alias_method, etc.) to the original 24 | class. This works, and is technically more true to 25 | the core idea of Cuts, but it is rather fragile -- 26 | getting a handle on the Cut vs. the Cut Class b/c 27 | tricky. 28 | 29 | The new implementation simply creates a module for 30 | a cut instead, and then used #extend on objects 31 | upon instantiation. The end result is less dynamic 32 | (cuts need to be defined up front), but it is more 33 | stable. As long as #new, when overriden, is done so 34 | properly (ie. use super or copy the cut extension 35 | logic) everthing will work as expected. 36 | 37 | 38 | == 0.0.4 (2008-03-05) 39 | 40 | Changes: 41 | 42 | * Added Rakefile to run tests. 43 | 44 | 45 | == 0.0.3 (2008-04-06) 46 | 47 | Changes: 48 | 49 | * Working release. 50 | 51 | 52 | == 0.0.1 (2008-03-05) 53 | 54 | Changes: 55 | 56 | * First release. 57 | 58 | -------------------------------------------------------------------------------- /test/test_cut.rb: -------------------------------------------------------------------------------- 1 | require 'microtest' 2 | require 'ae' 3 | require 'cuts' 4 | 5 | # 6 | class TestCutNew < MicroTest::TestCase 7 | 8 | class X 9 | def x; "x"; end 10 | end 11 | 12 | Xc = Cut.new(X) do 13 | def x; '{' + super + '}'; end 14 | end 15 | 16 | def test_method_is_wrapped_by_advice 17 | o = X.new 18 | o.x.assert == "{x}" 19 | end 20 | 21 | end 22 | 23 | # 24 | class TestLiteralSyntax < MicroTest::TestCase 25 | 26 | class F 27 | def f ; "f" ; end 28 | end 29 | 30 | cut :G < F do 31 | #join :f => :f 32 | def f; '<'+super+'>' ; end 33 | end 34 | 35 | def test_1_01 36 | f = F.new 37 | f.f.assert == "<f>" 38 | f.class.assert == F 39 | end 40 | 41 | def test_1_02 42 | assert(G) 43 | G.name.assert == "TestLiteralSyntax::G" 44 | end 45 | 46 | end 47 | 48 | # Test multiple cuts. 49 | class TestMultipleCuts < MicroTest::TestCase 50 | 51 | class F 52 | def f ; "f" ; end 53 | end 54 | 55 | cut :G < F do 56 | #join :f => :f 57 | def f; '<'+super+'>' ; end 58 | end 59 | 60 | cut :Q < F do 61 | #join :f => :f 62 | def f; '['+super+']'; end 63 | end 64 | 65 | #def test_2_01 66 | # F.cuts.assert == [Q, G] 67 | # F.predecessors.assert == [Q, G] 68 | #end 69 | 70 | def test_2_02 71 | f = F.new 72 | f.class.assert == F 73 | f.f.assert == "[<f>]" 74 | end 75 | 76 | def test_2_03 77 | assert(G) 78 | G.name.assert == "TestMultipleCuts::G" 79 | end 80 | 81 | def test_2_04 82 | assert(Q) 83 | Q.name.assert == "TestMultipleCuts::Q" 84 | end 85 | 86 | end 87 | 88 | -------------------------------------------------------------------------------- /work/deprecated/test_ccut.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'cuts' 3 | 4 | class TestCut < Test::Unit::TestCase 5 | 6 | class X 7 | def x; "x"; end 8 | end 9 | 10 | Xc = Cut.new(X) do 11 | def x; '{' + super + '}'; end 12 | end 13 | 14 | def test_method_is_wrapped_by_advice 15 | o = X.new 16 | assert_equal("{x}", o.x) 17 | end 18 | 19 | end 20 | 21 | class TestCut1 < Test::Unit::TestCase 22 | 23 | class F 24 | def f ; "f" ; end 25 | end 26 | 27 | cut :G < F do 28 | #join :f => :f 29 | def f; '<'+super+'>' ; end 30 | end 31 | 32 | def test_1_01 33 | f = F.new 34 | assert_equal( "<f>", f.f ) 35 | assert_equal( F, f.class ) 36 | end 37 | 38 | def test_1_02 39 | assert(G) 40 | assert_equal( "TestCut1::G", G.name ) 41 | end 42 | 43 | end 44 | 45 | # Test multiple cuts. 46 | 47 | class TestCut2 < Test::Unit::TestCase 48 | 49 | class F 50 | def f ; "f" ; end 51 | end 52 | 53 | cut :G < F do 54 | #join :f => :f 55 | def f; '<'+super+'>' ; end 56 | end 57 | 58 | cut :Q < F do 59 | #join :f => :f 60 | def f; '['+super+']'; end 61 | end 62 | 63 | #def test_2_01 64 | # assert_equal( [Q, G], F.cuts ) 65 | # assert_equal( [Q, G], F.predecessors ) 66 | #end 67 | 68 | def test_2_02 69 | f = F.new 70 | assert_equal( F, f.class ) 71 | assert_equal( "[<f>]", f.f ) 72 | end 73 | 74 | def test_2_03 75 | assert(G) 76 | assert_equal( "TestCut2::G", G.name ) 77 | end 78 | 79 | def test_2_04 80 | assert(Q) 81 | assert_equal( "TestCut2::Q", Q.name ) 82 | end 83 | 84 | end 85 | 86 | -------------------------------------------------------------------------------- /NOTICE.rdoc: -------------------------------------------------------------------------------- 1 | = COPYRIGHT NOTICES 2 | 3 | == Cuts 4 | 5 | Copyright:: (c) 2007 Thomas Sawyer, Peter Van Broekhoven 6 | License:: BSD-2-Clause 7 | Website:: http://rubyworks.github.com/cuts 8 | 9 | Copyright (c) 2007 Thomas Sawyer 10 | Copyright (c) 2007 Peter Van Broekhoven. 11 | All rights reserved. 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | 16 | 1. Redistributions of source code must retain the above copyright notice, 17 | this list of conditions and the following disclaimer. 18 | 19 | 2. Redistributions in binary form must reproduce the above copyright 20 | notice, this list of conditions and the following disclaimer in the 21 | documentation and/or other materials provided with the distribution. 22 | 23 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 24 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 25 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 28 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 30 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 32 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | 34 | -------------------------------------------------------------------------------- /work/sandbox/test_cut.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'cuts' 3 | 4 | class TestCut < Test::Unit::TestCase 5 | 6 | class X 7 | def x; "x"; end 8 | end 9 | 10 | Xc = Cut.new(X) do 11 | def x; '{' + super + '}'; end 12 | end 13 | 14 | def test_method_is_wrapped_by_advice 15 | o = X.new 16 | assert_equal("{x}", o.x) 17 | end 18 | 19 | end 20 | 21 | class TestCut1 < Test::Unit::TestCase 22 | 23 | class F 24 | def f ; "f" ; end 25 | end 26 | 27 | cut :G < F do 28 | #join :f => :f 29 | def f(target); '<'+target.super+'>' ; end 30 | end 31 | 32 | def test_1_01 33 | f = F.new 34 | assert_equal( "<f>", f.f ) 35 | assert_equal( F, f.class ) 36 | assert_equal( F, f.object_class ) 37 | end 38 | 39 | def test_1_02 40 | assert( G ) 41 | assert_equal( "TestCut1::G", G.name ) 42 | end 43 | 44 | end 45 | 46 | # Test multiple cuts. 47 | 48 | class TestCut2 < Test::Unit::TestCase 49 | 50 | class F 51 | def f ; "f" ; end 52 | end 53 | 54 | cut :G < F do 55 | #join :f => :f 56 | def f(target); '<'+target.super+'>' ; end 57 | end 58 | 59 | cut :Q < F do 60 | #join :f => :f 61 | def f(target); '['+target.super+']'; end 62 | end 63 | 64 | def test_2_01 65 | assert_equal( [Q, G], F.cuts ) 66 | assert_equal( [Q, G], F.predecessors ) 67 | end 68 | 69 | def test_2_02 70 | f = F.new 71 | assert_equal( F, f.class ) 72 | assert_equal( F, f.object_class ) 73 | assert_equal( "[<f>]", f.f ) 74 | end 75 | 76 | def test_2_03 77 | assert( G ) 78 | assert_equal( "TestCut2::G", G.name ) 79 | assert( Q ) 80 | assert_equal( "TestCut2::Q", Q.name ) 81 | end 82 | 83 | end 84 | 85 | -------------------------------------------------------------------------------- /work/sandbox/mike.rb: -------------------------------------------------------------------------------- 1 | class Module 2 | private 3 | 4 | # Stash all current methods in a module and add that module to 5 | # the hierarchy. Permits insertion of new modules "in front" of 6 | # the module itself so we can override methods using 'include' 7 | def inherit_from_self! 8 | stash = Module.new 9 | instance_methods(false).each do |name| 10 | # Copy method into the module 11 | method = instance_method(name) 12 | stash.send(:define_method, name) do |*args| 13 | method.bind(self).call(*args[0...method.arity]) 14 | end 15 | # Redefine our method to delegate upwards 16 | define_method(name) { super } 17 | end 18 | include stash 19 | end 20 | 21 | # Includes a module but places it in front of this module's 22 | # own methods. The mixin can use 'super' to refer to this module 23 | def include_in_front(mixin) 24 | inherit_from_self! 25 | include mixin 26 | end 27 | 28 | # A more fine-grained approach: overrides a single method and 29 | # allows you to refer to the old one using 'super' 30 | def override(name, &block) 31 | current = Module.new 32 | method = instance_method(name) 33 | current.send(:define_method, name) do |*args| 34 | method.bind(self).call(*args[0...method.arity]) 35 | end 36 | 37 | include current 38 | define_method(name, &block) 39 | end 40 | end 41 | 42 | 43 | # Example: make a class and a module, mix the module into the 44 | # class and use 'super' to refer to the class's method from the 45 | # mixin. Ordinarily, the class's methods would take precedence 46 | # over those from the module 47 | 48 | class Foo 49 | def initialize(name) 50 | @name = name 51 | end 52 | 53 | def foo 54 | @name.upcase 55 | end 56 | end 57 | 58 | puts Foo.new('Mike').foo 59 | #=> "MIKE" 60 | 61 | module Helper 62 | def foo(thing) 63 | "My name is #{super}! I like #{thing}" 64 | end 65 | end 66 | 67 | class Foo 68 | include_in_front Helper 69 | end 70 | 71 | puts Foo.new('Mike').foo('Ruby!') 72 | #=> "My name is MIKE! I like Ruby!" 73 | 74 | 75 | # Second example, using override 76 | 77 | class Bar 78 | def talk(item) 79 | "It's some #{item}" 80 | end 81 | 82 | override :talk do 83 | super.upcase 84 | end 85 | end 86 | 87 | puts Bar.new.talk('stuff') 88 | #=> "IT'S SOME STUFF" 89 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Cuts - Cut-based AOP for Ruby 2 | 3 | {Homepage}[http://rubyworks.github.com/cuts] | 4 | {Source Code}[http://github.com/rubyworks/cuts] | 5 | {Issue Tracker}[http://github.com/rubyworks/cuts/issues] 6 | 7 | {<img src="http://travis-ci.org/rubyworks/cuts.png" />}[http://travis-ci.org/rubyworks/cuts] 8 | 9 | 10 | == Description 11 | 12 | Cuts is an expiremental pure-Ruby implimentation of cut-base AOP. 13 | Cuts are a failry low-level system, so implementing them in pure-Ruby is no 14 | simple accomplishment, even for a language as reflective and metaprogrammable 15 | as Ruby. 16 | 17 | 18 | == History 19 | 20 | Cuts started it's life as a discussion about AOP on Ruby-talk, which led to 21 | a in-depth discussion between Trans and Peter Vanbroekhoven. The outcome of those talks 22 | were three projects. {Suby}[http://suby.rubyforge.org] an early expiremental 23 | branch of Ruby; the RCRFoundry, a section of the Ruby Garden Wiki dedicated to jointly 24 | developing RCRs; and the {Cut-based AOP RCR}[rcr.html]. The RCR of course, 25 | ultimately led to this project, as well as an expiremental Ruby 1.8.3 core 26 | implementation (see the Suby homepage). We continue to touch up the {RCR}[rcr.html] 27 | but on the whole it is now complete. 28 | 29 | Please read {Cut-based AOP for Ruby RCR}[rcr.html]. 30 | 31 | This Cuts library comes by way of {Ruby Facets}[http://rubyworks.github.com/facets], 32 | where the implementation was orginally housed. Becuase of it's expiremental nature, 33 | it was deemd best to move it into it's own project as part of the ongoing work to 34 | make Facets a rock solid library. Despite being expiremental, this implementation 35 | does pass it's unit test. However, it has had litte trial in integrated tests. 36 | It will be interesting to see if this library, as it matures, can prove robust 37 | enough for production use. In either case, clearly it would be preferable to have 38 | a Ruby-core implementation instead, but that potential is completely in other 39 | persons hands. 40 | 41 | 42 | == Usage 43 | 44 | Here is a quick and dirty example: 45 | 46 | require 'cuts' 47 | 48 | class C 49 | def f ; "f" ; end 50 | end 51 | 52 | cut :G < C do 53 | def f; '<'+super+'>' ; end 54 | end 55 | 56 | c = C.new 57 | 58 | c.f #=> "<f>" 59 | 60 | 61 | For detailed usage documentation, please refer to the {API Documentation}[http://rubydoc.info/gems/cuts]. 62 | 63 | 64 | == Install 65 | 66 | Install via RubyGems: 67 | 68 | $ gem install cuts 69 | 70 | 71 | == Special Thanks 72 | 73 | Special thanks to Peter Van Broekhoven. The man is a genius! 74 | 75 | 76 | == Copyrights 77 | 78 | Cuts, Copyright (c) 2007 Thomas Sawyer & Peter Van Broekhoven 79 | 80 | Cuts is distributable in accrodance with the *FreeBSD* License. 81 | 82 | See NOTICE.rdoc for details. 83 | -------------------------------------------------------------------------------- /lib/cuts/cut.rb: -------------------------------------------------------------------------------- 1 | # cut.rb 2 | # Copyright (c) 2005,2008 Thomas Sawyer 3 | # 4 | # Ruby License 5 | # 6 | # This module is free software. You may use, modify, and/or redistribute this 7 | # software under the same terms as Ruby. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | 13 | # = Cut 14 | # 15 | # Cuts are transparent subclasses. Thay are the basis of 16 | # Cut-based AOP. The general idea of Cut-based AOP is that 17 | # the Cut can serve a clean container for customized advice on 18 | # top of which more sophisticated AOP systems can be built. 19 | # 20 | # == Examples 21 | # 22 | # The basic usage is: 23 | # 24 | # class X 25 | # def x; "x"; end 26 | # end 27 | # 28 | # cut :Z < X do 29 | # def x; '{' + super + '}'; end 30 | # end 31 | # 32 | # X.new.x #=> "{x}" 33 | # 34 | # One way to use this in an AOP fashion is to define an aspect as a class 35 | # or function module, and tie it together with the Cut. 36 | # 37 | # module LogAspect 38 | # extend self 39 | # def log(meth, result) 40 | # ... 41 | # end 42 | # end 43 | # 44 | # cut :Z < X do 45 | # def x 46 | # LogAspect.log(:x, r = super) 47 | # return r 48 | # end 49 | # end 50 | # 51 | # == Implementation 52 | # 53 | # Cuts act as a "pre-class". Which depictively is: 54 | # 55 | # ACut < AClass < ASuperClass 56 | # 57 | # Instantiating AClass effecively instantiates ACut instead, 58 | # but that action is effectively transparent. 59 | # 60 | # This particular implementation create a module for each cut 61 | # and extends objects as they are created. Given the following example: 62 | # 63 | # class Klass 64 | # def x; "x"; end 65 | # end 66 | # 67 | # cut KlassCut < Klass 68 | # def x; '{' + super + '}'; end 69 | # end 70 | # 71 | # The effect is essentially: 72 | # 73 | # k = Klass.new 74 | # k.extend KlassCut 75 | # 76 | # p k.x 77 | # 78 | # The downside to this approach is a limitation in dynamicism. 79 | 80 | class Cut < Module 81 | def initialize(klass, &block) 82 | klass.cuts.unshift(self) 83 | module_eval(&block) 84 | end 85 | end 86 | 87 | class Class 88 | def cuts 89 | @cuts ||= [] 90 | end 91 | end 92 | 93 | class Object 94 | class << self 95 | alias_method :_new, :new 96 | 97 | def new(*a, &b) 98 | o = _new(*a, &b) 99 | if !cuts.empty? 100 | o.extend *cuts 101 | end 102 | o 103 | end 104 | 105 | end 106 | end 107 | 108 | class Symbol 109 | #alias :_op_lt_without_cuts :< 110 | 111 | # A little tick to simulate subclassing literal syntax. 112 | def <(klass) 113 | if Class === klass 114 | [self, klass] 115 | else 116 | raise NoMethodError, "undefined method `<' for :#{self}:Symbol" 117 | #_op_lt_without_cuts(cut_class) 118 | end 119 | end 120 | end 121 | 122 | module Kernel 123 | # Cut convienence method. 124 | def cut(klass, &block) 125 | case klass 126 | when Array 127 | name, klass = *klass 128 | else 129 | name = nil 130 | end 131 | 132 | cut = Cut.new(klass, &block) 133 | 134 | # How to handle main, but not other instance spaces? 135 | #klass.modspace::const_set(klass.basename, cut) 136 | mod = (Module === self ? self : Object) 137 | mod.const_set(name, cut) if name # <<- this is what we don't have in Cut.new 138 | 139 | return cut 140 | end 141 | end 142 | 143 | 144 | -------------------------------------------------------------------------------- /work/consider/test_cut_join.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'cuts' 3 | 4 | class TestCut1 < Test::Unit::TestCase 5 | 6 | class F 7 | def f ; "f" ; end 8 | end 9 | 10 | cut :G < F do 11 | #join :f => :f 12 | def f(target); '<'+target.super+'>' ; end 13 | end 14 | 15 | def test_1_01 16 | f = F.new 17 | assert_equal( "<f>", f.f ) 18 | assert_equal( F, f.class ) 19 | assert_equal( F, f.object_class ) 20 | end 21 | 22 | def test_1_02 23 | assert(G) 24 | assert_equal( "TestCut1::G", G.name ) 25 | end 26 | 27 | end 28 | 29 | # Test multiple cuts. 30 | 31 | class TestCut2 < Test::Unit::TestCase 32 | 33 | class F 34 | def f ; "f" ; end 35 | end 36 | 37 | cut :G < F do 38 | #join :f => :f 39 | def f(target); '<'+target.super+'>' ; end 40 | end 41 | 42 | cut :Q < F do 43 | #join :f => :f 44 | def f(target); '['+target.super+']'; end 45 | end 46 | 47 | def test_2_01 48 | assert_equal( [Q, G], F.cuts ) 49 | assert_equal( [Q, G], F.predecessors ) 50 | end 51 | 52 | def test_2_02 53 | f = F.new 54 | assert_equal( F, f.class ) 55 | assert_equal( F, f.object_class ) 56 | assert_equal( "[<f>]", f.f ) 57 | end 58 | 59 | def test_2_03 60 | assert( G ) 61 | assert_equal( "TestCut2::G", G.name ) 62 | assert( Q ) 63 | assert_equal( "TestCut2::Q", Q.name ) 64 | end 65 | 66 | end 67 | 68 | # 69 | 70 | class TestCut3 < Test::Unit::TestCase 71 | 72 | class C 73 | def r1; "r1"; end 74 | end 75 | 76 | cut :A < C do 77 | def r1 78 | b1( target( :r1 ){ super } ) 79 | end 80 | def b1( target ) 81 | '(' + target.super + ')' 82 | end 83 | end 84 | 85 | def test_3_01 86 | c = C.new 87 | assert_equal( '(r1)', c.r1 ) 88 | end 89 | 90 | end 91 | 92 | # Test the addition of new methods and module inclusions 93 | # after the cut is defined with dynamic joining. 94 | 95 | class TestCut4 < Test::Unit::TestCase 96 | 97 | class C 98 | def r1; "r1"; end 99 | def r2; "r2"; end 100 | def j1; "j1"; end 101 | def j2; "j2"; end 102 | end 103 | 104 | cut :A < C do 105 | 106 | join :wrappy => lambda { |jp| /^r/ =~ jp } 107 | join :square => :j1, :flare => :j2 108 | 109 | def wrappy( target ) 110 | '{'+target.super+'}' 111 | end 112 | 113 | def square(target) '['+target.super+']' end 114 | def flare(target) '*'+target.super+'*' end 115 | end 116 | 117 | class C 118 | def r3; "r3"; end 119 | end 120 | 121 | module M 122 | def r4 ; "r4"; end 123 | end 124 | 125 | class C 126 | include M 127 | end 128 | 129 | def test_4_01 130 | c = C.new 131 | assert_equal( '{r1}', c.r1 ) 132 | assert_equal( '{r2}', c.r2 ) 133 | assert_equal( '{r3}', c.r3 ) 134 | assert_equal( '{r4}', c.r4 ) 135 | end 136 | 137 | def test_4_02 138 | c = C.new 139 | assert_equal( '[j1]', c.j1 ) 140 | assert_equal( '*j2*', c.j2 ) 141 | end 142 | 143 | end 144 | 145 | # Test subclassing. 146 | 147 | class TestCut5 < Test::Unit::TestCase 148 | 149 | class C 150 | def r1; "r1"; end 151 | def r2; "r2"; end 152 | end 153 | 154 | cut :C1 < C do 155 | join :wrap1 => [:r1, :r2] 156 | 157 | def wrap1( target ) 158 | '{' + target.super + '}' 159 | end 160 | end 161 | 162 | cut :C2 < C do 163 | join :wrap2 => [:r1, :r2] 164 | 165 | def wrap2( target ) 166 | '[' + target.super + ']' 167 | end 168 | end 169 | 170 | class D < C 171 | def r1; '<' + super + '>'; end 172 | end 173 | 174 | def test_5_01 175 | c = C.new 176 | assert_equal( '[{r1}]', c.r1 ) 177 | assert_equal( '[{r2}]', c.r2 ) 178 | d = D.new 179 | assert_equal( '<[{r1}]>', d.r1 ) 180 | assert_equal( '[{r2}]', d.r2 ) 181 | end 182 | 183 | end 184 | 185 | -------------------------------------------------------------------------------- /work/sandbox/test_cut_join.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'cuts' 3 | 4 | class TestCut1 < Test::Unit::TestCase 5 | 6 | class F 7 | def f ; "f" ; end 8 | end 9 | 10 | cut :G < F do 11 | #join :f => :f 12 | def f(target); '<'+target.super+'>' ; end 13 | end 14 | 15 | def test_1_01 16 | f = F.new 17 | assert_equal( "<f>", f.f ) 18 | assert_equal( F, f.class ) 19 | assert_equal( F, f.object_class ) 20 | end 21 | 22 | def test_1_02 23 | assert( G ) 24 | assert_equal( "TestCut1::G", G.name ) 25 | end 26 | 27 | end 28 | 29 | # Test multiple cuts. 30 | 31 | class TestCut2 < Test::Unit::TestCase 32 | 33 | class F 34 | def f ; "f" ; end 35 | end 36 | 37 | cut :G < F do 38 | #join :f => :f 39 | def f(target); '<'+target.super+'>' ; end 40 | end 41 | 42 | cut :Q < F do 43 | #join :f => :f 44 | def f(target); '['+target.super+']'; end 45 | end 46 | 47 | def test_2_01 48 | assert_equal( [Q, G], F.cuts ) 49 | assert_equal( [Q, G], F.predecessors ) 50 | end 51 | 52 | def test_2_02 53 | f = F.new 54 | assert_equal( F, f.class ) 55 | assert_equal( F, f.object_class ) 56 | assert_equal( "[<f>]", f.f ) 57 | end 58 | 59 | def test_2_03 60 | assert( G ) 61 | assert_equal( "TestCut2::G", G.name ) 62 | assert( Q ) 63 | assert_equal( "TestCut2::Q", Q.name ) 64 | end 65 | 66 | end 67 | 68 | # 69 | 70 | class TestCut3 < Test::Unit::TestCase 71 | 72 | class C 73 | def r1; "r1"; end 74 | end 75 | 76 | cut :A < C do 77 | def r1 78 | b1( target( :r1 ){ super } ) 79 | end 80 | def b1( target ) 81 | '(' + target.super + ')' 82 | end 83 | end 84 | 85 | def test_3_01 86 | c = C.new 87 | assert_equal( '(r1)', c.r1 ) 88 | end 89 | 90 | end 91 | 92 | # Test the addition of new methods and module inclusions 93 | # after the cut is defined with dynamic joining. 94 | 95 | class TestCut4 < Test::Unit::TestCase 96 | 97 | class C 98 | def r1; "r1"; end 99 | def r2; "r2"; end 100 | def j1; "j1"; end 101 | def j2; "j2"; end 102 | end 103 | 104 | cut :A < C do 105 | 106 | join :wrappy => lambda { |jp| /^r/ =~ jp } 107 | join :square => :j1, :flare => :j2 108 | 109 | def wrappy( target ) 110 | '{'+target.super+'}' 111 | end 112 | 113 | def square(target) '['+target.super+']' end 114 | def flare(target) '*'+target.super+'*' end 115 | end 116 | 117 | class C 118 | def r3; "r3"; end 119 | end 120 | 121 | module M 122 | def r4 ; "r4"; end 123 | end 124 | 125 | class C 126 | include M 127 | end 128 | 129 | def test_4_01 130 | c = C.new 131 | assert_equal( '{r1}', c.r1 ) 132 | assert_equal( '{r2}', c.r2 ) 133 | assert_equal( '{r3}', c.r3 ) 134 | assert_equal( '{r4}', c.r4 ) 135 | end 136 | 137 | def test_4_02 138 | c = C.new 139 | assert_equal( '[j1]', c.j1 ) 140 | assert_equal( '*j2*', c.j2 ) 141 | end 142 | 143 | end 144 | 145 | # Test subclassing. 146 | 147 | class TestCut5 < Test::Unit::TestCase 148 | 149 | class C 150 | def r1; "r1"; end 151 | def r2; "r2"; end 152 | end 153 | 154 | cut :C1 < C do 155 | join :wrap1 => [:r1, :r2] 156 | 157 | def wrap1( target ) 158 | '{' + target.super + '}' 159 | end 160 | end 161 | 162 | cut :C2 < C do 163 | join :wrap2 => [:r1, :r2] 164 | 165 | def wrap2( target ) 166 | '[' + target.super + ']' 167 | end 168 | end 169 | 170 | class D < C 171 | def r1; '<' + super + '>'; end 172 | end 173 | 174 | def test_5_01 175 | c = C.new 176 | assert_equal( '[{r1}]', c.r1 ) 177 | assert_equal( '[{r2}]', c.r2 ) 178 | d = D.new 179 | assert_equal( '<[{r1}]>', d.r1 ) 180 | assert_equal( '[{r2}]', d.r2 ) 181 | end 182 | 183 | end 184 | 185 | -------------------------------------------------------------------------------- /lib/cuts/aop.rb: -------------------------------------------------------------------------------- 1 | require 'cuts/cut' 2 | 3 | require 'pp' 4 | 5 | # TODO: Can JointPoint and Target be the same class? 6 | 7 | # Aspect Oriented Programming for Ruby using Cuts. 8 | # 9 | class Aspect < Module 10 | 11 | def initialize(&block) 12 | instance_eval(&block) 13 | extend self 14 | end 15 | 16 | def points 17 | @points ||= {} 18 | end 19 | 20 | # TODO Should this accept pattern matches as an alternative to the block too? 21 | # Eg. join(name, pattern=nil, &block) 22 | def join(name, &block) 23 | (points[name] ||= []) << block 24 | end 25 | 26 | end 27 | 28 | # TODO: pass actual method instead of using instace_method ? 29 | 30 | class Joinpoint 31 | def initialize(object, base, method, *args, &block) 32 | @object = object 33 | @base = base 34 | @method = method 35 | @args = args 36 | @block = block 37 | end 38 | 39 | def ===(match) 40 | case match 41 | when Proc 42 | match.call(self) 43 | else # Pattern matches (not supported presently) 44 | match.to_sym == @method.to_sym 45 | end 46 | end 47 | 48 | def ==(sym) 49 | sym.to_sym == @method.to_sym 50 | end 51 | 52 | # 53 | 54 | def super 55 | anc = @object.class.ancestors.find{ |anc| anc.method_defined?(@method) } 56 | anc.instance_method(@method).bind(@object).call(*@args, &@block) 57 | end 58 | end 59 | 60 | 61 | # module LogAspect 62 | # extend self 63 | # 64 | # join :log do |jp| 65 | # jp.name == :x 66 | # end 67 | # 68 | # def log(target) 69 | # r = target.super 70 | # ... 71 | # return r 72 | # end 73 | # end 74 | # 75 | # class X 76 | 77 | 78 | class Target 79 | 80 | def initialize(aspect, advice, *target, &block) 81 | @aspect = aspect 82 | @advice = advice 83 | @target = target 84 | @block = block 85 | end 86 | 87 | def super 88 | @aspect.send(@advice, *@target, &@block) 89 | end 90 | 91 | alias_method :call, :super 92 | end 93 | 94 | 95 | def cross_cut(klass) 96 | 97 | Cut.new(klass) do 98 | 99 | define_method :__base__ do 100 | klass 101 | end 102 | 103 | def advices 104 | @advices ||= {} 105 | end 106 | 107 | def self.extended(obj) 108 | base = obj.class #__base__ 109 | 110 | # use string for 1.9-, and symbol for 1.9+ 111 | methods = obj.methods + 112 | obj.public_methods + 113 | obj.protected_methods + 114 | obj.private_methods - 115 | [:advices, 'advices'] 116 | 117 | methods.uniq.each do |sym| 118 | #meth = obj.method(sym) 119 | define_method(sym) do |*args, &blk| 120 | jp = Joinpoint.new(self, base, sym, *args) #, &blk) 121 | # calculate advices on first use. 122 | unless advices[sym] 123 | advices[sym] = [] 124 | base.aspects.each do |aspect| 125 | aspect.points.each do |advice, matches| 126 | matches.each do |match| 127 | if jp === match 128 | advices[sym] << [aspect, advice] 129 | end 130 | end 131 | end 132 | end 133 | end 134 | 135 | if advices[sym].empty? 136 | super(*args, &blk) 137 | else 138 | target = jp #Target.new(self, sym, *args, &blk) # Target == JoinPoint ? 139 | advices[sym].each do |(aspect, advice)| 140 | target = Target.new(aspect, advice, target) 141 | end 142 | target.call #super 143 | end 144 | end #define_method 145 | end #methods 146 | end #def 147 | 148 | end 149 | 150 | end 151 | 152 | 153 | # 154 | 155 | class Class 156 | #def cut; @cut; end 157 | def aspects; @aspects ||= []; end 158 | 159 | def apply(aspect) 160 | if aspects.empty? 161 | cross_cut(self) 162 | #(class << self;self;end).class_eval do 163 | # alias_method :__new, :new 164 | # def new(*args, &block) 165 | # CrossConcerns.new(self,*args, &block) 166 | # end 167 | #end 168 | end 169 | aspects.unshift(aspect) 170 | end 171 | 172 | end 173 | 174 | 175 | =begin demo 176 | 177 | class X 178 | def x; "x"; end 179 | def y; "y"; end 180 | def q; "<" + x + ">"; end 181 | end 182 | 183 | Xa = Aspect.new do 184 | join :x do |jp| 185 | jp == :x 186 | end 187 | 188 | def x(target); '{' + target.super + '}'; end 189 | end 190 | 191 | X.apply(Xa) 192 | 193 | x1 = X.new 194 | #print 'X == ' ; p x1 195 | print 'X == ' ; p x1.class 196 | print '["q", "y", "x"] == ' ; p x1.public_methods(false) 197 | print '"{x}" == ' ; p x1.x 198 | print '"y" == ' ; p x1.y 199 | print '"<{x}>" == ' ; p x1.q 200 | 201 | =end 202 | -------------------------------------------------------------------------------- /work/sandbox/cuts3.rb: -------------------------------------------------------------------------------- 1 | # TITLE: 2 | # 3 | # Super Simple Cuts 4 | # 5 | # SUMMARY: 6 | # 7 | # Cut-based AOP in it's most basic form. 8 | 9 | require 'facets/kernel/object' 10 | 11 | # This is the basic model. If we want: 12 | # 13 | # class Klass 14 | # def x; "x"; end 15 | # end 16 | # 17 | # cut KlassCut < Klass 18 | # def x; '{' + super + '}'; end 19 | # end 20 | # 21 | # We cut it like so: 22 | # 23 | # class Klass 24 | # def self.new 25 | # KlassCut.new 26 | # end 27 | # end 28 | # 29 | # class KlassCut 30 | # def self.new 31 | # Class.instance_method(:new).bind(self).call 32 | # end 33 | # end 34 | # 35 | # p Klass.new.x 36 | # 37 | # This is simple and relatvely robust. It lacks two general features of 38 | # a good AOP solution however. 1) It relies on +super+ in advice rather 39 | # than passing in a more versitle +target+ object. And 2) it doesn't 40 | # support joins, which allow targeted methods to share the same advice. 41 | 42 | class Cut < Module 43 | 44 | class << self; alias_method :create, :new; end 45 | 46 | def self.new(klass, &block) 47 | next_class = klass.cuts.empty? ? klass : klass.cuts.first 48 | 49 | cutclass = Class.new(next_class) 50 | cut = create(&block) 51 | 52 | cutclass.class_eval do 53 | define_method(:__class__){ klass } 54 | define_method(:__cut__){ cut } 55 | 56 | m1 = public_instance_methods 57 | m2 = protected_instance_methods 58 | m3 = private_instance_methods 59 | 60 | (m1 + m2 + m3).each do |meth| 61 | #instance_methods.each do |meth| 62 | undef_method(meth) unless meth.to_s =~ /^__|initialize|p/ 63 | end 64 | 65 | define_method(:class){ klass } 66 | define_method(:object_class){ klass } 67 | end 68 | 69 | # Method missing for cutclass. 70 | 71 | cutclass.class_eval do 72 | def method_missing(sym, *args, &blk) 73 | result = nil 74 | 75 | target = __class__.instance_method(sym).bind(self) # Not going to work if another cut!!! 76 | 77 | (class << target; self; end).class_eval do 78 | define_method( :super ){ call(*args, &blk) } 79 | end 80 | 81 | advices = [] 82 | 83 | __cut__.joinpoints.each do |advice, point| 84 | case point 85 | when Proc 86 | advices << advice if point.call(sym) 87 | else 88 | advices << advice if point.to_sym == sym 89 | end 90 | end 91 | 92 | if advices.empty? 93 | result = target.super 94 | else 95 | target = advices.inject(target) do |retarget, advice| 96 | supercall = lambda{ __cut__.send(advice, retarget) } 97 | target = retarget.to_proc.dup 98 | (class << target; self; end).class_eval do 99 | define_method(:super) { supercall.call } 100 | end 101 | target 102 | end 103 | 104 | result = target.super(*args, &blk) 105 | end 106 | 107 | return result 108 | end 109 | end 110 | 111 | def cutclass.new 112 | Class.instance_method(:new).bind(self).call 113 | end 114 | 115 | klass.cuts.unshift(cutclass) 116 | 117 | (class << klass; self; end).send(:define_method, :cut) do 118 | cutclass 119 | end 120 | 121 | def klass.new(*a, &b) 122 | cut.new(*a, &b) 123 | end 124 | 125 | return cutclass 126 | end 127 | 128 | 129 | attr :joinpoints 130 | 131 | def initialize(&block) 132 | @joinpoints = {} 133 | extend self 134 | class_eval(&block) 135 | end 136 | 137 | def join(hash) 138 | @joinpoints.update(hash) 139 | end 140 | 141 | 142 | # 143 | 144 | Stub = Struct.new(:cutname,:cutclass) 145 | 146 | def self.stub(cutname, cutclass) 147 | Stub.new(cutname,cutclass) 148 | end 149 | 150 | end 151 | 152 | 153 | class Class 154 | def cuts 155 | @cuts ||= [] 156 | end 157 | end 158 | 159 | 160 | class Method 161 | alias_method :super, :call 162 | end 163 | 164 | # 165 | 166 | class Symbol 167 | #-- 168 | # This is a hack. 169 | #++ 170 | 171 | #alias :_op_lt_without_cuts :< 172 | 173 | def <(cutclass) 174 | if Class === cutclass 175 | Cut.stub(self,cutclass) 176 | else 177 | raise NoMethodError, "undefined method `<' for :#{self}:Symbol" 178 | #_op_lt_without_cuts(cut_class) 179 | end 180 | end 181 | end 182 | 183 | #class Module 184 | module Kernel 185 | 186 | # 187 | 188 | def cut( cutclass, &block ) 189 | case cutclass 190 | when Cut::Stub 191 | cutname = cutclass.cutname 192 | cutclass = cutclass.cutclass 193 | else 194 | cutname = nil 195 | end 196 | 197 | # How to handle main, but not other instance spaces? 198 | mod = (Module === self ? self : Object) 199 | 200 | # We don't call Cut.new b/c we want to set the module name 201 | #cut = Cut.new(cutclass,&block) 202 | cut = Cut.new(cutclass, &block) 203 | mod.const_set(cutname, cut) # <<- this is what we don't have in Cut.new 204 | #cut.module_eval(&block) 205 | #proxy = cutclass.proxycut! 206 | #proxy.module_eval { include cut } 207 | 208 | cut 209 | end 210 | 211 | end 212 | 213 | 214 | 215 | # 216 | # Basic Test 217 | # 218 | 219 | if $0 == __FILE__ 220 | 221 | class X 222 | def x; "x"; end 223 | end 224 | 225 | Xc = Cut.new(X) do 226 | join :x => lambda { |jp| jp == :x } 227 | 228 | def x(target); '{' + target.super + '}'; end 229 | end 230 | 231 | x1 = X.new 232 | p x1.x 233 | p x1.class 234 | 235 | end 236 | -------------------------------------------------------------------------------- /.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | 5 | module DotRuby 6 | 7 | # 8 | class GemSpec 9 | 10 | # For which revision of .ruby is this gemspec intended? 11 | REVISION = 0 12 | 13 | # 14 | PATTERNS = { 15 | :bin_files => 'bin/*', 16 | :lib_files => 'lib/{**/}*.rb', 17 | :ext_files => 'ext/{**/}extconf.rb', 18 | :doc_files => '*.{txt,rdoc,md,markdown,tt,textile}', 19 | :test_files => '{test/{**/}*_test.rb,spec/{**/}*_spec.rb}' 20 | } 21 | 22 | # 23 | def self.instance 24 | new.to_gemspec 25 | end 26 | 27 | attr :metadata 28 | 29 | attr :manifest 30 | 31 | # 32 | def initialize 33 | @metadata = YAML.load_file('.ruby') 34 | @manifest = Dir.glob('manifest{,.txt}', File::FNM_CASEFOLD).first 35 | 36 | if @metadata['revision'].to_i != REVISION 37 | warn "You have the wrong revision. Trying anyway..." 38 | end 39 | end 40 | 41 | # 42 | def scm 43 | @scm ||= \ 44 | case 45 | when File.directory?('.git') 46 | :git 47 | end 48 | end 49 | 50 | # 51 | def files 52 | @files ||= \ 53 | #glob_files[patterns[:files]] 54 | case 55 | when manifest 56 | File.readlines(manifest). 57 | map{ |line| line.strip }. 58 | reject{ |line| line.empty? || line[0,1] == '#' } 59 | when scm == :git 60 | `git ls-files -z`.split("\0") 61 | else 62 | Dir.glob('{**/}{.*,*}') # TODO: be more specific using standard locations ? 63 | end.select{ |path| File.file?(path) } 64 | end 65 | 66 | # 67 | def glob_files(pattern) 68 | Dir.glob(pattern).select { |path| 69 | File.file?(path) && files.include?(path) 70 | } 71 | end 72 | 73 | # 74 | def patterns 75 | PATTERNS 76 | end 77 | 78 | # 79 | def executables 80 | @executables ||= \ 81 | glob_files(patterns[:bin_files]).map do |path| 82 | File.basename(path) 83 | end 84 | end 85 | 86 | def extensions 87 | @extensions ||= \ 88 | glob_files(patterns[:ext_files]).map do |path| 89 | File.basename(path) 90 | end 91 | end 92 | 93 | # 94 | def name 95 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 96 | end 97 | 98 | # 99 | def to_gemspec 100 | Gem::Specification.new do |gemspec| 101 | gemspec.name = name 102 | gemspec.version = metadata['version'] 103 | gemspec.summary = metadata['summary'] 104 | gemspec.description = metadata['description'] 105 | 106 | metadata['authors'].each do |author| 107 | gemspec.authors << author['name'] 108 | 109 | if author.has_key?('email') 110 | if gemspec.email 111 | gemspec.email << author['email'] 112 | else 113 | gemspec.email = [author['email']] 114 | end 115 | end 116 | end 117 | 118 | gemspec.licenses = metadata['copyrights'].map{ |c| c['license'] }.compact 119 | 120 | metadata['requirements'].each do |req| 121 | name = req['name'] 122 | version = req['version'] 123 | groups = req['groups'] || [] 124 | 125 | case version 126 | when /^(.*?)\+$/ 127 | version = ">= #{$1}" 128 | when /^(.*?)\-$/ 129 | version = "< #{$1}" 130 | when /^(.*?)\~$/ 131 | version = "~> #{$1}" 132 | end 133 | 134 | if groups.empty? or groups.include?('runtime') 135 | # populate runtime dependencies 136 | if gemspec.respond_to?(:add_runtime_dependency) 137 | gemspec.add_runtime_dependency(name,*version) 138 | else 139 | gemspec.add_dependency(name,*version) 140 | end 141 | else 142 | # populate development dependencies 143 | if gemspec.respond_to?(:add_development_dependency) 144 | gemspec.add_development_dependency(name,*version) 145 | else 146 | gemspec.add_dependency(name,*version) 147 | end 148 | end 149 | end 150 | 151 | # convert external dependencies into a requirements 152 | if metadata['external_dependencies'] 153 | ##gemspec.requirements = [] unless metadata['external_dependencies'].empty? 154 | metadata['external_dependencies'].each do |req| 155 | gemspec.requirements << req.to_s 156 | end 157 | end 158 | 159 | # determine homepage from resources 160 | homepage = metadata['resources'].find{ |key, url| key =~ /^home/ } 161 | gemspec.homepage = homepage.last if homepage 162 | 163 | gemspec.require_paths = metadata['load_path'] || ['lib'] 164 | gemspec.post_install_message = metadata['install_message'] 165 | 166 | # RubyGems specific metadata 167 | gemspec.files = files 168 | gemspec.extensions = extensions 169 | gemspec.executables = executables 170 | 171 | if Gem::VERSION < '1.7.' 172 | gemspec.default_executable = gemspec.executables.first 173 | end 174 | 175 | gemspec.test_files = glob_files(patterns[:test_files]) 176 | 177 | unless gemspec.files.include?('.document') 178 | gemspec.extra_rdoc_files = glob_files(patterns[:doc_files]) 179 | end 180 | end 181 | end 182 | 183 | end #class GemSpec 184 | 185 | end 186 | 187 | DotRuby::GemSpec.instance 188 | -------------------------------------------------------------------------------- /work/deprecated/ccut.rb: -------------------------------------------------------------------------------- 1 | # cut.rb 2 | # Copyright (c) 2005,2008 Thomas Sawyer 3 | # 4 | # Ruby License 5 | # 6 | # This module is free software. You may use, modify, and/or redistribute this 7 | # software under the same terms as Ruby. 8 | # 9 | # This program is distributed in the hope that it will be useful, but WITHOUT 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | 13 | # = Cut 14 | # 15 | # Cuts are transparent subclasses. Thay are the basis of 16 | # Cut-based AOP. The general idea of Cut-based AOP is that 17 | # the Cut can serve a clean container for customized advice on 18 | # top of which more sophisticated AOP systems can be built. 19 | # 20 | # == Examples 21 | # 22 | # The basic usage is: 23 | # 24 | # class X 25 | # def x; "x"; end 26 | # end 27 | # 28 | # cut :C < X do 29 | # def x; '{' + super + '}'; end 30 | # end 31 | # 32 | # X.new.x #=> "{x}" 33 | # 34 | # To use this in an AOP fashion you can define an Aspect, as a class 35 | # or function module, and tie it together with the Cut. 36 | # 37 | # module LogAspect 38 | # extend self 39 | # def log(meth, result) 40 | # ... 41 | # end 42 | # end 43 | # 44 | # cut :C < X do 45 | # def x 46 | # LogAspect.log(:x, r = super) 47 | # return r 48 | # end 49 | # end 50 | # 51 | # == Implementation 52 | # 53 | # Cuts act as a "pre-class". Which depictively is: 54 | # 55 | # ACut < AClass < ASuperClass 56 | # 57 | # Instantiating AClass effecively instantiates ACut instead, 58 | # but that action is effectively transparent. 59 | # 60 | # This is the basic model of this particluar implementation: 61 | # 62 | # class Klass 63 | # def x; "x"; end 64 | # end 65 | # 66 | # cut KlassCut < Klass 67 | # def x; '{' + super + '}'; end 68 | # end 69 | # 70 | # We cut it like so: 71 | # 72 | # Klass = KlassCut 73 | # 74 | # p Klass.new.x 75 | # 76 | # This is simple and relatvely robust, but not 100% transparent. 77 | # So we add some redirection methods to the cut to improve the 78 | # transparency. 79 | # 80 | # Due to limitation in meta-programming Ruby as this level, the 81 | # transparency isn't perfect, but it's fairly close. 82 | 83 | class Cut 84 | 85 | def self.new(klass, &block) 86 | cut = Class.new(klass, &block) # <-- This is the actual cut. 87 | 88 | #cut.class_eval(&block) 89 | 90 | cut.send(:include, Transparency) 91 | cut.extend MetaTransparency 92 | 93 | v = $VERBOSE 94 | $VERBOSE = false 95 | klass.modspace::const_set(klass.basename, cut) 96 | $VERBOSE = v 97 | 98 | return cut 99 | end 100 | 101 | # These methods are needed to emulate full transparancy as 102 | # closely as possible. 103 | 104 | module Transparency 105 | def methods(all=true) 106 | self.class.superclass.instance_methods(all) 107 | end 108 | def public_methods(all=true) 109 | self.class.superclass.public_instance_methods(all) 110 | end 111 | def private_methods(all=true) 112 | self.class.superclass.private_instance_methods(all) 113 | end 114 | def protected_methods(all=true) 115 | self.class.superclass.protected_instance_methods(all) 116 | end 117 | end 118 | 119 | # These methods are needed to emulate full transparancy as 120 | # closely as possible. 121 | 122 | module MetaTransparency 123 | #def instance_method(name) ; p "XXXX"; superclass.instance_method(name) ; end 124 | def define_method(*a,&b) ; superclass.define_method(*a,&b) ; end 125 | def module_eval(*a,&b) ; superclass.module_eval(*a,&b) ; end 126 | def class_eval(*a,&b) ; superclass.class_eval(*a,&b) ; end 127 | end 128 | 129 | end 130 | 131 | 132 | class Symbol 133 | #alias :_op_lt_without_cuts :< 134 | 135 | # A little tick to simulate subclassing literal syntax. 136 | 137 | def <(klass) 138 | if Class === klass 139 | [self,klass] 140 | else 141 | raise NoMethodError, "undefined method `<' for :#{self}:Symbol" 142 | #_op_lt_without_cuts(cut_class) 143 | end 144 | end 145 | end 146 | 147 | 148 | module Kernel 149 | # Cut convienence method. 150 | 151 | def cut(klass, &block) 152 | case klass 153 | when Array 154 | name, klass = *klass 155 | else 156 | name = nil 157 | end 158 | 159 | cut = Cut.new(klass, &block) 160 | 161 | # How to handle main, but not other instance spaces? 162 | #klass.modspace::const_set(klass.basename, cut) 163 | mod = (Module === self ? self : Object) 164 | mod.const_set(name, cut) if name # <<- this is what we don't have in Cut.new 165 | 166 | return cut 167 | end 168 | end 169 | 170 | class Module 171 | # Returns the root name of the module/class. 172 | # 173 | # module Example 174 | # class Demo 175 | # end 176 | # end 177 | # 178 | # Demo.name #=> "Example::Demo" 179 | # Demo.basename #=> "Demo" 180 | # 181 | # For anonymous modules this will provide a basename 182 | # based on Module#inspect. 183 | # 184 | # m = Module.new 185 | # m.inspect #=> "#<Module:0xb7bb0434>" 186 | # m.basename #=> "Module_0xb7bb0434" 187 | # 188 | def basename 189 | if name and not name.empty? 190 | name.gsub(/^.*::/, '') 191 | else 192 | nil #inspect.gsub('#<','').gsub('>','').sub(':', '_') 193 | end 194 | end 195 | 196 | # Returns the module's container module. 197 | # 198 | # module Example 199 | # class Demo 200 | # end 201 | # end 202 | # 203 | # Example::Demo.modspace #=> Example 204 | # 205 | # See also Module#basename. 206 | # 207 | def modspace 208 | space = name[ 0...(name.rindex( '::' ) || 0)] 209 | space.empty? ? Object : eval(space) 210 | end 211 | end 212 | 213 | -------------------------------------------------------------------------------- /work/sandbox/cut.rb: -------------------------------------------------------------------------------- 1 | # TITLE: 2 | # 3 | # Cut-based AOP 4 | # 5 | # DESCRIPTION: 6 | # 7 | # By definition, a Cut is a *transparent* subclass. 8 | # 9 | # COPYRIGHT: 10 | # 11 | # Copyright (c) 2005 Thomas Sawyer 12 | # 13 | # LICENSE: 14 | # 15 | # Ruby License 16 | # 17 | # This module is free software. You may use, modify, and/or redistribute this 18 | # software under the same terms as Ruby. 19 | # 20 | # This program is distributed in the hope that it will be useful, but WITHOUT 21 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 22 | # FOR A PARTICULAR PURPOSE. 23 | # 24 | # AUTHORS: 25 | # 26 | # - Thomas Sawyer 27 | 28 | 29 | require 'facets/kernel/object' 30 | #require 'facets/module/modspace' #? 31 | #require 'facets/module/basename' #? 32 | 33 | 34 | # = Cut 35 | # 36 | # Cut is a low-level AOP facility. 37 | # 38 | # class X 39 | # def x; "x"; end 40 | # end 41 | # 42 | # cut :C < X do 43 | # def x; '{' + super + '}'; end 44 | # end 45 | # 46 | # X.new.x #=> "{x}" 47 | # 48 | # Cuts acts as "pre-class". Which depictively is: 49 | # 50 | # ACut < AClass < ASuperClass 51 | # 52 | # Instantiating AClass effecively instantiates ACut instead, 53 | # but that action is essentially transparent. 54 | # 55 | # == Implementation 56 | # 57 | # This implementation of Cut-based AOP nearly meets the formal concepts. 58 | # This differs slighly in that a Cut is not a class but rather a subclass 59 | # of Module which is included into a single "proxycut" class. 60 | # 61 | # This is very low-level library, as pure-Ruby library go. It overrides 62 | # #new for all classes and included for all modules. If you consider 63 | # the formal usage of Cuts, it essentially usurps any further need for 64 | # callbacks of this kind. Nonetheless, those callback will still be used 65 | # and developers should "play nice" by wrapping such functionality 66 | # rather than overriding. Cuts on the other hand, purposefully overrides. 67 | # 68 | # IMPORTANT To appropriately use Cuts, it should be the first library 69 | # required. One way to faciliate this by adding it to your RUBYOPT. 70 | # 71 | # == Limitations 72 | # 73 | # You can not cut classes formed via literal constructors, such as a 74 | # String defined via "" or a Hash defined via {}. Ruby provides no means 75 | # for overriding literal constructors. 76 | 77 | class Cut < Module 78 | 79 | class << self; alias :create :new ; end 80 | 81 | def self.new(cutclass, &block) 82 | raise ArgumentError, "not a class" unless Class === cutclass 83 | cut = Cut.create 84 | cut.module_eval(&block) 85 | proxy = cutclass.proxycut! 86 | proxy.module_eval { include cut } 87 | cut 88 | end 89 | 90 | Stub = Struct.new(:cutname,:cutclass) 91 | 92 | def self.stub(cutname, cutclass) 93 | Stub.new(cutname,cutclass) 94 | end 95 | 96 | # 97 | 98 | def included( base ) 99 | return unless @points 100 | points = Hash.new{ |h,k| h[k] = [] } 101 | base.instance_methods.each do |method| 102 | @points.each do |advice, pointcut| 103 | if pointcut[method] 104 | points[advice] << method 105 | end 106 | end 107 | end 108 | join( points ) 109 | end 110 | 111 | # Define a pointcut as selection of joinpoints (methods in our case) 112 | # to be advised. The block should return the name of the advice to 113 | # use when the joinpoint matches, nil or false otherwise. 114 | # 115 | # join :break => { |jp| jp =~ /^log.*/ } 116 | # 117 | # This would be very interesting in the context of annotations too. 118 | # 119 | # join :break => { |jp| ann(jp,:class) == String ? :break : nil } 120 | # 121 | # Takes a hash of advice => method for joining the advice to the method. 122 | # 123 | #-- 124 | # TODO Add wildcards for method points. 125 | # TODO Should the method() be 1st class? 126 | #++ 127 | 128 | def join( points=nil ) 129 | @points ||= {} 130 | return @points unless points 131 | code = '' 132 | points.each do |advice, pointcut| 133 | case pointcut 134 | when Regexp 135 | @points[advice] = lambda { |jp| pointcut =~ jp } 136 | when Proc 137 | @points[advice] = pointcut 138 | else 139 | [pointcut].flatten.uniq.each do |method| 140 | code << %{ 141 | def #{method}(*args,&block) 142 | #{advice}( target(:"#{method}"){ super } ) 143 | end 144 | } 145 | #Thought about putting advice in separate namespace (option?) 146 | #ObjectSpace._id2ref(#{object_id}).advice.#{advice}( this(:"#{method}"){ super } ) 147 | end 148 | end 149 | end 150 | module_eval code 151 | end 152 | 153 | # # Store advice in separate namespace. 154 | # 155 | # def advice(&adv) 156 | # @advice ||= Module.new { extend self } 157 | # @advice.module_eval &adv if adv 158 | # @advice 159 | # end 160 | 161 | # Prevent infinite loop. 162 | 163 | def method_added(name) 164 | end 165 | 166 | end 167 | 168 | #class Module 169 | module Kernel 170 | 171 | # 172 | 173 | def cut( cutclass, &block ) 174 | case cutclass 175 | when Cut::Stub 176 | cutname = cutclass.cutname 177 | cutclass = cutclass.cutclass 178 | else 179 | cutname = nil 180 | end 181 | 182 | # How to handle main, but not other instance spaces? 183 | mod = (Module === self ? self : Object) 184 | 185 | # We don't call Cut.new b/c we want to set the module name 186 | #cut = Cut.new(cutclass,&block) 187 | cut = Cut.create 188 | mod.const_set(cutname, cut) # <<- this is what we don't have in Cut.new 189 | cut.module_eval(&block) 190 | proxy = cutclass.proxycut! 191 | proxy.module_eval { include cut } 192 | 193 | cut 194 | end 195 | 196 | # 197 | 198 | def target( name, &superblock ) 199 | target = method(name) 200 | (class << target; self; end).class_eval do 201 | define_method( :super ){ superblock.call } 202 | end 203 | target 204 | end 205 | 206 | end 207 | 208 | 209 | class Symbol 210 | #-- 211 | # This is a bit of a hack. 212 | #++ 213 | 214 | #alias :_op_lt_without_cuts :< 215 | 216 | def <(cutclass) 217 | if Class === cutclass 218 | Cut.stub(self,cutclass) 219 | else 220 | raise NoMethodError, "undefined method `<' for :#{self}:Symbol" 221 | #_op_lt_without_cuts(cut_class) 222 | end 223 | end 224 | end 225 | 226 | 227 | class Class 228 | 229 | # Master cutting class (contains all cuts) 230 | #-- 231 | # TODO This should not automatically create the cutclass. 232 | #++ 233 | def proxycut 234 | @proxycut 235 | end 236 | 237 | def proxycut! 238 | return @proxycut if @proxycut 239 | @proxycut = const_set( 'CUT', Class._new_without_cut(self) ) 240 | klass = self 241 | @proxycut.class_eval { 242 | define_method(:class){ klass } 243 | define_method(:object_class){ klass } 244 | } 245 | @proxycut 246 | end 247 | 248 | #protected 249 | def proxycut=(pc) 250 | @proxycut = pc 251 | end 252 | 253 | # List of cuts in outer to inner order. Eg. 254 | # 255 | # [ Cut2, Cut1 ] 256 | # 257 | def cuts 258 | return [] unless proxycut 259 | (proxycut.ancestors - ancestors)[1..-1] 260 | end 261 | alias :predecessors :cuts 262 | 263 | alias :_new_without_cut :new 264 | 265 | def new(*args,&block) 266 | if @proxycut 267 | @proxycut._new_without_cut(*args,&block) 268 | else 269 | _new_without_cut(*args,&block) 270 | end 271 | end 272 | 273 | alias :_allocate_without_cut :allocate 274 | 275 | def allocate(*args,&block) 276 | if @proxycut 277 | @proxycut._allocate_without_cut(*args,&block) 278 | else 279 | _allocate_without_cut(*args,&block) 280 | end 281 | end 282 | 283 | # When a method is added, check to see if 284 | # cut applies to it, and if so join it. 285 | 286 | def method_added( method ) 287 | cuts.each do |cut| 288 | next unless points = cut.join 289 | points.each do |advice, pointcut| 290 | if pointcut[method.to_s] 291 | cut.join( advice => method ) 292 | end 293 | end 294 | end 295 | end 296 | 297 | # 298 | 299 | def inherited( base ) 300 | if proxycut 301 | cuts.reverse_each { |c| base.module_eval { include c } } 302 | end 303 | end 304 | 305 | end 306 | 307 | 308 | class Module 309 | 310 | # When a module is included, we need to look at all it's 311 | # methods and see if they match any cut points and if 312 | # so join them. 313 | 314 | def included( base ) 315 | base.cuts.each do |cut| 316 | cut.included(self) 317 | #next unless base_points = cut.join 318 | #points = Hash.new{ |h,k| h[k] = [] } 319 | #instance_methods.each do |method| 320 | # base_points.each do |advice, pointcut| 321 | # if advice = pointcut[method.to_s] 322 | # points[advice] << method 323 | # end 324 | # end 325 | #end 326 | #cut.join( points ) 327 | end 328 | end 329 | 330 | end 331 | 332 | 333 | # 334 | # Test 335 | # 336 | 337 | =begin test 338 | 339 | require 'test/unit' 340 | 341 | # Test basic functionality. 342 | 343 | class TestCut1 < Test::Unit::TestCase 344 | 345 | class F 346 | def f ; "f" ; end 347 | end 348 | 349 | cut :G < F do 350 | def f; '<'+super+'>' ; end 351 | end 352 | 353 | def test_1_01 354 | f = F.new 355 | assert_equal( F, f.class ) 356 | assert_equal( F, f.object_class ) 357 | assert_equal( "<f>", f.f ) 358 | end 359 | 360 | def test_1_02 361 | assert( G ) 362 | assert_equal( "TestCut1::G", G.name ) 363 | end 364 | 365 | end 366 | 367 | # Test multiple cuts. 368 | 369 | class TestCut2 < Test::Unit::TestCase 370 | 371 | class F 372 | def f ; "f" ; end 373 | end 374 | 375 | cut :G < F do 376 | def f; '<'+super+'>' ; end 377 | end 378 | 379 | cut :Q < F do 380 | def f; '['+super+']'; end 381 | end 382 | 383 | def test_2_01 384 | f = F.new 385 | assert_equal( F, f.class ) 386 | assert_equal( F, f.object_class ) 387 | assert_equal( "[<f>]", f.f ) 388 | end 389 | 390 | def test_2_02 391 | assert( G ) 392 | assert_equal( "TestCut2::G", G.name ) 393 | assert( Q ) 394 | assert_equal( "TestCut2::Q", Q.name ) 395 | end 396 | 397 | def test_2_03 398 | assert_equal( [Q, G], F.cuts ) 399 | assert_equal( [Q, G], F.predecessors ) 400 | end 401 | 402 | end 403 | 404 | # 405 | 406 | class TestCut3 < Test::Unit::TestCase 407 | 408 | class C 409 | def r1; "r1"; end 410 | end 411 | 412 | cut :A < C do 413 | def r1 414 | b1( target( :r1 ){ super } ) 415 | end 416 | def b1( target ) 417 | '(' + target.super + ')' 418 | end 419 | end 420 | 421 | def test_3_01 422 | c = C.new 423 | assert_equal( '(r1)', c.r1 ) 424 | end 425 | 426 | end 427 | 428 | # Test the addition of new methods and module inclusions 429 | # after the cut is defined with dynamic joining. 430 | 431 | class TestCut4 < Test::Unit::TestCase 432 | 433 | class C 434 | def r1; "r1"; end 435 | def r2; "r2"; end 436 | def j1; "j1"; end 437 | def j2; "j2"; end 438 | end 439 | 440 | cut :A < C do 441 | 442 | join :wrappy => lambda { |jp| /^r/ =~ jp } 443 | join :square => :j1, :flare => :j2 444 | 445 | def wrappy( target ) 446 | '{'+target.super+'}' 447 | end 448 | 449 | def square(target) '['+target.super+']' end 450 | def flare(target) '*'+target.super+'*' end 451 | end 452 | 453 | class C 454 | def r3; "r3"; end 455 | end 456 | 457 | module M 458 | def r4 ; "r4"; end 459 | end 460 | 461 | class C 462 | include M 463 | end 464 | 465 | def test_4_01 466 | c = C.new 467 | assert_equal( '{r1}', c.r1 ) 468 | assert_equal( '{r2}', c.r2 ) 469 | assert_equal( '{r3}', c.r3 ) 470 | assert_equal( '{r4}', c.r4 ) 471 | end 472 | 473 | def test_4_02 474 | c = C.new 475 | assert_equal( '[j1]', c.j1 ) 476 | assert_equal( '*j2*', c.j2 ) 477 | end 478 | 479 | end 480 | 481 | # Test subclassing. 482 | 483 | class TestCut5 < Test::Unit::TestCase 484 | 485 | class C 486 | def r1; "r1"; end 487 | def r2; "r2"; end 488 | end 489 | 490 | cut :C1 < C do 491 | join :wrap1 => [:r1, :r2] 492 | 493 | def wrap1( target ) 494 | '{' + target.super + '}' 495 | end 496 | end 497 | 498 | cut :C2 < C do 499 | join :wrap2 => [:r1, :r2] 500 | 501 | def wrap2( target ) 502 | '[' + target.super + ']' 503 | end 504 | end 505 | 506 | class D < C 507 | def r1; '<' + super + '>'; end 508 | end 509 | 510 | def test_5_01 511 | c = C.new 512 | assert_equal( '[{r1}]', c.r1 ) 513 | assert_equal( '[{r2}]', c.r2 ) 514 | d = D.new 515 | assert_equal( '<[{r1}]>', d.r1 ) 516 | assert_equal( '[{r2}]', d.r2 ) 517 | end 518 | 519 | end 520 | 521 | =end 522 | -------------------------------------------------------------------------------- /work/CUT-AOP.txt: -------------------------------------------------------------------------------- 1 | == Cut-based AOP == 2 | Cuts are an object-oriented programming (OOP) concept for implementing aspect-oriented programming (AOP) in OOP languages. Cuts can be thought of as ''transparent subclasses''. Cuts were invented by Thomas Sawyer (Trans) and Peter Vanbroekhoven in considering how best to bring AOP to the Ruby programming language. However, the idea is generally applicable to any object-oriented programming language. 3 | 4 | '''NOTE: What follows is on-going re-edit of original paper on Cuts for Ruby. Over time the document should be fully edited to apply to OOPLs in general.''' 5 | 6 | == ABSTRACT == 7 | This page presents ''cut-based'' AOP, an efficient and easy-to-use approach to Aspect Oriented Programming for Ruby. 8 | 9 | The work herein is the culmination of multi-year discussion and inquiry on the topic of AOP for Ruby. It has been carried-out with the ultimate hope of establishing Ruby as a premier AOP language, if not ''the'' AOP language of choice. Since AOP is a very powerful paradigm for abstracting programming solutions into ''separate concerns'', and shows great promise for improvements in ''code maintenance'' and ''reusability'', it seems only natural that an agile language such as Ruby could provide strong support for this increasing popular pattern of design. 10 | 11 | IMPORTANT! This is a new edition of Cut-based AOP and is different from previous editions. The significant change, is that it simplifies the definition of a Cut to it's essential character --a transparent subclass. The remaining AOP support structure are transferred to an Aspect class, which is built on top of these pure Cuts. 12 | 13 | == PROBLEM == 14 | While Ruby's meta-programming facilities are powerful enough to allow for AOP-esque techniques, Ruby's lack of any ''dedicated'' AOP support makes it difficult and inefficient to apply Aspect Oriented Programming principles to application development and makes it practically impossible to do so in any conventional and thus generally reusable way. 15 | 16 | === Overview of AOP === 17 | In AOP, one considers ''aspects of concern'' applicable across multiple classes and methods. Thus AOP is said to address ''cross-cutting'' concerns. Aspects consist of ''advice'', which are methods designed to intercept other methods or events according to specified criteria. This criteria is called a ''point-cut'' and it designates a set of ''join-points''. A join-point (or ''code-point'') is the specific place within a program's execution where the advice can be inserted. In this way, AOP is thought to provide a means of organizing code '''orthogonal''' to OOP techniques. 18 | 19 | <pre> 20 | ^ 21 | | 22 | OOP | Prob Set. 23 | | 24 | +-------------> 25 | AOP 26 | </pre> 27 | 28 | The overall concept is very powerful, but likewise it can be difficult to integrate into an underlying system, easily succumbing to limitations in efficiency and subverting the intended ease-of-use and re-usability. For these reasons we believe AOP has not yet become widespread. Our design addresses these issues. 29 | 30 | === Qualifications for AOP === 31 | To qualify as an AOP capable language, the following criteria must be given considerable support: 32 | * '''Interception'''. This is the interjection of advice, adding new processing into certain locations in a system. The locations are called join-points, and advice are typically applied to a set of these points, the point-cut. While there are different types of interception, the most common by far is method-interception, whereby a method call can be supplemented before and/or after its execution. This form of interception is the minimum required of any AOP implementation. In an 100% OOP-based system, it is also the only form of interception required. 33 | * '''Introduction''' Where interception is behaviour, introduction is state. Introduction makes it possible to add further behaviour to an object, but in contrast to interception, this behaviour is not interleaved with the existing code, allowing AOP "modules" to store there own specific state. 34 | * '''Inspection''' It's important to have access to as much "control" information about a program as possible, over and above the normal internal state. In other words, meta-information. Arity is a good example of this. Other information, like what a method does, what attributes it modifies, what methods it calls, who calls the method and so on, to the greatest degree available, all further enhance the capabilities of AOP. 35 | * '''Modularization''' Not only must it be possible to intercept, introduce and inspect, it must also be possible to encapsulate. This encapsulation is the''aspect''. Aspects modularize individual cross-cutting concerns (such as persistence, undo, transactions, locking, caching and so on) into individual modules; possibly consisting of several sub-aspects, by delegation, inheritance or composition. 36 | 37 | The above four points are the functional criteria of any implementation of AOP. In addition there are three major ''means of implementation'': 38 | * '''Compile-time Preprocessing''' With this implementation, advice are weaved into a program prior to compilation or execution. As such, advice are akin to macros. This basis of AOP is the most efficient, for obvious reasons, but is also the least flexible, allowing no alteration based on runtime data. 39 | * '''Runtime Method Weaving''' Similar to Compile-time Preprocessing, but advice intercept methods dynamically at runtime. This itself can be accomplished in a few ways including simple hooks, subclassing or delegation. This is typically the most useful implementation of AOP in that it is both reasonably efficient and flexible. 40 | * '''Runtime Event Tracing''' In this form callbacks and/or tracing functions are used to intercept events, or tracepoints. While clearly the most capable basis of implementation, it also tends to be the least efficient. 41 | 42 | While the capabilities of these basis largely overlap, they admit of enough distinctions to justify independent support in accordance to the needs of the language. The first of these is generally ill suited to a highly dynamic language like Ruby (although we have recently determined that a hybrid of the first and last may be feasible), and Ruby already has some support for the third basis, albeit limited, via set_trace_func, but Ruby is hampered on the second count. This RCR focuses on the second basis, which is really the most suitable to a dynamic language like Ruby. 43 | 44 | === Design Principles === 45 | A mention before getting into the heart of this proposal: The development of this RCR has been guided by the following two important principles: 46 | * '''Consistent and Intuitive''' The initial spark of this work was the realization that AOP wrapping is equivalent to anonymous subclassing (somewhat similar to singleton classes). Utilizing this equivalency offers advantages in formal design, implementation, syntax and ease of use. 47 | * '''Make the Common Easy, and the Uncommon Possible''' The vast majority of advice is applicable to specific classes and method wrap join-points. This proposal therefore makes these convenient, while still allowing for more elaborate possibilities. 48 | 49 | == PROPOSAL == 50 | === The Cut === 51 | The first and foremost requirement of AOP is ''interception''. A few years ago it occurred to us that subclassing itself is very similar to interception. The difference was merely a matter of the visibility of the subclass. With interception, the subclass needed to have its effect ''transparently''. Indeed, ''Transparent subclassing is the fundamental proposition of this RCR.'' To accomplish it in Ruby we propose to introduce a new class called the ''Cut''. A ''cut'' is a primitive unit of aspecting. It is used to encapsulate ''advice for a single class''. Cuts are self-contained units much like classes and therefore can have their own state (introduction) as well as private auxiliary methods. Although the Cut class is very similar to the Class class, it cannot be instantiated. Rather it is used solely as an "invisible overrider". An example will help clarify. 52 | 53 | Given a class C: 54 | class C 55 | def f(*args); 1; end 56 | def g(*args); 2; end 57 | end 58 | One would normally subclass C in order to gain new functionality. 59 | class A < C 60 | def f 61 | print '{', super, '}' 62 | end 63 | end 64 | <br/> 65 | A.new.f #=> {1} 66 | But unlike a regular subclass, a cut acts transparently. So we introduce the 'cut' construction as follows. 67 | cut A < C 68 | def f 69 | print '{', super, '}' 70 | end 71 | end 72 | <br/> 73 | C.new.f #=> {1} 74 | Now, even though we have instantiated class C, we have the functional equivalent of the subclass of C, namely A. Another way of saying this is that we have ''cut-across'' the behavior of C with A. The cut is advantageous in its fine control of how advice interact with the intercepted class and its simple conformity to OOP design. By utilization of the cut AOP begins to flow naturally into ones programs. 75 | 76 | Because the Cut is essentially Class, like a Class it can also be defined anonymously, either through instantiation or as a special singleton. The anonymous definition can be especially convenient for internal wraps; useful for assertion checks, temporary tests, etc. 77 | class C 78 | def f; 9; end 79 | <br/> 80 | Cut.new(self) do 81 | def f 82 | '{' + super + '}' 83 | end 84 | end 85 | end 86 | <br/> 87 | C.new.f #=> {9} 88 | Or through the special singleton form, 89 | c = Object.new 90 | <br/> 91 | def c.f; 8; end 92 | <br/> 93 | cut << c 94 | def f 95 | '{' + super + '}' 96 | end 97 | end 98 | <br/> 99 | c.f #=> {8} 100 | Additionally, Cuts exist in proxy form to allow modules to be "premixed". This is analogous to proxy classes which allow modules to mixin to the class hierarchy. So too does a proxy-cut include a module, albeit preclusive rather the inclusive in its effect. We offer the module command #preclude to serve as designator of this purpose. 101 | module A 102 | def f ; "<#{super}>" ; end 103 | end 104 | <br/> 105 | Class T 106 | preclude A 107 | def f ; "okay" ; end 108 | end 109 | <br/> 110 | T.new.f #=> "<okay>" 111 | The Cut class is at the heart of this proposal. The remaining sections build on this basic device, demonstrating how to use it for AOP, and offers some important complementary suggestions to make Ruby more convenient with regard to it and AOP requirements in general. 112 | 113 | ==== Crosscutting & Targeting ==== 114 | A cut is useful for applying advice which intercept the methods of a single class. But to provide the full advantage of AOP we must also be able to cut-across multiple classes. The simplest means of cross-cutting is by use of a shared module. A shared module can serve as a simple ''aspect'' by its inclusion in a cut for each class. 115 | class C 116 | def f; 'C'; end 117 | end 118 | <br/> 119 | class D 120 | def f; 'D'; end 121 | end 122 | <br/> 123 | module A 124 | def f 125 | '{' + super + '}' 126 | end 127 | end 128 | <br/> 129 | cut Ac < C ; include A ; end 130 | cut Ad < D ; include A ; end 131 | <br/> 132 | C.new.f #-> {C} 133 | D.new.f #-> {D} 134 | Using a cut, advice intercept methods of the same name and use #super to call back to those methods --the basics of subclassing. But for advice to be fully reusable it must be possible to designate alternate method-to-advice mapping. The simplest way to do this is by calling secondary methods, as one might normally do within a class. 135 | cut A < C 136 | def f 137 | bracket 138 | end 139 | <br/> 140 | def g 141 | bracket 142 | end 143 | <br/> 144 | def bracket 145 | '{' + super + '}' # PROBLEM! 146 | end 147 | end 148 | But notice the problem that arises. Super will not be directed to <code>f</code> or <code>g</code> in class C, but to <code>bracket</code> which isn't defined in C. This is not the desired result. A presently possible way to correct this is to pass a closure on the super call of the ''target method''. 149 | cut A < C 150 | def f 151 | bracket( lambda{super} ) 152 | end 153 | <br/> 154 | def g 155 | bracket( lambda{super} ) 156 | end 157 | <br/> 158 | def bracket( target ) 159 | '{' + target.call + '}' 160 | end 161 | end 162 | This works well enough, though one must be careful to avoid name clashes between advice and methods in classes being cut, but it is a rather brutish; nor does it provide any significant ''inspection''. We can improvement upon this by passing the target method itself, but ''enhanced'' to provide the current super context, and usefully, its own name. We might define a method to provide this with something like: 163 | def target_method(name,&block) 164 | m = method(name) 165 | m.send(:define_method, :name, name) 166 | m.send(:define_method, :super, &block) 167 | m 168 | end 169 | Then we can use it as follows. 170 | cut A < C 171 | def f 172 | bracket( target(:f){super} ) 173 | end 174 | <br/> 175 | def g 176 | bracket( target(:g){super} ) 177 | end 178 | <br/> 179 | def bracket( target ) 180 | puts 'Advising #{target.name}...' 181 | '{' + target.super + '}' 182 | end 183 | end 184 | This technique may be common enough to warrant the introduction of a keyword just for the purpose, perhaps the term <code>this</code> would be a good choice. With "this" in place, the above example can be nicely simplified. 185 | cut A < C 186 | def f 187 | bracket( this ) 188 | end 189 | def g 190 | bracket( this ) 191 | end 192 | def bracket( target ) 193 | puts 'Advising #{target.name}... 194 | '{' + target.super + '}' 195 | end 196 | end 197 | The special call #this could also carry a method's call parameters and block if given; it could even be queried as <code>this.block_given?</code>. 198 | 199 | ==== Limitations of Cuts ==== 200 | At this point we reached the extent to which Cuts can provide AOP. Cuts are a robust technique provide ''unit-AOP'', ie. per-class interception. This is a powerful tool applicabe to many uses cases. However, to go further we need to look at the two limitations of cuts. 201 | 202 | FIRST. Advising multiple methods with a single advice, as we have done in the above examples, is a common case of AOP, a convenient means of redirecting target methods to advice is essential. It is trivial to define a method like the following <code>Cut#redirect_advice</code>: 203 | class Cut 204 | def redirect_advice( h ) 205 | c = h.collect { |k,v| 206 | "def #{k}(*a,&b) #{v}(this,*a, &b); end" 207 | } 208 | module_eval c.join("\n") 209 | end 210 | end 211 | <br/> 212 | cut A < C 213 | redirect_advice :f => :bracket, :g => :bracket 214 | def bracket( target ) 215 | '{' + target.super + '}' 216 | end 217 | end 218 | However, it not sufficient for dealing with Ruby's dynamicism. It will only handles methods defined in the target class at the moment the cut is defined. Complete AOP support requires the advice always stay in sync even under dynamic alteration of the targeted class. Ruby already provides means for this via the Module#method_added hook, but robust use of this technique is inconvenient at best. So a proper advice-oriented techinique is neede 219 | 220 | SECOND. When using redirected advice or, more importantly, when using modules as reusable aspects: care must be taken in choosing method names so as not to inadvertently interfere with the methods of the class(es) being cut. This can be a problem because it inhibits code reuse, i.e. the ability to design components without regard to where they may be applied. For example: 221 | class C 222 | def m ; "M" ; end 223 | def w ; "W" ; end 224 | def d ; w ; end 225 | end 226 | <br/> 227 | module MA 228 | def w( target ) 229 | '{' + target.super + '}' 230 | end 231 | end 232 | <br/> 233 | cut A < C 234 | include MA 235 | def m ; w( this ) ; end 236 | end 237 | C.new.d #=> "{W}" 238 | In this case, #d does not return "W" as expected, but rather "{W}" because the advice in MA caused an unexpected name clash with the #w method in C. To fulfil the true abstraction and re-usability potential of AOP this issue ''must'' be remedied. 239 | 240 | One remedy comes from Ruby's ability to dynamically manipulate class/module definitions on the fly, in other words, "sub-classing" the aspect module and applying any required name revisions to avoid the unwanted name clash. 241 | class C 242 | def m ; "M" ; end 243 | def w ; "W" ; end 244 | def d ; w ; end 245 | end 246 | <br/> 247 | module MA 248 | def w( target ) 249 | '{' + target.super + '}' 250 | end 251 | end 252 | <br/> 253 | module MArC 254 | include MA 255 | rename_method :q, :w 256 | end 257 | <br/> 258 | cut A < C 259 | include MArC 260 | def m ; q( this ) ; end 261 | end 262 | The @#rename_method@ effectively alias the original method and undefines it in one call. This solves the clash problem in a very controllable way, which is nice. We can even make it more convenient by defining some helper traits like methods. For instance: 263 | cut A < C 264 | include MA * { :q => :w } 265 | def m ; q( this ) ; end 266 | end 267 | This kind of solution largely address the name clash issue, but it is still less then optimal. 268 | 269 | === The Aspect === 270 | To address the limitation of the Cut, we take the next natural step in supporting AOP and create the Aspect. Aspects are similar to Cuts, in fact they can be built via delegation to Cuts, but they are a higher-level structure and support all the AOP features most are accustom, such a pointcuts, join-points and multi-class cross-cutting. An Aspect basically takes the Cut class, builds-in all the target features we handled by hand in Cuts, adds flow control methods for handling Ruby's dynamicism and provides a wholly separate area of encapsulation, which avoids any name clashing. 271 | 272 | The primary distinction of Aspects is the #join method, which identifies which methods are to be advised but what advice. 273 | class C 274 | def f ; "F" ; end 275 | def g ; "G" ; end 276 | end 277 | <br/> 278 | aspect A 279 | join :f => :bracket, :g => :bracket 280 | def bracket( target ) 281 | '{' + target.super + '}' 282 | end 283 | end 284 | <br/> 285 | A.apply_to(C) 286 | The #join method would also accept wild cards. 287 | aspect A 288 | join '*' => :bracket 289 | <br/> 290 | def bracket( target ) 291 | '{' + target.super + '}' 292 | end 293 | end 294 | An it can also take a block which allows us to work with join-points. In the code below, @jp@ is a JoinPoint object. 295 | Xa = Aspect.new do 296 | join :x do |jp| 297 | jp.name == :f or jp.name == :g 298 | end 299 | <br/> 300 | def x(target); '{' + target.super + '}'; end 301 | end 302 | A JoinPoint object is very similar to an internal Ruby frame, and provides parameters based on the targeted method plus many of the same parameters that #set_trace_func can use: event, file, line, id, binding, classname. Though some of these may be omitted for performance reasons. 303 | 304 | We don't necessarily need Aspects to cross-cut large swaths of classes. Ruby's built-in reflexion provides means via ObjectSpace. 305 | ObjectSpace.each_object(Class) { |c| 306 | if c.instance_methods(false).include?(:to_s) 307 | Cut.new(c) do 308 | def :to_s 309 | super.upcase + "!" 310 | end 311 | end 312 | end 313 | end 314 | <br/> 315 | "a lot of shouting for joy".to_s #=> "A LOT OF SHOUTING FOR JOY!" 316 | However system-wide effects must by definition be more robust as we can't always account for the nature of each class. So Aspects are much more appropriate to this use. To facilitate this, Aspects offer the #pointcut method. 317 | aspect A 318 | join :to_s => :to_s 319 | def to_s(target) 320 | target.super.upcase + "!" 321 | end 322 | end 323 | <br/> 324 | A.pointcut do |pc| 325 | true if pc.instance_methods(false).include?(:to_s) 326 | end 327 | There are plenty of great applications for broad cross-cutting like this, especially in the way of code inspection, unit testing, debugging, etc. The Aspect is a vital part to AOP as it provides the dynamic flexibility that is required of complete Ruby AOP solution. 328 | 329 | == ANALYSIS == 330 | The Cut and its supporting infrastructure as described above is designed to be a very robust, easy to use, and efficient, providing better overall AOP support than any other language presently in common use. 331 | 332 | In contrast, the traditional approach taken by the most AOP systems today, largely propagated by early implementations like Aspect/J, have proven unwieldy and ironically end-up inhibiting code reuse. Infact, the limited reusabiliy has been speculated elsewhere as a potential primary culprit in the limited penetration of AOP to date. This proposal circumvents these issues by offering a general solution directly integrated into the OOP system, rather than attempting to operate wholly beyond it. 333 | 334 | Cuts also trump simple method-wrapping mechanisms, like those proposed in Matz' RUbyConf 2004 presentation. While method hooks are especially convenient, they are weak with regards to SOC (Separation Of Concerns); most notably, method hooks lack ''introduction'' altogether. They also suffer from order of execution ambiguities that must be dealt with by imposing limitations or adding increasingly specialized declarations. Cuts again circumvent these issues by utilizing an inherent OOP construct --the subclass, rather than adding on a new, wholly "other" entity. 335 | === Pros === 336 | * Provides a robust method-wrapping solution, devoid of order ambiguities. 337 | * Clear separation of concerns using separate cuts and modular aspects. 338 | * Based on standard OOP devices, i.e. cuts are essentially subclasses. 339 | * Cut-based AOP is very easy to understand and thus to use. 340 | * Implementation is efficient. 341 | === Cons === 342 | * Cut syntax is a slightly more verbose and entails more overhead than simple method hooks. [Counterpoint: But the difference ''is'' minor. Simple methods hooks could, in point of fact, be implemented via cuts with efficiencies quite close to a simple hook solution.] 343 | * Cannot advise large numbers of classes in a single ''dedicated'' clause. [Counterpoint: Cuts are specifically designed ''not'' to do this as the inability also has advantage, not the least of which is performance efficiency. Moreover it has been discovered that such large swaths of cross-cutting are in actuality the uncommon usecases, more suitable to specialized applications like unit-testing and profiling.] 344 | * AOP traditionalists might be a bit taken aback by the Cut-based approach in that it does not specifically reference join-points and pointcuts. [Counerpoint: As above, cuts provide a better approach for the most common AOP usecases.] 345 | * Until local instance variables are available, and/or local instance methods, ''introduction'' is not as strong as it really needs to be for good AOP coverage. [Counterpoint: We suspect this is likely to be addressed in a future version of Ruby.] 346 | 347 | == IMPLEMENTAITON == 348 | One implementation detail, not specifically decided by this proposal, is whether cuts may or may not be applied to other cuts. If not allowed, once a cut is applied to a class, a subsequent cut can not be slipped in between it and that class. Cuts are intended to work transparently and offering this feature could thwart this principle. On the other hand, if allowed, it would provided a means for a cut to "underwrite" another cut providing greater flexibility in "meta"-controlling the effects of cuts. 349 | 350 | Another implementation detail to consider that falls outside the strict scope of this proposal, but that goes a long way toward bolstering it, is the limits on ''introduction'' due to the non-locality of instance variables and methods. Presently cuts will only be able to provide introduction through class variables --useful but weak by comparison. With the advent of locals in a future version of Ruby, cuts would gain robust introduction strengths. 351 | 352 | The first real step in implementation is, of course, the creation of the transparent subclass, the Cut. This requires an addition in the structure of an object's class hierarchy; essentially a new pointer to a chain of cuts, the last pointing back at the cut class itself --a very simpleton explanation to be sure. But fortunately, a well written Ruby patch has been coded by Peter Vanbroekhoven. It implements most of the core functionality described here, and should serve as a means to investigate and test the potential utility of this RCR. It may also serve as a basis for including these AOP features into Ruby proper, should this RCR be accepted. At this time the patch applies to Ruy 1.8.2 and can be download from [http://rubyforge.org/projects/suby/ here] under "transparent subclass (cut)". -------------------------------------------------------------------------------- /RCR.textile: -------------------------------------------------------------------------------- 1 | h1. Cut-based AOP 2 | 3 | by Trans and Peter Vanbroekhoven (rev. 71) 4 | 5 | <a href="http://creativecommons.org/licenses/by-sa/3.0/">© CA BY-SA</a> 6 | 7 | 8 | h2. ABSTRACT 9 | 10 | This RCR presents <i>cut-based</i> AOP, an efficient and easy-to-use approach to Aspect Oriented Programming for Ruby. 11 | 12 | The work herein is the culmination of multi-year discussion and inquiry on the topic of AOP for Ruby. It has been carried-out with the ultimate hope of establishing Ruby as a premier AOP language, if not <i>the</i> AOP language of choice. Since AOP is a very powerful paradigm for abstracting programming solutions into <i>separate concerns</i>, and shows great promise for improvements in <i>code maintenance</i> and <i>reusability</i>, it seems only natural that an agile language such as Ruby could provide strong support for this increasing popular pattern of design. 13 | 14 | 15 | h2. PROBLEM 16 | 17 | While Ruby's meta-programming facilities are powerful enough to allow for AOP-esque techniques, Ruby's lack of any <i>dedicated</i> AOP support makes it difficult and inefficient to apply Aspect Oriented Programming principles to application development and makes it practically impossible to do so in any conventional and thus generally reusable way. 18 | 19 | h3. Overview of AOP 20 | 21 | In AOP, one considers <i>aspects of concern</i> applicable across multiple classes and methods. Thus AOP is said to address <i>cross-cutting</i> concerns. Aspects consist of <i>advice</i>, which are methods designed to intercept other methods or events according to specified criteria. This criteria is called a <i>point-cut</i> and it designates a set of <i>join-points</i>. A join-point (or <i>code-point</i>) is the specific place within a program's execution where the advice can be inserted. In this way, AOP is thought to provide a means of organizing code <b>orthogonal</b> to OOP techniques. 22 | 23 | <pre> 24 | ^ 25 | | 26 | OOP | Prob Set. 27 | | 28 | +-------------> 29 | AOP 30 | </pre> 31 | 32 | The overall concept is very powerful, but likewise it can be difficult to integrate into an underlying system, easily succumbing to limitations in efficiency and subverting the intended ease-of-use and reusability. For these reasons we believe AOP has not yet become widespread. Our design addresses these issues. 33 | 34 | h3. Qualifications for AOP 35 | 36 | To qualify as an AOP capable language, the following criteria must be given considerable support: 37 | 38 | * <b>Interception</b>. This is the interjection of advice, adding new processing into certain locations in a system. The locations are called join-points, and advice are typically applied to a set of these points, the point-cut. While there are different types of interception, the most common by far is method-interception, whereby a method call can be supplemented before and/or after its execution. This form of interception is the minimum required of any AOP implementation. In an 100% OOP-based system, it is also the only form of interception required. 39 | * <b>Introduction</b> Where interception is behaviour, introduction is state. Introduction makes it possible to add further behaviour to an object, but in contrast to interception, this behaviour is not interleaved with the existing code, allowing AOP "modules" to store there own specific state. 40 | * <b>Inspection</b> It's important to have access to as much "control" information about a program as possible, over and above the normal internal state. In other words, meta-information. Arity is a good example of this. Other information, like what a method does, what attributes it modifies, what methods it calls, who calls the method and so on, to the greatest degree available, all further enhance the capabilities of AOP. 41 | * <b>Modularization</b> Not only must it be possible to intercept, introduce and inspect, it must also be possible to encapsulate. This encapsulation is the <i>aspect</i>. Aspects modularize individual cross-cutting concerns (such as persistence, undo, transactions, locking, caching and so on) into individual modules; possibly consisting of several sub-aspects, by delegation, inheritance or composition. 42 | 43 | The above four points are the functional criteria of any implementation of AOP. In addition there are three major <i>means of implementation</i>: 44 | 45 | * <b>Compile-time Preprocessing</b> With this implementation, advice are weaved into a program prior to compilation or execution. As such, advice are akin to macros. This basis of AOP is the most efficient, for obvious reasons, but is also the least flexible, allowing no alteration based on runtime data. 46 | * <b>Runtime Method Weaving</b> Similar to Compile-time Preprocessing, but advice intercept methods dynamically at runtime. This itself can be accomplished in a few ways including simple hooks, subclassing or delegation. This is typically the most useful implementation of AOP in that it is both reasonably efficient and flexible. 47 | * <b>Runtime Event Tracing</b> In this form callbacks and/or tracing functions are used to intercept events, or tracepoints. While clearly the most capable basis of implementation, it also tends to be the least efficient. 48 | 49 | While the capabilities of these basis largely overlap, they admit of enough distinctions to justify independent support in accordance to the needs of the language. The first of these is generally ill suited to a highly dynamic language like Ruby (although we have recently determined that a hybrid of the first and last may be feasible), and Ruby already has some support for the third basis, albeit limited, via set_trace_func, but Ruby is hampered on the second count. This RCR focuses on the second basis, which is really the most suitable to a dynamic language like Ruby. 50 | 51 | h3. Design Principles 52 | 53 | A mention before getting into the heart of this proposal: The development of this RCR has been guided by the following two important principles: 54 | 55 | * <b>Consistent and Intuitive</b> The initial spark of this work was the realization that AOP wrapping is equivalent to anonymous subclassing (somewhat similar to singleton classes). Utilizing this equivalency offers advantages in formal design, implementation, syntax and ease of use. 56 | * <b>Make the Common Easy, and the Uncommon Possible</b> The vast majority of advice is applicable to specific classes and method wrap join-points. This proposal therefore makes these convenient, while still allowing for more elaborate possibilities. 57 | 58 | 59 | h2. PROPOSAL 60 | 61 | h3. The Cut 62 | 63 | The first and foremost requirement of AOP is <i>interception</i>. A few years ago it occurred to us that subclassing itself is very similar to interception. The difference was merely a matter of the visibility of the subclass. With interception, the subclass needed to have its effect <i>transparently</i>. Indeed, <i>Transparent subclassing is the fundamental proposition of this RCR.</i> To accomplish it in Ruby we propose to introduce a new class called the <i>Cut</i>. A <i>cut</i> is a primitive unit of aspecting. It is used to encapsulate <i>advice for a single class</i>. Cuts are self-contained units much like classes and therefore can have their own state (introduction) as well as private auxiliary methods. Although the Cut class is very similar to the Class class, it cannot be instantiated. Rather it is used solely as an "invisible overrider". An example will help clarify. 64 | 65 | Given a class C: 66 | 67 | <pre> 68 | class C 69 | def f(*args); 1; end 70 | def g(*args); 2; end 71 | end 72 | </pre> 73 | 74 | One would normally subclass C in order to gain new functionality. 75 | 76 | <pre> 77 | class A < C 78 | def f 79 | print '{', super, '}' 80 | end 81 | end 82 | 83 | A.new.f #=> {1} 84 | </pre> 85 | 86 | But unlike a regular subclass, a cut acts transparently. So we introduce the 'cut' construction as follows. 87 | 88 | <pre> 89 | cut A < C 90 | def f 91 | print '{', super, '}' 92 | end 93 | end 94 | 95 | C.new.f #=> {1} 96 | </pre> 97 | 98 | Now, even though we have instantiated class C, we have the functional equivalent of the subclass of C, namely A. Another way of saying this is that we have <i>cut-across</i> the behaviour of C with A. The cut is advantageous in its fine control of how advice interact with the intercepted class and its simple conformity to OOP design. By utilization of the cut AOP begins to flow naturally into ones programs. 99 | 100 | Because the Cut is essentially Class, like a Class it can also be defined anonymously, either through instantiation or as a special singleton. The anonymous definition can be especially convenient for internal wraps; useful for assertion checks, temporary tests, etc. 101 | 102 | <pre> 103 | class C 104 | def f; 9; end 105 | 106 | Cut.new(self) do 107 | def f 108 | '{' + super + '}' 109 | end 110 | end 111 | end 112 | 113 | C.new.f #=> {9} 114 | </pre> 115 | 116 | Or through the special singleton form, 117 | 118 | <pre> 119 | c = Object.new 120 | 121 | def c.f; 8; end 122 | 123 | cut << c 124 | def f 125 | '{' + super + '}' 126 | end 127 | end 128 | 129 | c.f #=> {8} 130 | </pre> 131 | 132 | Additionally, Cuts exist in proxy form to allow modules to be "premixed". This is analogous to proxy classes which allow modules to mixin to the class hierarchy. So too does a proxy-cut include a module, albeit preclusive rather the inclusive in its effect. We offer the module command #preclude to serve as designator of this purpose. 133 | 134 | <pre> 135 | module A 136 | def f ; "<#{super}>" ; end 137 | end 138 | 139 | Class T 140 | preclude A 141 | def f ; "okay" ; end 142 | end 143 | 144 | T.new.f #=> "<okay>" 145 | </pre> 146 | 147 | 148 | The Cut class is at the heart of this proposal. The remaining sections build on this basic device, demonstrating how to use it for AOP, and offers some important complementary suggestions to make Ruby more convenient with regard to it and AOP requirements in general. 149 | 150 | h4. Crosscutting & Targeting 151 | 152 | A cut is useful for applying advice which intercept the methods of a single class. But to provide the full advantage of AOP we must also be able to cut-across multiple classes. The simplest means of cross-cutting is by use of a shared module. A shared module can serve as a simple <i>aspect</i> by its inclusion in a cut for each class. 153 | 154 | <pre> 155 | class C 156 | def f; 'C'; end 157 | end 158 | 159 | class D 160 | def f; 'D'; end 161 | end 162 | 163 | module A 164 | def f 165 | '{' + super + '}' 166 | end 167 | end 168 | 169 | cut Ac < C ; include A ; end 170 | cut Ad < D ; include A ; end 171 | 172 | C.new.f #-> {C} 173 | D.new.f #-> {D} 174 | </pre> 175 | 176 | 177 | Using a cut, advice intercept methods of the same name and use #super to call back to those methods --the basics of subclassing. But for advice to be fully reusable it must be possible to designate alternate method-to-advice mapping. The simplest way to do this is by calling secondary methods, as one might normally do within a class. 178 | 179 | <pre> 180 | cut A < C 181 | def f 182 | bracket 183 | end 184 | def g 185 | bracket 186 | end 187 | def bracket 188 | '{' + super + '}' # PROBLEM! 189 | end 190 | end 191 | </pre> 192 | 193 | But notice the problem that arises. Super will not be directed to <code>f</code> or <code>g</code> in class C, but to <code>bracket</code> which isn't defined in C. This is not the desired result. A presently possible way to correct this is to pass a closure on the super call of the <i>target method</i>. 194 | 195 | <pre> 196 | cut A < C 197 | def f 198 | bracket( lambda{super} ) 199 | end 200 | def g 201 | bracket( lambda{super} ) 202 | end 203 | def bracket( target ) 204 | '{' + target.call + '}' 205 | end 206 | end 207 | </pre> 208 | 209 | This works well enough, though one must be careful to avoid name clashes between advice and methods in classes being cut, but it is a rather brutish; nor does it provide any significant <i>inspection</i>. We can improvement upon this by passing the target method itself, but <i>enhanced</i> to provide the current super context, and usefully, its own name. We might define a method to provide this with something like: 210 | 211 | <pre> 212 | def target_method(name,&block) 213 | m = method(name) 214 | m.send(:define_method, :name, name) 215 | m.send(:define_method, :super, &block) 216 | m 217 | end 218 | </pre> 219 | 220 | Then we can use it as follows. 221 | 222 | <pre> 223 | cut A < C 224 | def f 225 | bracket( target(:f){super} ) 226 | end 227 | def g 228 | bracket( target(:g){super} ) 229 | end 230 | def bracket( target ) 231 | puts 'Advising #{target.name}...' 232 | '{' + target.super + '}' 233 | end 234 | end 235 | </pre> 236 | 237 | This technique may be common enough to warrant the introduction of a keyword just for the purpose, perhaps the term <code>this</code> would be a good choice. With "this" in place, the above example can be nicely simplified. 238 | 239 | <pre> 240 | cut A < C 241 | def f 242 | bracket( this ) 243 | end 244 | def g 245 | bracket( this ) 246 | end 247 | def bracket( target ) 248 | puts 'Advising #{target.name}... 249 | '{' + target.super + '}' 250 | end 251 | end 252 | </pre> 253 | 254 | The special call #this could also carry a method's call parameters and block if given; it could even be queried as <code>this.block_given?</code>. 255 | 256 | 257 | h4. Limitations of Cuts 258 | 259 | At this point we reached the extent to which Cuts can provide AOP. Cuts are a robust technique provide <i>unit-AOP</i>, ie. per-class interception. This is a powerful tool applicabe to many uses cases. However, to go further we need to look at the two limitations of cuts. 260 | 261 | FIRST. Advising multiple methods with a single advice, as we have done in the above examples, is a common case of AOP, a convenient means of redirecting target methods to advice is essential. It is trivial to define a method like the following <code>Cut#redirect_advice</code>: 262 | 263 | <pre> 264 | class Cut 265 | def redirect_advice( h ) 266 | c = h.collect { |k,v| 267 | "def #{k}(*a,&b) #{v}(this,*a, &b); end" 268 | } 269 | module_eval c.join("\n") 270 | end 271 | end 272 | 273 | cut A < C 274 | redirect_advice :f => :bracket, :g => :bracket 275 | def bracket( target ) 276 | '{' + target.super + '}' 277 | end 278 | end 279 | </pre> 280 | 281 | However, it not sufficient for dealing with Ruby's dynamicism. It will only handle methods defined in the target class at the moment the cut is defined. Complete AOP support requires the advice always stay in sync even under dynamic alteration of the targeted class. Ruby already provides means for this via the Module#method_added hook, but robust use of this technique is inconvenient at best. So a proper advice-oriented techinique would be preferable. 282 | 283 | SECOND. When using redirected advice or, more importantly, when using modules as reusable aspects: care must be taken in choosing method names so as not to inadvertently interfere with the methods of the class(es) being cut. This can be a problem because it inhibits code reuse, i.e. the ability to design components without regard to where they may be applied. For example: 284 | 285 | <pre> 286 | class C 287 | def m ; "M" ; end 288 | def w ; "W" ; end 289 | def d ; w ; end 290 | end 291 | 292 | module MA 293 | def w( target ) 294 | '{' + target.super + '}' 295 | end 296 | end 297 | 298 | cut A < C 299 | include MA 300 | def m ; w( this ) ; end 301 | end 302 | 303 | C.new.d #=> "{W}" 304 | </pre> 305 | 306 | In this case, #d does not return "W" as expected, but rather "{W}" because the advice in MA caused an unexpected name clash with the #w method in C. To fulfil the true abstraction and re-usability potential of AOP it would help to remedy this issue. 307 | 308 | One remedy comes from Ruby's ability to dynamically manipulate class/module definitions on the fly, in other words, "sub-classing" the aspect module and applying any required name revisions to avoid the unwanted name clash. 309 | 310 | <pre> 311 | class C 312 | def m ; "M" ; end 313 | def w ; "W" ; end 314 | def d ; w ; end 315 | end 316 | 317 | module MA 318 | def w( target ) 319 | '{' + target.super + '}' 320 | end 321 | end 322 | 323 | module MArC 324 | include MA 325 | rename_method :q, :w 326 | end 327 | 328 | cut A < C 329 | include MArC 330 | def m ; q( this ) ; end 331 | end 332 | </pre> 333 | 334 | The @#rename_method@ effectively alias the original method and undefines it in one call. This solves the clash problem in a very controllable way, which is nice. We can even make it more convenient by defining some helper traits like methods. For instance: 335 | 336 | <pre> 337 | cut A < C 338 | include MA * { :q => :w } 339 | def m ; q( this ) ; end 340 | end 341 | </pre> 342 | 343 | This kind of solution largely address the name clash issue, but it is still less then optimal. Possibly a better approach can be found. 344 | 345 | 346 | h3. The Aspect 347 | 348 | One way to address the limitation of the Cut, is to take the next natural step in supporting a full AOP system and create the Aspect. Aspects are similar to Cuts, in fact they can be built via delegation to Cuts, but they are a higher-level structure and support all the AOP features most are accustom, such a pointcuts, join-points and multi-class cross-cutting. An Aspect basically takes the Cut class, builds-in all the target features we handled by hand in Cuts, adds flow control methods for handling Ruby's dynamicism and provides a wholly separate area of encapsulation, which avoids any name clashing. 349 | 350 | The primary distinction of Aspects is the #join method, which identifies which methods are to be advised but what advice. 351 | 352 | <pre> 353 | class C 354 | def f ; "F" ; end 355 | def g ; "G" ; end 356 | end 357 | 358 | aspect A 359 | join :f => :bracket, :g => :bracket 360 | def bracket( target ) 361 | '{' + target.super + '}' 362 | end 363 | end 364 | 365 | A.apply_to(C) 366 | </pre> 367 | 368 | The #join method would also accept wild cards. 369 | 370 | <pre> 371 | aspect A 372 | join '*' => :bracket 373 | def bracket( target ) 374 | '{' + target.super + '}' 375 | end 376 | end 377 | </pre> 378 | 379 | An it can also take a block which allows us to work with join-points. In the code below, @jp@ is a JoinPoint object. 380 | 381 | <pre> 382 | Xa = Aspect.new do 383 | join :x do |jp| 384 | jp.name == :f or jp.name == :g 385 | end 386 | 387 | def x(target); '{' + target.super + '}'; end 388 | end 389 | </pre> 390 | 391 | A JoinPoint object is very similar to an internal Ruby frame, and provides parameters based on the targeted method plus many of the same parameters that #set_trace_func can use: event, file, line, id, binding, classname. Though some of these may be omitted for performance reasons. 392 | 393 | We don't necessarily need Aspects to cross-cut large swaths of classes. Ruby's built-in reflexion provides means via ObjectSpace. 394 | 395 | <pre> 396 | ObjectSpace.each_object(Class) { |c| 397 | if c.instance_methods(false).include?(:to_s) 398 | Cut.new(c) do 399 | def :to_s 400 | super.upcase + "!" 401 | end 402 | end 403 | end 404 | end 405 | 406 | "a lot of shouting for joy".to_s #=> "A LOT OF SHOUTING FOR JOY!" 407 | </pre> 408 | 409 | However system-wide effects must by definition be more robust as we can't always account for the nature of each class. So Aspects are much more appropriate to this use. To facilitate this, Aspects offer the #pointcut method. 410 | 411 | <pre> 412 | aspect A 413 | join :to_s => :to_s 414 | def to_s(target) 415 | target.super.upcase + "!" 416 | end 417 | end 418 | 419 | A.pointcut do |pc| 420 | true if pc.instance_methods(false).include?(:to_s) 421 | end 422 | </pre> 423 | 424 | There are plenty of great applications for broad cross-cutting like this, especially in the way of code inspection, unit testing, debugging, etc. The Aspect is an important part to AOP as it provides the dynamic flexibility that is required of complete Ruby AOP solution. 425 | 426 | 427 | h2. ANALYSIS 428 | 429 | The Cut and its supporting infrastructure as described above is designed to be a very robust, easy to use, and efficient, providing better overall AOP support than any other language presently in common use. 430 | 431 | In contrast, the traditional approach taken by the most AOP systems today, largely propagated by early implementations like Aspect/J, have proven unwieldy and ironically end-up inhibiting code reuse. Infact, the limited reusabiliy has been speculated elsewhere as a potential primary culprit in the limited penetration of AOP to date. This proposal circumvents these issues by offering a general solution directly integrated into the OOP system, rather than attempting to operate wholly beyond it. 432 | 433 | Cuts also trump simple method-wrapping mechanisms, like those proposed in Matz' RUbyConf 2004 presentation. While method hooks are especially convenient, they are weak with regards to SOC (Separation Of Concerns); most notably, method hooks lack <i>introduction</i> altogether. They also suffer from order of execution ambiguities that must be dealt with by imposing limitations or adding increasingly specialized declarations. Cuts again circumvent these issues by utilizing an inherent OOP construct --the subclass, rather than adding on a new, wholly "other" entity. 434 | 435 | h3. Pros 436 | 437 | * Provides a robust method-wrapping solution, devoid of order ambiguities. 438 | * Clear separation of concerns using separate cuts and modular aspects. 439 | * Based on standard OOP devices, i.e. cuts are essentially subclasses. 440 | * Cut-based AOP is very easy to understand and thus to use. 441 | * Implementation is efficient. 442 | 443 | h3. Cons 444 | 445 | * Cut syntax is a slightly more verbose and entails more overhead than simple method hooks. [Counterpoint: But the difference <i>is</i> minor. Simple methods hooks could, in point of fact, be implemented via cuts with efficiencies quite close to a simple hook solution.] 446 | * Cannot advise large numbers of classes in a single <i>dedicated</i> clause. [Counterpoint: Cuts are specifically designed <i>not</i> to do this as the inability also has advantage, not the least of which is performance efficiency. Moreover it has been discovered that such large swaths of cross-cutting are in actuality the uncommon usecases, more suitable to specialized applications like unit-testing and profiling.] 447 | * AOP traditionalists might be a bit taken aback by the Cut-based approach in that it does not specifically reference join-points and pointcuts. [Counerpoint: As above, cuts provide a better approach for the most common AOP usecases.] 448 | * Until local instance variables are available, and/or local instance methods, <i>introduction</i> is not as strong as it could be for good AOP coverage. [Counterpoint: Simply using well thought out naming schemes can largely take care of this. It might also be more directly addressed in a future version of Ruby via proposed `@_` variables.] 449 | 450 | 451 | 452 | h2. IMPLEMENTAITON 453 | 454 | One implementation detail, not specifically decided by this proposal, is whether cuts may or may not be applied to other cuts. If not allowed, once a cut is applied to a class, a subsequent cut can not be slipped in between it and that class. Cuts are intended to work transparently and offering this feature could thwart this principle. On the other hand, if allowed, it would provided a means for a cut to "underwrite" another cut providing greater flexibility in "meta"-controlling the effects of cuts. 455 | 456 | Another implementation detail to consider that falls outside the strict scope of this proposal, but that goes a long way toward bolstering it, is the limits on <i>introduction</i> due to the non-locality of instance variables and methods. Presently cuts will only be able to provide introduction through class varaibles --useful but weak by comparision. With the advent of locals in a future version of Ruby, cuts would gain robust introduction strengths. 457 | 458 | The first real step in implementation is, of course, the creation of the transparent subclass, the Cut. This requires an addition in the structure of an object's class hierarchy; essentially a new pointer to a chain of cuts, the last pointing back at the cut class itself --a very simpleton explanation to be sure. But fortunately, a well written Ruby patch has been coded by Peter Vanbroekhoven. It implements most of the core funtionality described here, and should serve as a means to investigate and test the potential utility of this RCR. It may also serve as a basis for including these AOP features into Ruby proper, should this RCR be accepted. At this time the patch applys to Ruy 1.8.2 and can be download from "here":HTTP://rubyforge.org/projects/suby/ under "transparent subclass (cut)". 459 | 460 | --------------------------------------------------------------------------------