Skip to content

Commit

Permalink
Merge ae8dc90 into 91aefeb
Browse files Browse the repository at this point in the history
  • Loading branch information
marian13 authored Feb 25, 2023
2 parents 91aefeb + ae8dc90 commit b9bda16
Show file tree
Hide file tree
Showing 10 changed files with 392 additions and 116 deletions.
1 change: 1 addition & 0 deletions lib/convenient_service/common/plugins.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require_relative "plugins/caches_constructor_params"
require_relative "plugins/caches_return_value"
require_relative "plugins/can_be_copied"
require_relative "plugins/can_have_user_provided_entity"
require_relative "plugins/has_callbacks"
require_relative "plugins/has_around_callbacks"
require_relative "plugins/has_constructor"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

require_relative "can_have_user_provided_entity/commands"
require_relative "can_have_user_provided_entity/container"
require_relative "can_have_user_provided_entity/errors"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# frozen_string_literal: true

require_relative "commands/find_or_create_entity"
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# frozen_string_literal: true

module ConvenientService
module Common
module Plugins
module CanHaveUserProvidedEntity
module Commands
class FindOrCreateEntity < Support::Command
##
# @!attribute [r] namespace
# @return [Class]
#
attr_reader :namespace

##
# @!attribute [r] proto_entity
# @return [Class]
#
attr_reader :proto_entity

##
# @param namespace [Class]
# @param proto_entity [Class]
# @return [void]
#
def initialize(namespace:, proto_entity:)
@namespace = namespace
@proto_entity = proto_entity
end

##
# @return [void]
#
def call
raise Errors::ProtoEntityHasNoName.new(proto_entity: proto_entity) unless proto_entity_name
raise Errors::ProtoEntityHasNoConcern.new(proto_entity: proto_entity) unless proto_entity_concern

entity.include proto_entity_concern

##
# @example Result for service.
#
# klass = ConvenientService::Common::Plugins::CanHaveUserProvidedEntity::Commands::FindOrCreateEntity.call(
# namespace: SomeService,
# proto_entity: ConvenientService::Service::Plugins::HasResult::Entities::Result
# )
#
# ##
# # `klass` is something like:
# #
# # class Result < ConvenientService::Service::Plugins::HasResult::Entities::Result # or just `class Result` if service (namespace) class defines its own.
# # include ConvenientService::Service::Plugins::HasResult::Entities::Result::Concern # (concern)
# #
# # class << self
# # def proto_entity
# # ##
# # # NOTE: Returns `proto_entity` passed to `FindOrCreateEntity`.
# # #
# # proto_entity
# # end
# #
# # def ==(other)
# # return unless other.respond_to?(:proto_entity)
# #
# # self.proto_entity == other.proto_entity
# # end
# # end
# # end
#
entity.class_exec(proto_entity) do |proto_entity|
define_singleton_method(:proto_entity) { proto_entity }
define_singleton_method(:==) { |other| self.proto_entity == other.proto_entity if other.respond_to?(:proto_entity) }

##
# TODO: `inspect`.
#
# define_singleton_method(:inspect) { "#{entity}(Prototyped by #{proto_entity})" }
end

entity
end

private

##
# @return [Class]
#
def entity
@entity ||= Utils::Module.get_own_const(namespace, proto_entity_demodulized_name) || ::Class.new(proto_entity)
end

##
# @return [String, nil]
#
def proto_entity_name
Utils::Object.memoize_including_falsy_values(self, :@proto_entity_name) { proto_entity.name }
end

##
# @return [String]
#
def proto_entity_demodulized_name
@proto_entity_demodulized_name ||= Utils::String.demodulize(proto_entity_name)
end

##
# @return [Module]
#
def proto_entity_concern
Utils::Object.memoize_including_falsy_values(self, :@proto_entity_concern) { Utils::Module.get_own_const(proto_entity, :Concern) }
end
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module ConvenientService
module Common
module Plugins
module CanHaveUserProvidedEntity
module Container
include Support::DependencyContainer::Export

export :"commands.FindOrCreateEntity" do
Commands::FindOrCreateEntity
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

module ConvenientService
module Common
module Plugins
module CanHaveUserProvidedEntity
module Errors
class ProtoEntityHasNoName < ConvenientService::Error
def initialize(proto_entity:)
message = <<~TEXT
Proto entity `#{proto_entity}` has no name.
In other words:
proto_entity.name
# => nil
NOTE: Anonymous classes do NOT have names. Are you passing an anonymous class?
TEXT

super(message)
end
end

class ProtoEntityHasNoConcern < ConvenientService::Error
def initialize(proto_entity:)
message = <<~TEXT
Proto entity `#{proto_entity}` has no concern.
Have a look at `ConvenientService::Service::Plugins::HasResult::Entities::Result`.
It is an example of a valid proto entity.
TEXT

super(message)
end
end
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,32 @@ module Plugins
module HasResult
module Commands
class CreateResultClass < Support::Command
include Support::DependencyContainer::Import

##
# @!attribute [r] service_class
# @return Class
#
attr_reader :service_class

##
# @param service_class [Class]
# @return [void]
# @return Class
#
def initialize(service_class:)
@service_class = service_class
end
import :"commands.FindOrCreateEntity", from: Common::Plugins::CanHaveUserProvidedEntity::Container

##
# @param service_class [Class]
# @return [void]
#
def call
result_class.include Entities::Result::Concern

##
# class Result < ConvenientService::Service::Plugins::HasResult::Entities::Result # or just `class Result` if service class defines its own.
# include ConvenientService::Service::Plugins::HasResult::Entities::Result::Concern
#
# class << self
# def service_class
# ##
# # NOTE: Returns `service_class` passed to `CreateResultClass`.
# #
# service_class
# end
#
# def ==(other)
# return unless other.instance_of?(self.class)
#
# self.service_class == other.service_class
# end
# end
# end
#
result_class.class_exec(service_class) do |service_class|
define_singleton_method(:service_class) { service_class }
define_singleton_method(:==) { |other| self.service_class == other.service_class if other.instance_of?(self.class) }
end

result_class
def initialize(service_class:)
@service_class = service_class
end

private

##
# @return [Class]
#
def result_class
@result_class ||= Utils::Module.get_own_const(service_class, :Result) || ::Class.new(Entities::Result)
def call
commands.FindOrCreateEntity.call(namespace: service_class, proto_entity: Entities::Result)
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ def error(
# @api private
# @return [Class]
#
# @internal
# NOTE: A command instead of `import` is used in order to NOT pollute the public interface.
# TODO: Specs that prevent public interface accidental pollution.
#
def result_class
@result_class ||= Commands::CreateResultClass.call(service_class: self)
end
Expand Down
Loading

0 comments on commit b9bda16

Please sign in to comment.