2011-08-05 10 views
3

Facebookの共有リンクプレビューのようなものを書いています。拡張可能ハンドラ/プラグインアーキテクチャのRuby構造

カスタムパーサーを作成する新しいサイトごとに新しいファイルをドロップするだけで、簡単に新しいサイトを拡張できるようにしたいと考えています。デザインパターンの基本的な考え方は分かっていますが、詳細を釘付けするのに十分なモジュールの経験はありません。私は他のプロジェクトでこれほどの事例がたくさんあると確信しています。

結果はこのようなものでなければなりません:

> require 'link' 
=> true 
> Link.new('http://youtube.com/foo').preview 
=> {:title => 'Xxx', :description => 'Yyy', :embed => '<zzz/>' } 
> Link.new('http://stackoverflow.com/bar').preview 
=> {:title => 'Xyz', :description => 'Zyx' } 

、コードはこのようなものになるだろう:あなたは、このアプローチを取って喜んでいる場合は、おそらく提出をスキャンする必要があり

#parsers/youtube.rb 
module YoutubeParser 
    url_match /(youtube\.com)|(youtu.be)\// 
    def preview 
    get_stuff_using youtube_api 
    end 
end 

#parsers/stackoverflow.rb 
module SOFParser 
    url_match /stachoverflow.com\// 
    def preview 
    get_stuff 
    end 
end 

#link.rb 
class Link 
    def initialize(url) 
    extend self with the module that has matching regexp 
    end 
end 

答えて

3
# url_processor.rb 
class UrlProcessor 
    # registers url handler for given pattern 
    def self.register_url pattern, &block 
    @patterns ||= {} 
    @patterns[pattern] = block 
    end 

    def self.process_url url 
    _, handler = @patterns.find{|p, _| url =~ p} 
    if handler 
     handler.call(url) 
    else 
     {} 
    end 
    end 
end 

# plugins/so_plugin.rb 
class SOPlugin 
    UrlProcessor.register_url /stackoverflow\.com/ do |url| 
    {:title => 'foo', :description => 'bar'} 
    end 
end 

# plugins/youtube_plugin.rb 
class YoutubePlugin 
    UrlProcessor.register_url /youtube\.com/ do |url| 
    {:title => 'baz', :description => 'boo'} 
    end 
end 

p UrlProcessor.process_url 'http://www.stackoverflow.com/1234' 
#=>{:title=>"foo", :description=>"bar"} 
p UrlProcessor.process_url 'http://www.youtube.com/1234' 
#=>{:title=>"baz", :description=>"boo"} 
p UrlProcessor.process_url 'http://www.foobar.com/1234' 
#=>{} 

プラグインディレクトリの.rbごとにrequireする必要があります。

+0

ありがとう、これは、モジュールを使いこなすことと、拡張すること、含まないこと、そしてそうでないことよりも簡単な方法です。私は別のアプローチでチュートリアル[リンク](http://thomasjo.com/2010/12/15/really-simple-and-naive-ruby-plugin-framework/)も見つけました。 –

0

数学的な文字列の場合はincludeとなります。

同じ状況で私は別のアプローチを試みました。私は新しいメソッドでモジュールを拡張しています。@それらを登録して、同じ名前のメソッドを2つ登録しないようにします。これまでのところ、私が始めたプロジェクトは、特定のWebサイトの混乱した混乱の特定の領域を去っていくところはありませんが、うまくいきます。

これはメインファイルです。

module Onigiri 
    extend self 
    @@registry ||= {} 

    class OnigiriHandlerTaken < StandardError 
    def description 
     "There was an attempt to override registered handler. This usually indicates a bug in Onigiri." 
    end 
    end 

    def clean(data, *params) 
    dupe = Onigiri::Document.parse data 
    params.flatten.each do |method| 
     dupe = dupe.send(method) if @@registry[method] 
    end 
    dupe.to_html 
    end 

    class Document < Nokogiri::HTML::DocumentFragment 
    end 

    private 

    def register_handler(name) 
    unless @@registry[name] 
     @@registry[name] = true 
    else 
     raise OnigiriHandlerTaken 
    end 
    end 

end 

ここに拡張ファイルがあります。

# encoding: utf-8 
module Onigiri 
    register_handler :fix_backslash 
    class Document 
    def fix_backslash 
     dupe = dup 
     attrset = ['src', 'longdesc', 'href', 'action'] 
     dupe.css("[#{attrset.join('], [')}]").each do |target| 
     attrset.each do |attr| 
      target[attr] = target[attr].gsub("\\", "/") if target[attr] 
     end 
     end 
     dupe 
    end 
    end 
end 

私が見るもう一つの方法は、右のいずれかを呼び出すために、簡単な意思決定のメカニズムと異なる(しかし、行動的に区別できない)クラスのセットを使用することです。クラス名と対応するurl_matcherを保持する単純なハッシュで十分でしょう。

これが役に立ちます。

0

私はそれを釘付けにしたと思います。

irb(main):001:0> require './url_handler' 
=> true 
irb(main):002:0> UrlHandler.new('www.youtube.com').process 
=> {:description=>"Nyan nyan!", :title=>"Youtube"} 
irb(main):003:0> UrlHandler.new('www.facebook.com').process 
=> {:description=>"Hello!", :title=>"Facebook"} 
irb(main):004:0> UrlHandler.new('www.stackoverflow.com').process 
=> {:description=>"Title fetcher", :title=>"Generic"} 

url_handler.rb:

class UrlHandler 
    attr_accessor :url 
    def initialize(url) 
    @url=url 
    if plugin=Module.url_pattern.find{|re, plugin| @url.match(re)} 
     extend plugin.last 
    else 
     extend HandlerPlugin::Generic 
    end 
    end 
end 

class Module 
    def url_pattern(pattern=nil) 
    @@patterns ||= {} 
    @@patterns[pattern] ||= self unless pattern.nil? 
    return @@patterns 
    end 
end 

module HandlerPlugin 
    module Generic 
    def process 
     {:title => 'Generic', :description => 'Title fetcher'} 
    end 
    end 
end 

Dir[File.join(File.dirname(__FILE__), 'plugins', '*.rb')].each {|file| require file } 

プラグイン/ youtube.rb(facebook.rbは非常に似ている)

module HandlerPlugin::Youtube 
    include HandlerPlugin 
    url_pattern /youtube/ 
    def process 
    {:title => 'Youtube', :description => 'Nyan nyan!'} 
    end 
end 

そのようなモジュールを汚染する素敵ではないかもしれませんが、これまでのところ、これは私が考え出すことができる最高の解決策です。

関連する問題