用于可扩展处理程序/插件架构的 Ruby 结构

发布于 2024-11-28 02:29:12 字数 867 浏览 1 评论 0原文

我正在写的东西有点像 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

I'm writing something that is a bit like Facebook's shared link preview.

I would like to make it easily extendable for new sites by just dropping in a new file for each new site I want to write a custom parser for. I have the basic idea of the design pattern figured out but don't have enough experience with modules to nail the details. I'm sure there are plenty of examples of something like this in other projects.

The result should be something like this:

> 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' }

And the code would be something like this:

#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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

反话 2024-12-05 02:29:12
# 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 即可。

# 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'
#=>{}

You just need to require every .rb from plugins directory.

痴情换悲伤 2024-12-05 02:29:12

如果您愿意采用这种方法,您可能应该扫描该文件以查找数学字符串,然后包含正确的字符串。

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

这是主文件。

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 的简单哈希可能就足够了。

希望这有帮助。

If you're willing to take this approach you should probably scan the filed for the mathing string and then include the right one.

In the same situation I attempted a different approach. I'm extending the module with new methods, @@registering them so that I won't register two identically named methods. So far it works good, though the project I started is nowhere near leaving the specific domain of one tangled mess of a particular web-site.

This is the main file.

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

And here's the extending file.

# 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

Another way I see is to use a set of different (but behaviorally indistinguishable) classes with a simple decision making mechanism to call a right one. A simple hash that holds class names and corresponding url_matcher would probably suffice.

Hope this helps.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文