How to create a Rails-like before filter in Sinatr

2019-07-15 04:54发布

问题:

class Foo
  def do_before
    ...
  end

  def do_something
    ...

Is there a way to run do_before method before each other method in the Foo class (like do_something)?

It seems that the Sinatra before block runs before each HTTP request, which has nothing to do with this class.

EDIT: As Michael pointed out in the comments, the only similar functionality that Rails offers is in the Controller. However, both Rails and Sinatra offer something similar to this functionality.

回答1:

As iain pointed out in the comments, the example you specify is not specific to Rails/Sinatra. I'm assuming you want before filter like the ones in Rails and this is what Sinatra offers:

Sinatra's modular apps:

class Foo < Sinatra::Base

  before do
    "Do something"
  end

  get '/' do
    "Hello World"
  end
end

class Bar < Sinatra::Base

  before do
    "Do something else"
  end

  get '/' do
    "Hello World"
  end
end

In your config.rb file,

require 'foo.rb'
require 'bar.rb'

map '/foo' do
  run Foo
end

map '/bar' do
  run Bar
end

This is the nearest analogy for a Rails controller in Sinatra. Create more classes like this and you'll have a similar functionality (similar, but may not be the same as you might expect in Rails world).



回答2:

You can also use a bit of meta-programming to create a before filter. For instance:

class Foo
  def method_1; p "Method 1"; end
  def method_2; p "Method 2"; end
  def preprocess_method; p "Pre-Processing method"; end

  def self.before_filter m
    current_methods = instance_methods(false) - [m]
    self.new.instance_eval do
      current_methods.each do |meth|
        inst_method =  public_method(meth)
        self.class.send :define_method,inst_method.name do
          public_send m
          inst_method.call
        end
      end
    end
  end
  before_filter :preprocess_method
end

o = Foo.new
o.method_1
#output: 
"Pre-Processing method"
"Method 1"

o.method_2
#outputs
"Pre-Processing method"
"Method 2"

In this case, the preprocess_method (that is the do_before on your example) will be called before each call to any instance method defined within the Foo class.



回答3:

Not knowing what you're doing makes it difficult to know how to answer, but to increase the information out there on the web;) I'll give an alternative to @fmendez's answer:

module Filterable
  def self.included(base)
    base.extend ClassMethods
  end
  module ClassMethods
    def do_before( name, &block )
      before_filters[name.to_sym] = block
    end
    def before_filters
      @before_filters ||= {}
    end
    def method_added( name )
      return if name.to_s.start_with?("unfiltered_")
      return if before_filters.has_key? name
      before_filters[name.to_sym] ||= nil
      alias_method "unfiltered_#{name}", name
      define_method name do |*args,&block|
        self.class.before_filters[name.to_sym].call if self.class.before_filters[name.to_sym]
        send "unfiltered_#{name}", *args, &block
      end
    end
  end
end

class Foo
  include Filterable

  def something( x )
    x * 3
  end

  do_before :something do
    puts "Before…"
  end
end

Foo.new.something 4

output:

Before…
# => 12



标签: ruby sinatra