Skip to content

Commit

Permalink
Merge fc3d617 into 8c96891
Browse files Browse the repository at this point in the history
  • Loading branch information
marian13 committed Oct 17, 2022
2 parents 8c96891 + fc3d617 commit e80c3cd
Show file tree
Hide file tree
Showing 14 changed files with 703 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@ class Base
attr_reader :entity

##
#
# @return [void]
# @raise [ConvenientService::Support::AbstractMethod::Errors::AbstractMethodNotOverridden] if NOT overridden in descendant.
#
abstract_method :commit_config!

##
#
# @return [Array<Class, Module>]
# @raise [ConvenientService::Support::AbstractMethod::Errors::AbstractMethodNotOverridden] if NOT overridden in descendant.
#
abstract_method :ancestors

##
#
# @return [Module]
# @raise [ConvenientService::Support::AbstractMethod::Errors::AbstractMethodNotOverridden] if NOT overridden in descendant.
#
abstract_method :methods_middlewares_callers

Expand All @@ -39,22 +42,68 @@ def initialize(entity:)
end

##
# @param method [Symbol, String]
# @return [Method]
# 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
# TODO: Consider to create `Utils::Array.find_map`
# - https://github.com/rubyworks/facets/blob/main/lib/core/facets/enumerable/find_yield.rb
# - https://stackoverflow.com/a/38457218/12201472
# 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 resolve_super_method(method)
def ancestors_greater_than_methods_middlewares_callers
Utils::Array.keep_after(ancestors, methods_middlewares_callers)
end

##
# @param method_name [Symbol, String]
# @return [Method, nil]
#
def resolve_super_method(method_name)
commit_config!

ancestors
.then { |ancestors| Utils::Array.drop_while(ancestors, inclusively: true) { |ancestor| ancestor != methods_middlewares_callers } }
.find { |ancestor| Utils::Module.has_own_instance_method?(ancestor, method, private: true) }
.then { |ancestor| Utils::Module.get_own_instance_method(ancestor, method, private: true) }
.bind(entity)
method = Utils::Array.find_yield(ancestors_greater_than_methods_middlewares_callers) { |ancestor| Utils::Module.get_own_instance_method(ancestor, method_name, private: true) }

return unless method

method.bind(entity)
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def ancestors
# @return [Module]
#
def methods_middlewares_callers
entity::ClassMethodsMiddlewaresCallers
Utils::Module.get_own_const(entity, :ClassMethodsMiddlewaresCallers)
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ def ancestors
end

##
# @return [Module]
# @return [Module, nil]
#
def methods_middlewares_callers
entity.class::InstanceMethodsMiddlewaresCallers
Utils::Module.get_own_const(entity.class, :InstanceMethodsMiddlewaresCallers)
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ def result
private

##
# @return [Method, nil]
#
# @internal
# TODO: A possible bottleneck. Should be removed if receives negative feedback.
#
Expand Down
10 changes: 10 additions & 0 deletions lib/convenient_service/utils/array.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
require_relative "array/contain_exactly"
require_relative "array/drop_while"
require_relative "array/find_last"
require_relative "array/find_yield"
require_relative "array/keep_after"
require_relative "array/merge"
require_relative "array/rjust"
require_relative "array/wrap"
Expand All @@ -25,6 +27,14 @@ def find_last(...)
FindLast.call(...)
end

def find_yield(...)
FindYield.call(...)
end

def keep_after(...)
KeepAfter.call(...)
end

def merge(...)
Merge.call(...)
end
Expand Down
93 changes: 93 additions & 0 deletions lib/convenient_service/utils/array/find_yield.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# frozen_string_literal: true

module ConvenientService
module Utils
module Array
##
# @example
# Preconditions (pseudo-real world):
#
# - Both `match?` and `match` do exactly the same thing, only their return values are different.
# - `match?` returns `true` when the string matches the regex, otherwise - `false`.
# - `match` returns regex match data when the string matches the regex, otherwise - `nil`.
# - Regex matching is a very resource-heavy process.
#
# - `test` is defined like this:
# def test
# string = strings.find { |string| string.match?(regexp) }
#
# return unless string
#
# string.match(regexp)
# end
#
# - To simplify the example, imagine that `strings` return an array with only one string and that string matches regex.
# def strings
# ["foo bar"]
# end
#
# def regexp
# /\w+/
# end
# - This way regex matching (a very resource-heavy process) is executed twice.
# - First time during the `match?` method call.
# - The second time during the `match` method call.
#
# Task:
# - How to implement `test` in a way that regexp matching (very resource-heavy process) is executed only once for the preconditions above?
#
# Solution:
# - Use `ConvenientService::Utils::Array.find_yield`
# - `test` should be rewritten like this:
# def test
# ConvenientService::Utils::Array.find_yield(strings) { |string| string.match(regexp) }
# end
#
# Inspiration:
# - https://github.com/rubyworks/facets/blob/3.1.0/lib/core/facets/enumerable/find_yield.rb#L28
# - https://stackoverflow.com/a/38457218/12201472
#
# Note: `String#match?` and `String#match` docs.
# - https://ruby-doc.org/core-2.7.0/String.html#method-i-match-3F
# - https://ruby-doc.org/core-2.7.0/String.html#method-i-match
#
class FindYield < Support::Command
##
# @!attribute [r] array
# @return [Array]
#
attr_reader :array

##
# @!attribute [r] block
# @return [Proc]
#
attr_reader :block

##
# @param array [Array]
# @param block [Proc]
# @return [void]
#
def initialize(array, &block)
@array = array
@block = block
end

##
# @return [Object, nil] Can be any type.
# @see https://github.com/rubyworks/facets/blob/main/lib/core/facets/enumerable/find_yield.rb#L28
#
def call
array.each do |item|
block_value = block.call(item)

return block_value if block_value
end

nil
end
end
end
end
end
41 changes: 41 additions & 0 deletions lib/convenient_service/utils/array/keep_after.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

module ConvenientService
module Utils
module Array
class KeepAfter < Support::Command
##
# @!attribute [r] array
# @return [Array]
#
attr_reader :array

##
# @!attribute [r] object
# @return [Object] Can be any type.
#
attr_reader :object

##
# @param array [Array]
# @param object [Object] Can be any type.
# @return [void]
#
def initialize(array, object)
@array = array
@object = object
end

##
# @return [Object, nil] Can be any type.
# @see https://github.com/rubyworks/facets/blob/main/lib/core/facets/array/before.rb#L35
#
def call
return [] unless array.include?(object)

Utils::Array.drop_while(array, inclusively: true) { |item| item != object }
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

require "spec_helper"

require "convenient_service"

RSpec.describe ConvenientService::Core::Entities::MethodMiddlewares::Entities::Callers::Base do
include ConvenientService::RSpec::Matchers::DelegateTo

let(:caller) { caller_class.new(entity: entity) }
let(:caller_class) { described_class }

let(:entity) { double }
let(:method) { :result }

example_group "modules" do
include ConvenientService::RSpec::Matchers::IncludeModule

subject { described_class }

it { is_expected.to include_module(ConvenientService::Support::AbstractMethod) }
end

example_group "attributes" do
include ConvenientService::RSpec::Matchers::HaveAttrReader

subject { caller }

it { is_expected.to have_attr_reader(:entity) }
end

example_group "abstract methods" do
include ConvenientService::RSpec::Matchers::HaveAbstractMethod

subject { caller }

it { is_expected.to have_abstract_method(:commit_config!) }
it { is_expected.to have_abstract_method(:ancestors) }
it { is_expected.to have_abstract_method(:methods_middlewares_callers) }
end

# example_group "instance methods" do
# describe "#ancestors_greater_than_methods_middlewares_callers" do
# ##
# # NOTE: `#ancestors_greater_than_methods_middlewares_callers` is tested in `ConvenientService::Core::Entities::MethodMiddlewares::Entities::Callers::Base` descendants.
# #
# end
#
# describe "#resolve_super_method" do
# ##
# # NOTE: `#resolve_super_method` is tested in `ConvenientService::Core::Entities::MethodMiddlewares::Entities::Callers::Base` descendants.
# #
# end
# end
end
Loading

0 comments on commit e80c3cd

Please sign in to comment.