Реализовать Apect-ориентированные вложенные фильтры вокруг Ruby - PullRequest
2 голосов
/ 15 июня 2010

Я пытаюсь написать класс, который поддерживает вложенные фильтры, не представляя Aspect-ориентированную библиотеку.

class Foo
  attr_accessor :around_filter

  def initialize
    #filters which wrap the following one are the ones with interesting logic
    #vanilla do-nothing filter
    @around_filter = lambda { yield } # or lambda {|&blk| blk.call}
  end

  def bar
    #would execute the around filters however deeply nested, then "meaty code"
    @around_filter.call do
      #meaty code here
      puts 'done' 
    end
  end

  #I expected to point only to the topmost filter, hoping to recurse
  def add_around_filter(&blk)
    prior_around_filter = @around_filter
    @around_filter = #??mystery code here?? refers to prior_around_filter
  end
end

Цель состоит в том, чтобы иметь возможность добавлять любое количество обходных фильтров:

foo = Foo.new
foo.add_around_filter do
  puts 'strawberry'
  yield
  puts 'blueberry'
end
foo.add_around_filter do
  puts 'orange'
  yield
  puts 'grape'
end
foo.bar #=> orange, strawberry, done, blueberry, grape

Я знаю, что в примере есть куча дыр.Я написал только достаточно, чтобы составить общее направление, выделив его из гораздо большего фактического класса.

Хотя я предпочитаю синтаксис yield, я не против блоковых ссылок:

foo.add_around_filter do |&blk|
  puts 'orange'
  blk.call
  puts 'grape'
end

Я получил это работает только с одним фильтром вокруг.Я много чего перепробовал с вложением, но так и не разгадал загадку.Если у вас есть решение, я буду признателен за это!

Ответы [ 4 ]

2 голосов
/ 15 июня 2010

yield в ваших фильтрах попытается уступить блоку, определенному в точке их определения, а это не то, что вам нужноОн будет работать с явной блочной формой (в 1.9) следующим образом:

class Foo
  attr_accessor :around_filter

  def initialize
    #filters which wrap the following one are the ones with interesting logic
    #vanilla do-nothing filter
    @around_filter = Proc.new{|&blk| blk.call }
  end

  def bar
    #would execute the around filters however deeply nested, then "meaty code"
    @around_filter.call do
      #meaty code here
      puts 'done' 
    end
  end

  #I expected to point only to the topmost filter, hoping to recurse
  def add_around_filter(&filter)
    prior_around_filter = @around_filter
    @around_filter = Proc.new do |&blk|
      filter.call do
        prior_around_filter.call(&blk)
      end
    end
  end
end

foo = Foo.new
foo.add_around_filter do |&blk|
  puts 'strawberry'
  blk.call
  puts 'blueberry'
end
foo.add_around_filter do |&blk|
  puts 'orange'
  blk.call
  puts 'grape'
end
foo.bar #=> orange, strawberry, done, blueberry, grape
1 голос
/ 15 июня 2010

Одна возможность - явно указать части «до» и «после» каждого фильтра:

Код

class Foo
  def initialize
    @before_filters = Array.new
    @after_filters = Array.new
  end

  def bar
    @before_filters.each { |f| f.call }
    puts "done"
    @after_filters.each { |f| f.call }
  end

  def add_filters(options)
    @before_filters.insert(0, options[:before])
    @after_filters << options[:after]
  end
end

Пример использования

>> foo = Foo.new
>> foo.add_filters(:before => lambda { puts "<body>" },
                   :after => lambda { puts "</body>" })
>> foo.add_filters(:before => lambda { puts "<html>" },
                   :after => lambda { puts "</html>" })
>> foo.bar
<html>
<body>
done
</body>
</html>
0 голосов
/ 20 июля 2012

Если вы хотите использовать библиотеку AOP, такую ​​как aspector , то вы можете сохранить много шаблонного кода, плюс вы можете проверить аспект отдельно.Без использования какой-либо библиотеки, приведенный ниже код будет делать то, что вы хотите

class A
  def bar input
    puts input
    input.upcase
  end

  def self.add_around_filter target, &block
    @filter_count ||= 0
    @filter_count += 1
    filter_method = "#{target}_around_filter_#{@filter_count}"
    define_method filter_method, block

    orig_method = instance_method(target)
    define_method target do |*args, &block|
      send filter_method, orig_method.bind(self), *args, &block
    end
  end
end

A.add_around_filter :bar do |proxy, *args, &block|
  puts 'before'
  result = proxy.call(*args, &block)
  puts 'after'
  result
end

A.add_around_filter :bar do |proxy, *args, &block|
  puts 'before 2'
  result = proxy.call(*args, &block)
  puts 'after 2'
  result
end

puts A.new.bar 'abc'

# Output
#before 2
#before
#abc
#after
#after 2
#ABC
0 голосов
/ 19 июня 2012

Вы даже можете сделать это в ruby ​​1.8.X, имитируя поведение Rack с помощью промежуточного программного обеспечения:

class Builder
  def initialize
    @filters = []
    @app = nil
  end

  def add_filter filter
    @filters << proc { |app| filter.new(app)}
  end

  def run app
    @app=app
  end

  def build
    app = @app
    @filters.reverse.each do  |filter|
      app = filter.call(app)
    end
    @app = app
  end
  def go(env)
    build
    @app.call(env)
  end
end


class FilterBase
  def initialize(app)
    @app = app
  end

  def call(env)
    puts ">>> BEFORE"
    @app.call(env)
    puts "<<< AFTER"
  end
end

class FilterSecond
  def initialize(app)
    @app = app
  end

  def call(env)
    puts "<hello>"
    @app.call(env)
    puts "</hello>"
  end
end

class FilterThird
  def initialize(app)
    @app = app
  end

  def call(env)
    env[:feeling]=:happy
    @app.call(env)
  end
end

class DummyApp
  def call(env)
    puts "HELLO #{env.inspect}" 
  end
end


b = Builder.new
b.add_filter FilterBase
b.add_filter FilterSecond
b.add_filter FilterThird
b.run DummyApp.new
b.go({:feeling => :bad})

приведет к

>>> BEFORE
<hello>
HELLO {:feeling=>:happy}
</hello>
<<< AFTER
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...