Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Persistence implementation: Repository pattern #71

Merged
merged 32 commits into from May 27, 2014
Merged

Persistence implementation: Repository pattern #71

merged 32 commits into from May 27, 2014

Conversation

karmi
Copy link
Contributor

@karmi karmi commented Apr 4, 2014

The persistence branch contains the "elasticsearch-persistence" Rubygem, which provides a number of features and facilities to store and retrieve Ruby domain objects in Elasticsearch, via the repository pattern.

Please see the README, the code annotations and the unit tests for detailed information.
The repository implementation will be used to provide an ActiveRecord-like integration with Ruby domain objects, similar to the old Tire::Model::Persistence.

TODO

  • Example application for Repository (Sinatra)
  • Integration tests

This was referenced Apr 4, 2014
@karmi karmi changed the title Persistence implementation: repository pattern Persistence implementation: Repository pattern Apr 17, 2014
@karmi
Copy link
Contributor Author

karmi commented Apr 17, 2014

For the discussion about the ActiveRecord pattern, please follow #78.

karmi added a commit that referenced this pull request Apr 17, 2014
karmi added a commit that referenced this pull request Apr 27, 2014
karmi added 23 commits May 27, 2014 11:48
Instead of:

    class MyRepository
      include Elasticsearch::Persistence::Repository
    end

    repository = MyRepository.new

you can do:

    repository = Elasticsearch::Persistence::Repository.new

The module function `new` returns an Elasticsearch::Persistence::Repository::Class instance.
Provides getting Ruby class from Elasticsearch type and vice versa,
ID from the document (Hash), as well as setting the `klass`
for the whole Repository instance.
The repository uses two symmetric methods: `serialize` and `deserialize`
to convert documents when:

1. passing them to the storage,
2. initializing Ruby objects when retrieving documents from storage

Every repository can easily customize (overload) these methods,
to provide (de)serialization for complex use-cases, such as storing
PDF files or images in the storage.

See:

* https://www.braintreepayments.com/braintrust/untangle-domain-and-persistence-logic-with-curator
* https://github.com/braintree/curator/blob/master/lib/curator/repository.rb
The `Store` module saves and deletes the documents in Elasticsearch
via the `save` and `delete` methods.
The module provides method to find one or multiple documents and to
check for documents existence.

The methods return `deserialize`-d Ruby objects based on `klass` or
the document Elasticsearch `_type`.

Missing documents are kept in the resulting Array as `nil` objects.
The module provides an interface for getting objects from the repository
based on a search query.

The results are wrapped in the `Response::Results` instance, which
proxies methods to the `results` property.
So:

    reposistory.klass = Foobar

is equivalent to:

    repository.klass Foobar
Included `Elasticsearch::Model::Indexing::ClassMethods` to support setting the index name and document type,
and to allow configuring the mappings and settings for the index.

See:

https://github.com/elasticsearch/elasticsearch-rails/blob/6f4a57a/elasticsearch-model/lib/elasticsearch/model/indexing.rb
…ository

Also available as `document_type="foo"`.
… it's set

When the `document_type` is configured for the repository, it is used
in `save`, `delete`, `find`, etc method to set the `type` parameter
for the client.

When the `document_type` is not set, the old behaviour of inferring
from `klass`, `document.class`, etc. is preserved.
To provide flexibility to the end-user, the integration of the "Repository" module
has been refactored to proxy all repository methods via a gateway.

This allows users to set up the repository in a custom class, with class methods, in a DSL-like fashion:

    class NoteRepository
      include Elasticsearch::Persistence::Repository

      klass Note
      index :my_notes

      mapping do
        indexes :title, analyzer: 'snowball'
      end

      client.transport.logger = Logger.new(STDERR)

      gateway do
        def serialize(document)
          super.merge(special: 'stuff')
        end
      end
    end

The bundled Repository class can be configured via a block passed to the initializer:

    repository = Elasticsearch::Persistence::Repository.new do
      klass Note
      index :my_notes

      mapping do
        indexes :title, analyzer: 'snowball'
      end

      client.transport.logger = Logger.new(STDERR)
    end

The repository methods can be accessed via the class or instance methods:

    NoteRepository.klass Note
    repository.klass Note
    class NoteRepository
      include Elasticsearch::Persistence::Repository
    end

    NoteRepository.index_name
    => "note_repository"
Also, when using the "shortcut" to create the repository, a default
name of `repository` is set.
Both:

    repository.client = MyClient.new

and:

    repository.client MyClient.new

are equivalent now.
…ds directly in the class

Instead of calling the `gateway` method with a block, to redefine the serialize/deserialize methods:

    class NoteRepository
      gateway do
        def serialize(document)
          Hash[document.to_hash.map() { |k,v|  v.upcase! if k == :title; [k,v] }]
        end
        def deserialize(document)
          MyNote.new ActiveSupport::HashWithIndifferentAccess.new(document['_source']).deep_symbolize_keys
        end
      end
    end

Define them directly in the class, and they will be intercepted by the hook, and (re)defined directly on the gateway:

    class NoteRepository
      def serialize(document)
        Hash[document.to_hash.map() { |k,v|  v.upcase! if k == :title; [k,v] }]
      end
      def deserialize(document)
        MyNote.new ActiveSupport::HashWithIndifferentAccess.new(document['_source']).deep_symbolize_keys
      end
    end

See: http://www.ruby-doc.org/core-2.1.1/Module.html#method-i-method_added

(COMITTED WITH FINGERS CROSSED :)
karmi added a commit that referenced this pull request May 27, 2014
@karmi karmi merged commit 91814b3 into master May 27, 2014
@picandocodigo picandocodigo deleted the persistence branch September 1, 2020 09:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants