aop 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 71f2faab1dfdd248cc8a9c2aea4c166106dbd1e3
4
- data.tar.gz: fe1c1dc3713fa1789c7ace4c12c5f55c8b51c428
3
+ metadata.gz: af9f44c8cac54ec4197e54a135d3c6829b8badd6
4
+ data.tar.gz: f9ffd78f10555e9ef4f09c2d14231d7248f4c82f
5
5
  SHA512:
6
- metadata.gz: f9fc488c1c3c6ca225f89f24f934c051141ddbfbcb5dd3eaac203a679f55a73967c019830a53651c77fa692fbe5e0b8527d597bcf6fceb4f51c766ce2968e534
7
- data.tar.gz: bcbb2ab9c96b2fde4cdf45727ba7f426c5a26963cef3fc6bd6804f593519688ce327a16bec838d6aa3b1a53c24c8c22d86c780c31866581097fc7f682df78cfc
6
+ metadata.gz: f24dd40907b9bd09ac43e491651cfb64cedaa4044363d99fba028e3b23c85afe2a5d1a12baa6d2c440a6e7dec0aec8f65413048753584183cb63efb8a8451b2a
7
+ data.tar.gz: af8f883d3f73072302b052afd3e69cca24d4d5d0517bb404cb30c68bc9f9be906e987aa3e1a596c9d3e625a96e028a28c5f7166708f405e66aa184b23282ca71
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1
7
+ - 2.2
8
+ - jruby-18mode
9
+ - jruby-19mode
10
+ - rbx
11
+
12
+ script: bundle exec rspec
13
+ bundler_args: --without development
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in aop.gemspec
4
4
  gemspec
5
+
6
+ group :test do
7
+ gem 'rspec'
8
+ end
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Aop
2
2
 
3
+ [![Build Status](https://travis-ci.org/waterlink/aop.svg?branch=master)](https://travis-ci.org/waterlink/aop)
4
+
3
5
  Very thin AOP gem for Ruby.
4
6
 
5
7
  Thin and fast framework for Aspect Oriented Programming in Ruby.
@@ -24,11 +26,9 @@ Or install it yourself as:
24
26
 
25
27
  ### Before advice
26
28
 
27
- *Not implemented*
28
-
29
29
  ```ruby
30
30
  module Authentication
31
- Aop["BankAccount#transfer:before"] do |account, *args, &blk|
31
+ Aop["BankAccount#transfer:before"].advice do |account, *args, &blk|
32
32
  can!(:transfer, account)
33
33
  end
34
34
 
@@ -40,11 +40,9 @@ end
40
40
 
41
41
  ### After advice
42
42
 
43
- *Not implemented*
44
-
45
43
  ```ruby
46
44
  module Analytics
47
- Aop["User#sign_in:after"] do |target, *args, &blk|
45
+ Aop["User#sign_in:after"].advice do |target, *args, &blk|
48
46
  report("sign_in", user.id)
49
47
  end
50
48
  end
@@ -56,7 +54,7 @@ end
56
54
 
57
55
  ```ruby
58
56
  module Transactional
59
- Aop["BankAccount#transfer:around"] do |joint_point, account, *args, &blk|
57
+ Aop["BankAccount#transfer:around"].advice do |joint_point, account, *args, &blk|
60
58
  start_transaction
61
59
  joint_point.call
62
60
  finish_transaction
@@ -66,7 +64,7 @@ end
66
64
 
67
65
  ## TODO (to specify)
68
66
 
69
- - multiple classes, methods and types of advices
67
+ - multiple classes, methods and types of advices at once
70
68
 
71
69
  ## Contributing
72
70
 
data/lib/aop.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require "aop/version"
2
+ require "aop/pointcut"
2
3
 
3
4
  module Aop
4
- # Your code goes here...
5
5
  end
@@ -0,0 +1,106 @@
1
+ require "securerandom"
2
+
3
+ module Aop
4
+ def self.[](pointcut_spec)
5
+ Pointcut.new(pointcut_spec)
6
+ end
7
+
8
+ class Pointcut
9
+ def initialize(spec)
10
+ @spec = spec
11
+
12
+ @class_spec = spec.scan(/^[^#\.]+/).first || ""
13
+ @class_names = @class_spec.split(",")
14
+ @classes = @class_names.map { |name| Object.const_get(name) }
15
+
16
+ @method_spec = spec.scan(/[#\.][^#\.:]+/)
17
+ @methods = @method_spec.map { |m| MethodReference.from(m) }
18
+
19
+ @advices = spec.scan(/[^:]:([^:]+)/).flatten
20
+ end
21
+
22
+ def advice(&advised)
23
+ advices = @advices
24
+ advices.each do |advice|
25
+ _advice(advice, advised)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def _advice(advice, advised)
32
+ return before_advice(advised) if advice == "before"
33
+ after_advice(advised)
34
+ end
35
+
36
+ def generic_advice(advised, &body)
37
+ methods = @methods
38
+ @classes.each do |klass|
39
+ klass.class_eval do
40
+ methods.each do |method_ref|
41
+ method_ref.decorate(klass, &body[method_ref])
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def before_advice(advised)
48
+ generic_advice(advised) do |method_ref|
49
+ lambda do |*args, &blk|
50
+ advised.call(self, *args, &blk)
51
+ method_ref.call(self, *args, &blk)
52
+ end
53
+ end
54
+ end
55
+
56
+ def after_advice(advised)
57
+ generic_advice(advised) do |method_ref|
58
+ lambda do |*args, &blk|
59
+ result = method_ref.call(self, *args, &blk)
60
+ advised.call(self, *args, &blk)
61
+ result
62
+ end
63
+ end
64
+ end
65
+
66
+ class MethodReference
67
+ def self.from(m)
68
+ return new(m) if m[0...1] == "#"
69
+ Singleton.new(m)
70
+ end
71
+
72
+ def initialize(m)
73
+ @name = m[1..-1]
74
+ end
75
+
76
+ def decorate(target, &with)
77
+ name = @name
78
+ new_name = alias_name
79
+ alias_target(target).class_eval do
80
+ alias_method(new_name, name)
81
+ define_method(name, &with)
82
+ end
83
+ end
84
+
85
+ def call(target, *args, &blk)
86
+ alias_target(target).send(alias_name, *args, &blk)
87
+ end
88
+
89
+ private
90
+
91
+ def alias_target(target)
92
+ target
93
+ end
94
+
95
+ def alias_name
96
+ @_alias_name ||= "__aop_#{SecureRandom.hex(10)}"
97
+ end
98
+
99
+ class Singleton < self
100
+ def alias_target(target)
101
+ class << target; self; end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -1,3 +1,3 @@
1
1
  module Aop
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -0,0 +1,24 @@
1
+ RSpec.describe "After advice" do
2
+ include FixtureLoader
3
+
4
+ let(:spy) { double("Spy") }
5
+
6
+ before do
7
+ load_fixture("BankAccount", "bank_account")
8
+
9
+ Aop["BankAccount#transfer:after"].advice do |account, *args, &blk|
10
+ spy.after(account, *args, &blk)
11
+ end
12
+ end
13
+
14
+ it "fires after #transfer" do
15
+ account = BankAccount.new(spy)
16
+ other = BankAccount.new(spy)
17
+ amount = 55
18
+
19
+ expect(spy).to receive(:inside).with(other, amount).ordered.once
20
+ expect(spy).to receive(:after).with(account, other, amount).ordered.once
21
+
22
+ expect(account.transfer(other, amount)).to eq(:a_result)
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ RSpec.describe "Before advice" do
2
+ include FixtureLoader
3
+
4
+ let(:spy) { double("Spy") }
5
+
6
+ before do
7
+ load_fixture("BankAccount", "bank_account")
8
+
9
+ Aop["BankAccount#transfer:before"].advice do |account, *args, &blk|
10
+ spy.before(account, *args, &blk)
11
+ end
12
+ end
13
+
14
+ it "fires before #transfer" do
15
+ account = BankAccount.new(spy)
16
+ other = BankAccount.new(spy)
17
+ amount = 55
18
+
19
+ expect(spy).to receive(:before).with(account, other, amount).ordered.once
20
+ expect(spy).to receive(:inside).with(other, amount).ordered.once
21
+
22
+ expect(account.transfer(other, amount)).to eq(:a_result)
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ class BankAccount < Struct.new(:spy)
2
+ def transfer(to, amount)
3
+ spy.inside(to, amount)
4
+ :a_result
5
+ end
6
+ end
@@ -0,0 +1,29 @@
1
+ require "aop"
2
+ require "./spec/support/loader"
3
+
4
+ RSpec.configure do |config|
5
+ config.expect_with :rspec do |expectations|
6
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
7
+ end
8
+
9
+ config.mock_with :rspec do |mocks|
10
+ mocks.verify_partial_doubles = true
11
+ end
12
+
13
+ config.filter_run :focus
14
+ config.run_all_when_everything_filtered = true
15
+
16
+ config.disable_monkey_patching!
17
+
18
+ config.warnings = true
19
+
20
+ if config.files_to_run.one?
21
+ config.default_formatter = 'doc'
22
+ end
23
+
24
+ config.profile_examples = 10
25
+
26
+ config.order = :random
27
+
28
+ Kernel.srand config.seed
29
+ end
@@ -0,0 +1,23 @@
1
+ module FixtureLoader
2
+ def self.included(base)
3
+ base.after { reset_fixtures }
4
+ end
5
+
6
+ def load_fixture(name, filename)
7
+ return if loaded[name]
8
+ load "./spec/fixtures/#{filename}.rb"
9
+ loaded[name] = 1
10
+ end
11
+
12
+ def reset_fixtures
13
+ loaded.each do |name, _|
14
+ Object.send(:remove_const, name)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def loaded
21
+ @_loaded ||= {}
22
+ end
23
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Fedorov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-22 00:00:00.000000000 Z
11
+ date: 2015-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -46,13 +46,21 @@ extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
48
  - ".gitignore"
49
+ - ".rspec"
50
+ - ".travis.yml"
49
51
  - Gemfile
50
52
  - LICENSE.txt
51
53
  - README.md
52
54
  - Rakefile
53
55
  - aop.gemspec
54
56
  - lib/aop.rb
57
+ - lib/aop/pointcut.rb
55
58
  - lib/aop/version.rb
59
+ - spec/aop/after_spec.rb
60
+ - spec/aop/before_spec.rb
61
+ - spec/fixtures/bank_account.rb
62
+ - spec/spec_helper.rb
63
+ - spec/support/loader.rb
56
64
  homepage: https://github.com/waterlink/aop
57
65
  licenses:
58
66
  - MIT
@@ -77,4 +85,9 @@ rubygems_version: 2.2.2
77
85
  signing_key:
78
86
  specification_version: 4
79
87
  summary: Very thin AOP gem for Ruby
80
- test_files: []
88
+ test_files:
89
+ - spec/aop/after_spec.rb
90
+ - spec/aop/before_spec.rb
91
+ - spec/fixtures/bank_account.rb
92
+ - spec/spec_helper.rb
93
+ - spec/support/loader.rb