Ruby中的存储库或网关模式

时间:2012-02-20 17:44:19

标签: ruby-on-rails ruby design-patterns architecture

如何在Ruby中实现Repository或Gateway模式?

我来自C#世界,我通常抽象出我的数据访问,但是使用ActiveRecord作为Ruby中的默认数据访问机制,如何实现它并不明显。

我通常在C#中做的是使用抽象接口,然后对EFCustomerRepositoryNHibernateCustomerRepositoryInMemoryCustomerRepository进行具体实现,并根据情况我注入匹配的具体实现

现在,Ruby的方式是什么?!

据我了解,在动态语言中,您不需要像DI(依赖注入)这样的东西。 Ruby具有强大的语言功能,可以实现mixins等功能。

但是你要将mixin定义为在类或模块级静态使用吗?

如果我想针对内存存储库进行开发,那么如何编写我的业务逻辑?在生产中我会切换到我的ActiveRecord-Repository?

如果我可能在错误的道路上,因为我习惯于用静态类型语言思考。如何以Ruby的方式处理这项任务?基本上我想让我的持久层抽象,它的实现可以互换。

编辑:我指的是robert c. martins (unclebob) keynote about architecture

感谢您的帮助......

3 个答案:

答案 0 :(得分:3)

我得到你说的话。我也来自.NET背景。抽象出你的业务逻辑和持久性逻辑是一个好主意。我还没有找到适合你的宝石。但是你可以轻松地自己滚动一些东西。最后,存储库模式基本上是一个委托给持久层的类。

以下是我的工作:

require 'active_support/core_ext/module/attribute_accessors'

class GenericRepository

  def initialize(options = {})
    @scope = options[:scope]
    @association_name = options[:association_name]
  end

  def self.set_model(model, options = {})
    cattr_accessor :model
    self.model = model
  end

  def update(record, attributes)
    check_record_matches(record)
    record.update_attributes!(attributes)
  end

  def save(record)
    check_record_matches(record)
    record.save
  end

  def destroy(record)
    check_record_matches(record)
    record.destroy
  end

  def find_by_id(id)
    scoped_model.find(id)
  end

  def all
    scoped_model.all
  end

  def create(attributes)
    scoped_model.create!(attributes)
  end

private

  def check_record_matches(record)
    raise(ArgumentError, "record model doesn't match the model of the repository") if not record.class == self.model
  end

  def scoped_model
    if @scope
      @scope.send(@association_name)
    else
      self.model
    end
  end

end

然后你可以拥有一个Post存储库。

class PostRepository < GenericRepository

  set_model Post

  # override all because we also want to fetch the comments in 1 go.
  def all
    scoped_model.all(:include => :comments)
  end

  def count()
    scoped_model.count
  end

end

只需在您的控制器中在before_filter或初始化或任何地方实例化它。在这种情况下,我将其范围限定为current_user,以便它只获取这些记录并自动为当前用户创建帖子。

def initialize
  @post_repository = PostRepository.new(:scope => @current_user, :association_name => 'posts')
end

def index
  @posts = @post_repository.all
  respond_with @posts, :status => :ok
end

我遇到https://github.com/bkeepers/morphine这是一个很小的DI框架。它可能适合你:)但DI不是红宝石中使用频繁的模式。此外,我实例化我的存储库,以便将它们范围限定为当前用户或其他用户。

我正在努力找到正确的方法去做你所要求的事情,并且如果我找到的话就做一些关于它的文章。但就目前来说,已经足够在持久性和稳定性之间进行彻底切割。我的控制员。如果这样做得当,以后切换到不同的系统将不会是一个大麻烦。或者添加缓存等。

答案 1 :(得分:2)

好吧,ActiveRecord已经提供了抽象持久层 - 它有several different adapters允许它使用不同的数据库后端。此外,它是开源的,所以你可以自由地看看它是如何实现的。

乍一看,您可以看到它还有一个AbstractAdapter所有其他适配器继承,但是,由于Ruby是动态的,鸭子类型语言,AbstractAdapter不必包含抽象将在儿童班中重写的方法,既没有定义他们应该尊重的“合同”。

编辑:

这是一个简单的草图,介绍如何在Ruby中抽象出存储,而不确定它是哪个模式:

# say you have an AR model of a person
class Person < ActiveRecord::Base
end

# and in-memory store of persons (simply, a hash)
IN_MEMORY_STORE = {
  :Person => ['Tim', 'Tom', 'Tumb']
}

# this will abstract access
class MyAbstractModel
  def initialize item, adapter
    @item = item
    @adapter = adapter
  end

  # get all elements from the store
  def all
    case @adapter
    when :active_record
      # pull from database:
      Object.const_get(@item).all
    when :in_memory_store
      # get from in-memory store
      IN_MEMORY_STORE[@item]
    else
      raise "Unknown adapter"
    end
  end
end

# get all Persons from in-memory storage...
p MyAbstractModel.new(:Person, :in_memory_store).all
# ...and from a database
p MyAbstractModel.new(:Person, :active_record).all

答案 2 :(得分:1)

@serverinfo,我对C#了解不多。但是当我从Java / C背景来到Ruby时,当我意识到这种语言的灵活性时,我感到非常震惊。你说这里你真正的问题是“抽象你的持久层并让它可以交换”。您还问“我将如何编写业务逻辑”。

我建议你抛弃你的先入之见并问自己:“我如何喜欢在我的业务逻辑层中表达数据访问/存储”?不要担心你认为能做什么或不能做什么;如果你能弄清楚如何喜欢这个界面,那么可能有一种方法可以在Ruby中完成。

您还必须决定如何指定要使用的具体实现。是否有可能要为不同的模型对象使用不同的数据存储?你想在运行时切换吗?是否要指定要在配置文件或代码中使用的后端?如果您可以决定想要做什么,Stack Overflow上有很多人可以帮助您找出 的方式。