Шаблон репозитория или шлюза в Ruby - PullRequest
8 голосов
/ 20 февраля 2012

Как я могу реализовать шаблон Repository или Gateway в Ruby?

Я из мира C # и обычно абстрагирую свой доступ к данным, но с ActiveRecord в качестве механизма доступа к данным по умолчанию в Ruby, это не очевиднокак это сделать.

В C # я обычно работаю с абстрактными интерфейсами, а затем имею конкретную реализацию для EFCustomerRepository, NHibernateCustomerRepository и InMemoryCustomerRepository, и в зависимости от ситуации я вставляю сопоставлениеконкретная реализация.

Итак, каков путь Ruby?!

Насколько я понимаю, в динамических языках вам не понадобится что-то вроде DI (внедрение зависимости).А в Ruby есть мощные языковые функции, позволяющие использовать такие вещи, как миксины.

Но вы бы определили, что миксины будут использоваться статически на уровне классов или модулей?

Как мне написать свою бизнес-логику, если я хочучтобы развиваться против репозитория в памяти и в процессе работы, я бы переключился на свой ActiveRecord-Repository?

Если бы здесь был неправильный путь, так как я привык думать на языке статической типизации.Как кто-то может решить эту задачу Ruby?По сути, я хочу сделать свой уровень персистентности абстрактным, а его реализации взаимозаменяемыми.

РЕДАКТИРОВАТЬ: Я имею в виду Роберт c.Мартинс (дядя Боб) основной доклад об архитектуре

Спасибо за любую помощь ...

Ответы [ 3 ]

3 голосов
/ 23 февраля 2012

Я понимаю, что вы говорите. Я также пришел из .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 не очень часто используется в ruby. Кроме того, я создаю экземпляры своих репозиториев, чтобы охватить их текущим пользователем или чем-то еще.

Я нахожусь в поиске правильного способа сделать то, что вы просите, и написать немного об этом, если я когда-нибудь найду это. Но на данный момент уже достаточно провести чистый разрыв между стойкостью и моими контроллерами. Если это сделано правильно, то переключение на другую систему в дальнейшем не составит большого труда. Или добавить кеширование и т. Д.

2 голосов
/ 20 февраля 2012

Что ж, ActiveRecord уже предоставляет абстрактный уровень персистентности - он имеет несколько различных адаптеров , что позволяет ему использовать разные базы данных. Кроме того, это с открытым исходным кодом, так что вы можете взглянуть на то, как это было достигнуто.

На первый взгляд вы можете видеть, что он также имеет AbstractAdapter , который наследуют все другие адаптеры, однако, поскольку Ruby является динамическим языком утиной типизации, AbstractAdapter не должен содержать абстрактный методы, которые будут переопределены в дочерних классах, также не определяет «контракт», который они должны соблюдать.

Edit:

Вот простой набросок того, как вы можете абстрагировать свое хранилище в 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
1 голос
/ 20 февраля 2012

@ serverinfo, я мало что знаю о C #.Но когда я пришел к Ruby из опыта Java / C, я был поражен, когда понял, насколько гибок этот язык на самом деле.Вы говорите, что ваша настоящая проблема заключается в том, чтобы «абстрагировать свой слой постоянства и сделать его обменным».Вы также спросили: «Как мне написать бизнес-логику».

Я предлагаю вам отбросить свои предвзятые мнения и спросить себя: «Как бы я хотел выразить доступ к данным / их хранение в моемслой бизнес-логики "?Не беспокойтесь о том, что, по вашему мнению, можно или нельзя сделать;если вы можете выяснить, как бы вы хотели работать с интерфейсом, возможно, есть способ сделать это в Ruby.

Вам также придется решить, как вы хотите указатьконкретная реализация будет использоваться.Возможно ли, что вы захотите использовать другое хранилище данных для разных объектов модели?Можете ли вы переключиться во время выполнения?Желаете ли вы указать бэкэнд для использования в файле конфигурации или в коде?Если вы можете решить, что вы хотите сделать, есть много людей в Переполнении стека, которые могут помочь вам выяснить как сделать это.

...