如何在Ruby中实现Repository或Gateway模式?
我来自C#世界,我通常抽象出我的数据访问,但是使用ActiveRecord作为Ruby中的默认数据访问机制,如何实现它并不明显。
我通常在C#中做的是使用抽象接口,然后对EFCustomerRepository
,NHibernateCustomerRepository
和InMemoryCustomerRepository
进行具体实现,并根据情况我注入匹配的具体实现
现在,Ruby的方式是什么?!
据我了解,在动态语言中,您不需要像DI(依赖注入)这样的东西。 Ruby具有强大的语言功能,可以实现mixins等功能。
但是你要将mixin定义为在类或模块级静态使用吗?
如果我想针对内存存储库进行开发,那么如何编写我的业务逻辑?在生产中我会切换到我的ActiveRecord-Repository?
如果我可能在错误的道路上,因为我习惯于用静态类型语言思考。如何以Ruby的方式处理这项任务?基本上我想让我的持久层抽象,它的实现可以互换。
编辑:我指的是robert c. martins (unclebob) keynote about architecture
感谢您的帮助......
答案 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上有很多人可以帮助您找出 的方式。