Ruby on rails Rails架构问题

Ruby on rails Rails架构问题,ruby-on-rails,architecture,oop,model,Ruby On Rails,Architecture,Oop,Model,我正在构建一个Rails站点,该站点允许用户构建自己的配方库。配方可以手动输入,也可以通过链接到另一个站点(think等)输入。我写的脚本将从这些网站上刮取一个配方,从用户那里得到一个链接,到目前为止(尽管存在法律问题),这部分并没有给我带来任何麻烦 但是,我不确定我正在为这些scraper脚本编写的代码放在哪里。我的第一个想法是把它放在菜谱模型中,但它似乎有点太复杂了,无法做到这一点;图书馆或助手更合适吗 另外,正如我提到的,我正在为不同的食品网站构建几个不同的刮板。在我看来,实现这一点的优雅

我正在构建一个Rails站点,该站点允许用户构建自己的配方库。配方可以手动输入,也可以通过链接到另一个站点(think等)输入。我写的脚本将从这些网站上刮取一个配方,从用户那里得到一个链接,到目前为止(尽管存在法律问题),这部分并没有给我带来任何麻烦

但是,我不确定我正在为这些scraper脚本编写的代码放在哪里。我的第一个想法是把它放在菜谱模型中,但它似乎有点太复杂了,无法做到这一点;图书馆或助手更合适吗


另外,正如我提到的,我正在为不同的食品网站构建几个不同的刮板。在我看来,实现这一点的优雅方法是定义一个接口(或抽象基类),该接口确定一组方法,用于在给定链接的情况下构造配方对象,但我也不确定这里的最佳方法是什么。如何构建这些OO关系,以及代码应该放在哪里?

通常,不属于MVC设计的实用程序类会放在
lib
文件夹中。我也看到人们把它们放在
models
文件夹中,但是
lib
确实是“正确”的地方


然后,您可以根据需要在控制器中创建配方刮板的实例,将数据输入到模型中。

这件事有两个明显的方面。首先是如何存储配方,这些配方将是模型。显然,模型不会删除其他站点,因为它们只有一个职责:存储有效数据。启动刮片和存储过程的控制器也不应包含刮片代码(尽管它们会调用该代码)

在Ruby中,我们不使用抽象类或接口——它是duck类型的,所以您的scraper实现一个已知的方法或一组方法就足够了——您的scraping引擎应该都是类似的,特别是在它们公开的公共方法方面


你可以把刮刀放在任何你想放的地方,这是一个蹩脚的答案
lib
很好,但是如果你想制作一个插件,这也不是一个坏主意。请看我的问题——著名的Rails人耶胡达·卡茨(Yehuda Katz)给出了一个惊人的答案——了解其他一些想法,但总的来说:没有正确的答案。不过,也有一些错误的模式。

并非应用程序/模型中的所有内容都必须是ActiveRecord模型。因为它们直接属于应用程序的业务逻辑,所以它们属于app目录,而不是lib目录。它们也不是控制器、视图或帮助器(帮助器用于帮助视图和视图本身)。因此,它们属于应用程序/模型。我会确保将它们命名为名称空间,只是为了组织目的,将它们命名为app/models/scraper或类似的东西。

我会设置一个rake任务来刮取站点并创建新的rake任务。一旦它工作起来,我将使用后台处理器或cron作业来运行rake任务。

我将在lib中创建一个名为scrapers的文件夹。然后在该文件夹中为每个刮板创建一个文件。调用这些epicurious、cooks等。然后您可以定义一个基本的scrapers类,该类包含所有scrapers通用的任何共享方法。与以下内容类似

lib/scrapers/base.rb

class Scrapers::base
  def shared_1()
  end
  def shared_2()
  end
  def must_implement1
    raise NotImplemented
  end
  def must_implement2
    raise NotImplemented
  end
end
lib/scrapers/epicurious.rb

Class Epicurious < Base
  def must_implement1
  end
  def must_implement2
  end
end
Class-Epicurious

然后使用
Scrapers::Epicurious.new
从控制器中调用相关类,或者调用
Scrapers::Base
中的class方法,该方法基于传递的参数调用相关实现。

刮取引擎应该是独立插件或gem插件。对于脏的和快的,你可以把它放在lib里面。反正这是惯例。它可能应该实现一个工厂类,根据url实例化不同类型的刮刀,因此对于客户端使用,它将非常简单:

Scraper.scrape(url)

此外,如果这是一个长时间运行的任务,您可能需要考虑使用RESK或延迟的作业来将任务卸载到单独的进程。

尝试集中在将其移动到GEM/Pu外LL之前先将其工作。 另外,忘记接口/抽象类-只需编写完成该操作的代码。 您的模型应该知道的唯一一件事是,这是否是远程配方,以及url是什么。 你可以把所有的刮码放在app/scrapers中。下面是一个示例实施大纲:

class RecipePage
  def new(url)
    @url = url
    @parser = get_parser
  end

  def get_attributes
    raise "trying to scrape unknown site" unless @parser        
    @parser.recipe_attributes(get_html)
  end

  private       
  def get_html
    #this uses your favorite http library to get html from the @url
  end

  def get_parser(url)
    #this matches url to your class, ie returns domain camelized, or nil if you are not handling particular site yet
    return EpicurusComParser
  end
end

class EpicurusComParser

  def self.recipe_attributes(html)
    # this does the hard job of querying html and moving
    # all the code to get title, text, image of recipe and return hash
    {
      :title => "recipe title",
      :text => "recipe text",
      :image => "recipe_image_url",
    }
  end
end
然后在你的模型中

class Recipe
  after_create :scrape_recipe, :if => :recipe_url

  private 

  def scrape_recipe
    # do that in background - ie in DelayedJob
    recipe_page = RecipePage.new(self.recipe_url)
    self.update_attributes(recipe_page.get_attributes.merge(:scraped => true))
  end
end
然后您可以创建更多的解析器,如CookComParser等