angeloashmore/hanami-rethinkdb

View on GitHub
lib/hanami/model/adapters/rethinkdb_adapter.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'hanami/model/adapters/abstract'
require 'hanami/model/adapters/rethinkdb/collection'
require 'hanami/model/adapters/rethinkdb/command'
require 'hanami/model/adapters/rethinkdb/query'
require 'rethinkdb'

module Hanami
  module Model
    module Adapters

      # Creates a RethinkdbIOError
      #
      # @return [Object]
      class RethinkdbIOError < Hanami::Model::Error

        def initialize(collection, operation)
          super "You try to #{operation} a nil entity for collection <#{collection}"
        end
      end

      # Adapter for RethinkDB databases
      #
      # @see Hanami::Model::Adapters::Implementation
      #
      # @api private
      # @since 0.1.0
      class RethinkdbAdapter < Abstract
        include ::RethinkDB::Shortcuts

        # @attr_reader parsed_uri [Hash] the database connection details parsed
        #   from a URI
        #
        # @since 0.1.0
        # @api private
        attr_reader :parsed_uri

        # Initialize the adapter.
        #
        # Hanami::Model uses RethinkDB.
        #
        # @param mapper [Object] the database mapper
        # @param connection [RethinkDB::Connection] the database connection
        #
        # @return [Hanami::Model::Adapters::RethinkdbAdapter]
        #
        # @see Hanami::Model::Mapper
        # @see http://rethinkdb.com/api/ruby/
        #
        # @api private
        # @since 0.1.0
        def initialize(mapper, uri, options={})
          super
          @connection = r.connect(_parse_uri)
        end

        # Creates or updates a document in the database for the given entity.
        #
        # @param collection [Symbol] the target collection (it must be mapped).
        # @param entity [#id, #id=] the entity to persist
        #
        # @return [Object] the entity
        #
        # @api private
        # @since 0.1.0
        def persist(collection, entity)
          ::Kernel.raise RethinkdbIOError.new(collection, 'IO') if entity.nil?
          if entity.id
            update(collection, entity)
          else
            create(collection, entity)
          end
        end

        # Creates a document in the database for the given entity.
        # It assigns the `id` attribute, in case of success.
        #
        # @param collection [Symbol] the target collection (it must be mapped).
        # @param entity [#id=] the entity to create
        #
        # @return [Object] the entity
        #
        # @api private
        # @since 0.1.0
        def create(collection, entity)
          ::Kernel.raise Adapters::RethinkdbIOError.new(collection, 'create') if entity.nil?
          command(
            query(collection)
          ).create(entity)
        end

        # Updates a document in the database corresponding to the given entity.
        #
        # @param collection [Symbol] the target collection (it must be mapped).
        # @param entity [#id] the entity to update
        #
        # @return [Object] the entity
        #
        # @api private
        # @since 0.1.0
        def update(collection, entity)
          ::Kernel.raise RethinkdbIOError.new(collection, 'update') if entity.nil?
          command(
            _find(collection, entity.id)
          ).update(entity)
        end

        # Deletes a document in the database corresponding to the given entity.
        #
        # @param collection [Symbol] the target collection (it must be mapped).
        # @param entity [#id] the entity to delete
        #
        # @api private
        # @since 0.1.0
        def delete(collection, entity)
          ::Kernel.raise RethinkdbIOError.new(collection, 'delete') if entity.nil?
          command(
            _find(collection, entity.id)
          ).delete
        end

        # Returns all the documents for the given collection
        #
        # @param collection [Symbol] the target collection (it must be mapped).
        #
        # @return [Array] all the documents
        #
        # @api private
        # @since 0.1.0
        def all(collection)
          query(collection).all
        end

        # Returns a unique document from the given collection, with the given
        # id.
        #
        # @param collection [Symbol] the target collection (it must be mapped).
        # @param id [Object] the identity of the object.
        #
        # @return [Object] the entity
        #
        # @api private
        # @since 0.1.0
        def find(collection, id)
          _first(
            _find(collection, id)
          )
        end

        # This method is not implemented. RethinkDB does not have sequential
        # primary keys.
        #
        # @param _collection [Symbol] the target collection (it must be mapped)
        #
        # @raise [NotImplementedError]
        #
        # @since 0.1.0
        def first(_collection)
          fail NotImplementedError
        end

        # This method is not implemented. RethinkDB does not have sequential
        # primary keys.
        #
        # @param _collection [Symbol] the target collection (it must be mapped)
        #
        # @raise [NotImplementedError]
        #
        # @since 0.1.0
        def last(_collection)
          fail NotImplementedError
        end

        # Deletes all the documents from the given collection.
        #
        # @param collection [Symbol] the target collection (it must be mapped).
        #
        # @api private
        # @since 0.1.0
        def clear(collection)
          command(query(collection)).clear
        end

        # Fabricates a command for the given query.
        #
        # @param query [Hanami::Model::Adapters::Rethinkdb::Query] the query
        # object to act on.
        #
        # @return [Hanami::Model::Adapters::Rethinkdb::Command]
        #
        # @see Hanami::Model::Adapters::Rethinkdb::Command
        #
        # @api private
        # @since 0.1.0
        def command(query)
          Rethinkdb::Command.new(query)
        end

        # Fabricates a query
        #
        # @param collection [Symbol] the target collection (it must be mapped).
        # @param blk [Proc] a block of code to be executed in the context of
        #   the query.
        #
        # @return [Hanami::Model::Adapters::Rethinkdb::Query]
        #
        # @see Hanami::Model::Adapters::Rethinkdb::Query
        #
        # @api private
        # @since 0.1.0
        def query(collection, context = nil, &blk)
          Rethinkdb::Query.new(_collection(collection), context, &blk)
        end

        private

        # Sets a parsed URI hash to be used when creating the database
        # connection.
        #
        # @api private
        # @since 0.1.0
        def _parse_uri
          parsed = URI.parse(@uri)
          db = parsed.path.gsub(/^\//, '')

          fail DatabaseAdapterNotFound if parsed.scheme != 'rethinkdb'
          fail "No database specified in #{@uri}" if db.empty? || db.nil?

          {
            auth_key: parsed.password,
            host: parsed.host,
            port: parsed.port || 28_015,
            db: db
          }
        end

        # Returns a collection from the given name.
        #
        # @param name [Symbol] a name of the collection (it must be mapped).
        #
        # @return [Hanami::Model::Adapters::Rethinkdb::Collection]
        #
        # @see Hanami::Model::Adapters::Rethinkdb::Collection
        #
        # @api private
        # @since 0.1.0
        def _collection(name)
          Rethinkdb::Collection.new(
            @connection, r.table(name), _mapped_collection(name)
          )
        end

        def _mapped_collection(name)
          @mapper.collection(name)
        end

        def _find(collection, id)
          identity = _identity(collection)
          query(collection).where(identity => _id(collection, identity, id))
        end

        def _first(query)
          query.limit(1).first
        end

        def _identity(collection)
          _mapped_collection(collection).identity
        end

        def _id(collection, column, value)
          _mapped_collection(collection).deserialize_attribute(column, value)
        end
      end
    end
  end
end