2011-08-05 51 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' 
#=>{} 

你只需要require从插件目录每.RB。

+0

谢谢,这是一个比搞乱模块,扩展和包括更简单的方法,而不是。我还用另一种方法找到了一个教程[link](http://thomasjo.com/2010/12/15/really-simple-and-naive-ruby-plugin-framework/)。 –

0

如果你愿意采取这种方式你应该扫描归档为字符串然后include正确的一个。

在同样的情况我尝试了不同的方法。我用新方法扩展模块,@@注册它们,以便我不会注册两个相同名称的方法。到目前为止它运行良好,尽管我开始的这个项目远没有离开一个特定网站混乱的特定领域。

这是主文件。

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 

它可能不是很好污染模块这样,但到目前为止,这是我能想出的最佳解决方案。