Рубиновая структура для расширяемой архитектуры обработчика / плагина - PullRequest
3 голосов
/ 05 августа 2011

Я пишу что-то похожее на предварительный просмотр общей ссылки Facebook.

Я хотел бы сделать его легко расширяемым для новых сайтов, просто вставив новый файл для каждого нового сайта, для которого я хочу написать собственный анализатор.У меня есть основная идея шаблона проектирования, но у меня нет достаточного опыта работы с модулями для детализации.Я уверен, что есть много примеров чего-то подобного в других проектах.

Результат должен быть примерно таким:

> 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 ]

3 голосов
/ 05 августа 2011
# 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 голосов
/ 08 августа 2011

Полагаю, я это прибил.

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 }

plugins / youtube.rb (очень похоже на facebook.rb)

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

Может быть, нехорошо загрязнять Модуль таким образом, но пока это лучшее решение, которое я мог придумать.

0 голосов
/ 05 августа 2011

Если вы готовы пойти на такой подход, вам, вероятно, следует отсканировать поданную строку поиска и затем 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, вероятно, будет достаточно.

Надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...