Skip to content

Commit

Permalink
Merge 6753287 into f12c8e8
Browse files Browse the repository at this point in the history
  • Loading branch information
marian13 committed Oct 25, 2022
2 parents f12c8e8 + 6753287 commit b43e483
Show file tree
Hide file tree
Showing 37 changed files with 859 additions and 1,131 deletions.
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
| Low | 🚧 | GitHub Wiki/Gists for Support | |
| Low | 🚧 | Contribute to Shoulda Matchers | |
| High || `respond_to_missing?` | [ConvenientService::Core::ClassMethods#respond_to_missing?](https://github.com/marian13/convenient_service/blob/main/lib/convenient_service/core/class_methods.rb#L105), [ConvenientService::Core::InstanceMethods#respond_to_missing?](https://github.com/marian13/convenient_service/blob/main/lib/convenient_service/core/instance_methods.rb#L30) |
| Medium || Custom matcher to track `ConvenientService::Logger` messages | |
| High || Custom matcher to track `ConvenientService::Logger` messages | |
| Medium | 🚧 | Remove `respond_to?` from `Copyable` | Investigate before making any decision |
| High | 🚧 | Unified `inspect` | |
| High || Remove race condition for `method_missing` | https://github.com/marian13/convenient_service/pull/5 |
Expand Down
4 changes: 4 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ tasks:
- sh: '[ "${IN_DOCKER_CONTAINER}" != "true" ]'
msg: This task can be invoked only from the host operating system (https://www.ibm.com/cloud/learn/containerization)

deps:
cmds:
- task: deps:install

deps:install:
cmds:
- bundle install
Expand Down
15 changes: 8 additions & 7 deletions lib/convenient_service/core/class_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@ module ClassMethods
# concerns(&configuration_block)
#
def concerns(&configuration_block)
@concerns ||= Entities::Concerns.new(entity: self)

if configuration_block
@concerns ||= Entities::Concerns.new(entity: self)
@concerns.assert_not_included!
@concerns.configure(&configuration_block)
end

@concerns
@concerns || Entities::Concerns.new(entity: self)
end

##
Expand Down Expand Up @@ -75,14 +74,14 @@ def concerns(&configuration_block)
def middlewares(method, scope: :instance, &configuration_block)
@middlewares ||= {}
@middlewares[scope] ||= {}
@middlewares[scope][method] ||= Entities::MethodMiddlewares.new(scope: scope, method: method, container: self)

if configuration_block
@middlewares[scope][method] ||= Entities::MethodMiddlewares.new(scope: scope, method: method, klass: self)
@middlewares[scope][method].configure(&configuration_block)
@middlewares[scope][method].define!
end

@middlewares[scope][method]
@middlewares[scope][method] || Entities::MethodMiddlewares.new(scope: scope, method: method, klass: self)
end

##
Expand Down Expand Up @@ -130,9 +129,11 @@ def respond_to_missing?(method_name, include_private = false)
# IMPORTANT: `method_missing` should be thread-safe.
#
def method_missing(method, *args, **kwargs, &block)
concerns.include!
commit_config!

return super unless Utils::Module.class_method_defined?(self, method, private: true)

return super unless Utils::Method.defined?(method, singleton_class, private: true)
return super if middlewares(method, scope: :class).defined_without_super_method?

ConvenientService.logger.debug { "[Core] Included concerns into `#{self}` | Triggered by `method_missing` | Method: `.#{method}`" }

Expand Down
78 changes: 63 additions & 15 deletions lib/convenient_service/core/entities/method_middlewares.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,46 @@ module ConvenientService
module Core
module Entities
class MethodMiddlewares
include Support::Delegate

##
# @param scope [:instance, :class]
# @param method [Symbol, String]
# @param container [Class]
# @param klass [Class]
# @return [void]
#
def initialize(scope:, method:, container:)
def initialize(scope:, method:, klass:)
@scope = scope
@method = method
@container = Entities::Container.new(service_class: container)
@klass = klass
end

##
# @param entity [Object, Class]
# @param scope [:instance, :class]
# @param method [Symbol, String]
# @return [Method, nil]
# @return [String]
#
def no_super_method_exception_message_for(entity)
"super: no superclass method `foo' for #{entity}"
end

##
# @return [Boolean]
#
def self.resolve_super_method(entity, scope, method)
caller = Commands::CastCaller.call(other: {entity: entity, scope: scope})
def defined?
Utils::Module.has_own_instance_method?(methods_middlewares_callers, method)
end

return unless caller
##
# @return [Boolean]
#
def super_method_defined?
caller.super_method_defined?(method)
end

caller.resolve_super_method(method)
##
# @return [Boolean]
#
def defined_without_super_method?
self.defined? && !super_method_defined?
end

##
Expand Down Expand Up @@ -66,6 +82,19 @@ def call(env, original_method)
stack.dup.use(original_method).call(env.merge(method: method))
end

##
# @param entity [Object, Class]
# @return [Method, nil]
#
# @internal
# NOTE: Consider to remove when support for Ruby 2.7 is dropped.
#
def resolve_super_method(entity)
klass.commit_config!

caller.resolve_super_method(method, entity)
end

##
# @param other [ConvenientService::Core::Entities::MethodMiddlewares, Object]
# @return [Boolean, nil]
Expand All @@ -75,7 +104,7 @@ def ==(other)

return false if scope != other.scope
return false if method != other.method
return false if container != other.container
return false if klass != other.klass
return false if stack != other.stack

true
Expand Down Expand Up @@ -103,10 +132,10 @@ def to_a
attr_reader :method

##
# @!attribute [r] container
# @return [ConvenientService::Core::Entities::MethodMiddlewares::Entities::Container]
# @!attribute [r] klass
# @return [Symbol, String]
#
attr_reader :container
attr_reader :klass

##
# @return [ConvenientService::Core::Entities::MethodMiddlewares::Entities::Stack]
Expand All @@ -117,6 +146,25 @@ def stack

private

##
# @return [Module]
#
delegate :methods_middlewares_callers, to: :container

##
# @return [ConvenientService::Core::Entities::MethodMiddlewares::Entities::Caller]
#
def caller
@caller ||= Entities::Caller.new(container: container)
end

##
# @return [ConvenientService::Core::Entities::MethodMiddlewares::Entities::Container]
#
def container
@container ||= Entities::Container.cast!({scope: scope, klass: klass})
end

##
# @return [String]
#
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def call
# @return [String]
#
def formatted_container
container.service_class
container.klass
end

##
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative "entities/callers"
require_relative "entities/caller"
require_relative "entities/chain"
require_relative "entities/container"
require_relative "entities/middleware"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# frozen_string_literal: true

module ConvenientService
module Core
module Entities
class MethodMiddlewares
module Entities
class Caller
include Support::Delegate

##
# @!attribute [r] container
# @return [ConvenientService::Core::Entities::MethodMiddlewares::Entities::Container]
#
attr_reader :container

##
# @return [Class]
#
delegate :klass, to: :container

##
# @return [Module, nil]
#
delegate :methods_middlewares_callers, to: :container

##
# @return [Array<Class, Module>]
#
delegate :ancestors, to: :klass

##
# @param container [ConvenientService::Core::Entities::MethodMiddlewares::Entities::Container]
# @return [void]
#
def initialize(container:)
@container = container
end

##
# @param method_name [Symbol, String]
# @return [UnboundMethod, nil]
#
def super_method_defined?(method_name)
Utils::Bool.to_bool(resolve_unbound_super_method(method_name))
end

##
# Returns ancestors before the `methods_middlewares_callers` module. See the example below.
# When ancestors do NOT contain `methods_middlewares_callers`, returns an empty array.
#
# @return [Array<Class, Module>]
#
# @example The entity is an object.
# class Service
# include ConvenientService::Core
#
# middlewares :result do
# use ConvenientService::Plugins::Common::NormalizesEnv
# end
# end
#
# entity = Service.new
#
# entity.class.ancestors
# # [Service::InstanceMethodsMiddlewaresCallers, Service, ConvenientService::Core::InstanceMethods, ConvenientService::Core, ConvenientService::Support::Concern, Object, Kernel, BasicObject]
#
# ancestors_greater_than_methods_middlewares_callers # For the entity defined above.
# # [Service, ConvenientService::Core::InstanceMethods, ConvenientService::Core, ConvenientService::Support::Concern, Object, Kernel, BasicObject]
#
# @example The entity is class.
# class Service
# include ConvenientService::Core
#
# middlewares :result, scope: :class do
# use ConvenientService::Plugins::Common::NormalizesEnv
# end
# end
#
# entity = Service
#
# entity.singleton_class.ancestors
# # [Service::ClassMethodsMiddlewaresCallers, #<Class:Service>, ConvenientService::Core::ClassMethods, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
#
# ancestors_greater_than_methods_middlewares_callers # For the entity defined above.
# # [#<Class:Service>, ConvenientService::Core::ClassMethods, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]# [Service::ClassMethodsMiddlewaresCallers, #<Class:Service>, ConvenientService::Core::ClassMethods, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
#
# @internal
# NOTE: greater than -> futher in the inheritance chain than
# https://ruby-doc.org/core-2.7.0/Module.html#method-i-3E
#
# NOTE: lower than -> closer in the inheritance chain than
# https://ruby-doc.org/core-2.7.0/Module.html#method-i-ancestors
#
def ancestors_greater_than_methods_middlewares_callers
Utils::Array.keep_after(ancestors, methods_middlewares_callers)
end

##
# @param method_name [Symbol, String]
# @return [UnboundMethod, nil]
#
def resolve_unbound_super_method(method_name)
Utils::Array.find_yield(ancestors_greater_than_methods_middlewares_callers) { |ancestor| Utils::Module.get_own_instance_method(ancestor, method_name, private: true) }
end

##
# @param method_name [Symbol, String]
# @param entity [Class, Object]
# @return [Method, nil]
#
def resolve_super_method(method_name, entity)
unbound_super_method = resolve_unbound_super_method(method_name)

return unless unbound_super_method

unbound_super_method.bind(entity)
end

##
# @param other [ConvenientService::Core::Entities::MethodMiddlewares::Entities::Callers::Base, Object]
# @return [Boolean]
#
def ==(other)
return unless other.instance_of?(self.class)

return false if container != other.container

true
end

##
# @return [Hash]
#
def to_kwargs
{container: container}
end
end
end
end
end
end
end

This file was deleted.

Loading

0 comments on commit b43e483

Please sign in to comment.