Skip to content

Commit

Permalink
Merge bbd0b73 into ba53e02
Browse files Browse the repository at this point in the history
  • Loading branch information
marian13 committed Oct 9, 2022
2 parents ba53e02 + bbd0b73 commit 505497d
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 44 deletions.
2 changes: 0 additions & 2 deletions lib/convenient_service/core/class_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ def concerns(&configuration_block)

return @concerns unless configuration_block

@concerns.assert_not_included!

@concerns.configure(&configuration_block)

@concerns
Expand Down
46 changes: 14 additions & 32 deletions lib/convenient_service/core/entities/concerns.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,6 @@ def private_class_method_defined?(method_name)
private_class_method_defined_in_class_methods_modules?(method_name)
end

##
# @return [void]
#
def assert_not_included!
return unless included?

raise Errors::ConcernsAreIncluded.new(concerns: concerns)
end

##
# @return [void]
#
Expand All @@ -69,30 +60,26 @@ def configure(&configuration_block)
end

##
# Includes concerns into entity when called for the first time.
# Does nothing for the subsequent calls.
# Includes concerns into entity.
#
# @return [Boolean] true if called for the first time, false otherwise (similarly as Kernel#require).
# @return [void]
#
# @see https://ruby-doc.org/core-3.1.2/Kernel.html#method-i-require
# @see https://stackoverflow.com/questions/1077412/what-is-an-idempotent-operation
#
def include!
return false if included?

stack.call(entity: stack.entity)

mark_as_included!

true
end

##
# Checks whether concerns are included into entity (include! was called at least once).
#
# @return [Boolean]
# @param other [ConvenientService::Core::Entities::Concerns, Object]
# @return [Boolean, nil]
#
def included?
Utils::Bool.to_bool(@included)
def ==(other)
return unless other.instance_of?(self.class)

return false if stack != other.stack

true
end

##
Expand All @@ -102,14 +89,16 @@ def to_a
plain_concerns
end

private
protected

##
# @!attribute [r] stack
# @return [ConvenientService::Core::Entities::Concerns::Entities::Stack]
#
attr_reader :stack

private

##
# @return [Array<Module>]
#
Expand Down Expand Up @@ -182,13 +171,6 @@ def class_methods_modules
.select { |concern| concern.const_defined?(:ClassMethods, false) }
.map { |concern| concern.const_get(:ClassMethods) }
end

##
# @return [void]
#
def mark_as_included!
@included = true
end
end
end
end
Expand Down
12 changes: 12 additions & 0 deletions lib/convenient_service/core/entities/concerns/entities/stack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,18 @@ def to_a
stack.to_a
end

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

return false if stack != other.stack

true
end

##
# TODO: Unify `inspect`. Specs for `inspect`.
#
Expand Down
38 changes: 30 additions & 8 deletions lib/convenient_service/support/middleware/stack_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ module ConvenientService
module Support
module Middleware
##
# TODO: Contribute.
# @internal
# TODO: Contribute.
#
# NOTE: Minimal `ibsciss-middleware` version - `0.4.2`.
# https://github.com/Ibsciss/ruby-middleware/tree/v0.4.2
# NOTE: Minimal `ibsciss-middleware` version - `0.4.2`.
# https://github.com/Ibsciss/ruby-middleware/tree/v0.4.2
#
class StackBuilder < Extractions::RubyMiddleware::Middleware::Builder
##
# https://github.com/Ibsciss/ruby-middleware/blob/v0.4.2/lib/middleware/builder.rb#L43
# @param opts [Hash]
# @param block [Proc]
# @return [void]
#
# @see https://github.com/Ibsciss/ruby-middleware/blob/v0.4.2/lib/middleware/builder.rb#L43
#
def initialize(opts = {}, &block)
super
Expand All @@ -23,16 +28,33 @@ def initialize(opts = {}, &block)
end

##
# NOTE: `use` can accept additional arguments and block,
# that is why `stack` contains tuples like [middleware, args, block].
# https://github.com/Ibsciss/ruby-middleware/blob/v0.4.2/lib/middleware/builder.rb#L76
# @param other [ConvenientService::Support::Middleware::StackBuilder, Object]
# @return [Boolean, nil]
#
def ==(other)
return unless other.instance_of?(self.class)

return false if stack != other.stack

true
end

##
# @return [Array]
#
# @internal
# NOTE: `use` can accept additional arguments and block, that is why `stack` contains tuples like [middleware, args, block].
# https://github.com/Ibsciss/ruby-middleware/blob/v0.4.2/lib/middleware/builder.rb#L76
#
# TODO: better name than just `to_a`.
# TODO: better name than just `to_a`.
#
def to_a
stack
end

##
# @return [ConvenientService::Support::Middleware::StackBuilder]
#
def dup
self.class.new(
##
Expand Down
66 changes: 64 additions & 2 deletions spec/lib/convenient_service/core/class_methods_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,70 @@
# rubocop:disable RSpec/NestedGroups
RSpec.describe ConvenientService::Core::ClassMethods do
include ConvenientService::RSpec::Matchers::DelegateTo
include ConvenientService::RSpec::Matchers::CacheItsValue

let(:service_instance) { service_class.new }
describe "#concerns" do
let(:service_class) do
Class.new do
include ConvenientService::Core
end
end

context "when `configuration_block` is NOT passed" do
let(:concerns) { ConvenientService::Core::Entities::Concerns.new(entity: service_class) }

it "returns concerns" do
expect(service_class.concerns).to eq(concerns)
end

specify { expect { service_class.concerns }.to cache_its_value }
end

context "when `configuration_block` is passed" do
let(:concerns) do
ConvenientService::Core::Entities::Concerns.new(entity: service_class)
.tap { |concerns| concerns.configure(&configuration_block) }
end

let(:configuration_block) do
proc do
##
# NOTE: Simplest concern is just a module.
#
concern = Module.new do
class << self
##
# NOTE: `name` returns `nil` when module is anonymous.
# `name` and `==` are overridden to make sure that module was defined in this particular test suite.
# https://ruby-doc.org/core-2.7.0/Module.html#method-i-name
#
# NOTE: For `expect(service_class.concerns(&configuration_block)).to eq(concerns)`.
#
def name
"Anonymous module from test"
end

def ==(other)
name == other.name
end
end
end

use concern
end
end

specify {
expect { service_class.concerns(&configuration_block) }
.to delegate_to(service_class.concerns, :configure)
.with_arguments(&configuration_block)
}

it "returns concerns" do
expect(service_class.concerns(&configuration_block)).to eq(concerns)
end
end
end

describe "#method_missing" do
let(:service_class) do
Expand Down Expand Up @@ -284,7 +346,7 @@ def foo
end

context "when `include_private` is NOT passed" do
let(:result) { service_instance.respond_to_missing?(method_name) }
let(:result) { service_class.respond_to_missing?(method_name) }

let(:service_class) do
Class.new do
Expand Down
31 changes: 31 additions & 0 deletions spec/lib/convenient_service/core_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

require "spec_helper"

require "convenient_service"

RSpec.describe ConvenientService::Core do
example_group "modules" do
include ConvenientService::RSpec::Matchers::IncludeModule
include ConvenientService::RSpec::Matchers::ExtendModule

subject { described_class }

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

context "when included" do
subject { service_class }

let(:service_class) do
Class.new.tap do |klass|
klass.class_exec(described_class) do |mod|
include mod
end
end
end

it { is_expected.to include_module(ConvenientService::Core::InstanceMethods) }
it { is_expected.to extend_module(ConvenientService::Core::ClassMethods) }
end
end
end

0 comments on commit 505497d

Please sign in to comment.