rom-rb/rom-repository

View on GitHub
lib/rom/repository/class_interface.rb

Summary

Maintainability
A
1 hr
Test Coverage
module ROM
  class Repository
    # Class-level APIs for repositories
    #
    # @api public
    module ClassInterface
      # Create a root-repository class and set its root relation
      #
      # @example
      #   # where :users is the relation name in your rom container
      #   class UserRepo < ROM::Repository[:users]
      #   end
      #
      # @param name [Symbol] The relation `register_as` value
      #
      # @return [Class] descendant of ROM::Repository::Root
      #
      # @api public
      def [](name)
        klass = Class.new(self < Repository::Root ? self : Repository::Root)
        klass.relations(name)
        klass.root(name)
        klass
      end

      # Inherits configured relations and commands
      #
      # @api private
      def inherited(klass)
        super

        return if self === Repository

        klass.relations(*relations)
        klass.commands(*commands)
      end

      # Define which relations your repository is going to use
      #
      # @example
      #   class MyRepo < ROM::Repository
      #     relations :users, :tasks
      #   end
      #
      #   my_repo = MyRepo.new(rom)
      #
      #   my_repo.users
      #   my_repo.tasks
      #
      # @return [Array<Symbol>]
      #
      # @api public
      def relations(*names)
        if names.empty?
          @relations ||= []
        else
          attr_reader(*(names - relations))
          @relations = relations | names
        end
      end

      # Defines command methods on a root repository
      #
      # @example
      #   class UserRepo < ROM::Repository[:users]
      #     commands :create, update: :by_pk, delete: :by_pk
      #   end
      #
      #   # with custom command plugin
      #   class UserRepo < ROM::Repository[:users]
      #     commands :create, plugin: :my_command_plugin
      #   end
      #
      #   # with custom mapper
      #   class UserRepo < ROM::Repository[:users]
      #     commands :create, mapper: :my_custom_mapper
      #   end
      #
      # @param [Array<Symbol>] names A list of command names
      # @option :mapper [Symbol] An optional mapper identifier
      # @option :use [Symbol] An optional command plugin identifier
      #
      # @return [Array<Symbol>] A list of defined command names
      #
      # @api public
      def commands(*names, mapper: nil, use: nil, **opts)
        if names.any? || opts.any?
          @commands = names + opts.to_a

          @commands.each do |spec|
            type, *view = Array(spec).flatten

            if view.size > 0
              define_restricted_command_method(type, view, mapper: mapper, use: use)
            else
              define_command_method(type, mapper: mapper, use: use)
            end
          end
        else
          @commands ||= []
        end
      end

      private

      # @api private
      def define_command_method(type, **opts)
        define_method(type) do |*input|
          if input.size == 1 && input[0].respond_to?(:commit)
            changeset = input[0]
            map_tuple(changeset.relation, changeset.commit)
          else
            command(type => self.class.root, **opts).call(*input)
          end
        end
      end

      # @api private
      def define_restricted_command_method(type, views, **opts)
        views.each do |view_name|
          meth_name = views.size > 1 ? :"#{type}_#{view_name}" : type

          define_method(meth_name) do |*args|
            view_args, *input = args

            changeset = input.first

            if changeset.respond_to?(:commit)
              map_tuple(changeset.relation, changeset.commit)
            else
              command(type => self.class.root, **opts)
                .public_send(view_name, *view_args)
                .call(*input)
            end
          end
        end
      end
    end
  end
end