From 94ea2e1207d0839a9e9c9277511cddeff43f697f Mon Sep 17 00:00:00 2001 From: Sergei Malykh Date: Tue, 10 Dec 2024 15:39:07 +0300 Subject: [PATCH] wip --- Gemfile | 1 + Gemfile.lock | 88 +++++++++++++++++++ lib/active_dry_deps/container.rb | 12 ++- lib/active_dry_deps/dependency.rb | 138 +++++++++++++++++++----------- lib/active_dry_deps/deps.rb | 23 ++--- lib/active_dry_deps/stub.rb | 28 ++++-- spec/active_dry_deps_spec.rb | 7 -- spec/app/create_order.rb | 2 +- spec/spec_helper.rb | 2 + 9 files changed, 217 insertions(+), 84 deletions(-) diff --git a/Gemfile b/Gemfile index 32be448..9a38d6f 100644 --- a/Gemfile +++ b/Gemfile @@ -9,5 +9,6 @@ gem 'rake', '~> 13.0' gem 'rspec', '~> 3.0' gem 'activesupport' +gem 'rails' gem 'combustion' gem 'rubocop-gp', github: 'corp-gp/rubocop-gp', require: false diff --git a/Gemfile.lock b/Gemfile.lock index a5ed47b..bd22dfc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,6 +17,31 @@ PATH GEM remote: https://rubygems.org/ specs: + actioncable (7.0.7) + actionpack (= 7.0.7) + activesupport (= 7.0.7) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailbox (7.0.7) + actionpack (= 7.0.7) + activejob (= 7.0.7) + activerecord (= 7.0.7) + activestorage (= 7.0.7) + activesupport (= 7.0.7) + mail (>= 2.7.1) + net-imap + net-pop + net-smtp + actionmailer (7.0.7) + actionpack (= 7.0.7) + actionview (= 7.0.7) + activejob (= 7.0.7) + activesupport (= 7.0.7) + mail (~> 2.5, >= 2.5.4) + net-imap + net-pop + net-smtp + rails-dom-testing (~> 2.0) actionpack (7.0.7) actionview (= 7.0.7) activesupport (= 7.0.7) @@ -24,12 +49,34 @@ GEM rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) + actiontext (7.0.7) + actionpack (= 7.0.7) + activerecord (= 7.0.7) + activestorage (= 7.0.7) + activesupport (= 7.0.7) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) actionview (7.0.7) activesupport (= 7.0.7) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) + activejob (7.0.7) + activesupport (= 7.0.7) + globalid (>= 0.3.6) + activemodel (7.0.7) + activesupport (= 7.0.7) + activerecord (7.0.7) + activemodel (= 7.0.7) + activesupport (= 7.0.7) + activestorage (7.0.7) + actionpack (= 7.0.7) + activejob (= 7.0.7) + activerecord (= 7.0.7) + activesupport (= 7.0.7) + marcel (~> 1.0) + mini_mime (>= 1.1.0) activesupport (7.0.7) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) @@ -44,6 +91,7 @@ GEM thor (>= 0.14.6) concurrent-ruby (1.2.2) crass (1.0.6) + date (3.4.1) diff-lcs (1.5.0) dry-configurable (1.1.0) dry-core (~> 1.0, < 2) @@ -52,6 +100,8 @@ GEM concurrent-ruby (~> 1.0) zeitwerk (~> 2.6) erubi (1.13.0) + globalid (1.2.1) + activesupport (>= 6.1) i18n (1.14.1) concurrent-ruby (~> 1.0) json (2.6.3) @@ -59,8 +109,27 @@ GEM loofah (2.23.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.0.4) method_source (1.1.0) + mini_mime (1.1.5) minitest (5.19.0) + net-imap (0.5.1) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.0) + net-protocol + nio4r (2.7.4) + nokogiri (1.16.8-arm64-darwin) + racc (~> 1.4) nokogiri (1.16.8-x86_64-linux) racc (~> 1.4) parallel (1.23.0) @@ -71,6 +140,20 @@ GEM rack (2.2.8) rack-test (2.1.0) rack (>= 1.3) + rails (7.0.7) + actioncable (= 7.0.7) + actionmailbox (= 7.0.7) + actionmailer (= 7.0.7) + actionpack (= 7.0.7) + actiontext (= 7.0.7) + actionview (= 7.0.7) + activejob (= 7.0.7) + activemodel (= 7.0.7) + activerecord (= 7.0.7) + activestorage (= 7.0.7) + activesupport (= 7.0.7) + bundler (>= 1.15.0) + railties (= 7.0.7) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -133,9 +216,13 @@ GEM rubocop-factory_bot (~> 2.22) ruby-progressbar (1.13.0) thor (1.3.2) + timeout (0.4.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.4.2) + websocket-driver (0.7.6) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) zeitwerk (2.6.11) PLATFORMS @@ -146,6 +233,7 @@ DEPENDENCIES active_dry_deps! activesupport combustion + rails rake (~> 13.0) rspec (~> 3.0) rubocop-gp! diff --git a/lib/active_dry_deps/container.rb b/lib/active_dry_deps/container.rb index b778e02..f89cb78 100644 --- a/lib/active_dry_deps/container.rb +++ b/lib/active_dry_deps/container.rb @@ -3,12 +3,16 @@ module ActiveDryDeps class Container < Hash - def resolve(const_name) - unless key?(const_name) - self[const_name] = Object.const_get(const_name) + def resolve(container_key) + unless key?(container_key) + self[container_key] = Object.const_get(container_key) end - self[const_name] + self[container_key] + end + + def register(container_key, &block) + self[container_key.to_s] = block end end diff --git a/lib/active_dry_deps/dependency.rb b/lib/active_dry_deps/dependency.rb index 9c700f8..aa79bee 100644 --- a/lib/active_dry_deps/dependency.rb +++ b/lib/active_dry_deps/dependency.rb @@ -3,88 +3,124 @@ module ActiveDryDeps class Dependency - VALID_METHOD_NAME = /^[a-zA-Z_0-9]+$/ - VALID_CONST_NAME = /^[[:upper:]][a-zA-Z_0-9\:]*$/ - METHODS_AS_KLASS = %w[perform_later call].freeze + LOWER = /[[:lower:]]/ - attr_reader :receiver_method_name, :receiver_method, :const_name, :method_name + attr_reader :receiver_method_name, :resolve_key, :const_name, :method_name def initialize(resolver, receiver_method_alias: nil) - if resolver.respond_to?(:call) - receiver_method_by_proc(resolver, receiver_method_alias: receiver_method_alias) + if LOWER.match?(resolver[0]) + receiver_method_by_key(resolver, receiver_method_alias:) else - receiver_method_by_const_name(resolver, receiver_method_alias: receiver_method_alias) + receiver_method_by_const_name(resolver, receiver_method_alias:) end end - def callable? - !@receiver_method.nil? - end - - def receiver_method_string - if @method_name - <<~RUBY - # def create_order(*args, **options, &block) - # ::ActiveDryDeps::Deps::CONTAINER.resolve("OrderService::Create").call(*args, **options, &block) - # end - - def #{@receiver_method_name}(*args, **options, &block) - ::ActiveDryDeps::Deps::CONTAINER.resolve("#{@const_name}").#{@method_name}(*args, **options, &block) - end - RUBY - else - <<~RUBY - # def create_order_service - # ::ActiveDryDeps::Deps::CONTAINER.resolve("OrderService::Create") - # end - - def #{@receiver_method_name} - ::ActiveDryDeps::Deps::CONTAINER.resolve("#{@const_name}") - end - RUBY + if Rails.env.test? + def receiver_method_string + Kernel.sprintf( + METHOD_TEMPLATES.fetch(container: true, method_call: !@method_name.nil?), + receiver_method_name: @receiver_method_name, + container_key_or_const_name: @const_name || @resolve_key, + method_name: @method_name, + ) + end + else + def receiver_method_string + Kernel.sprintf( + METHOD_TEMPLATES.fetch(container: !@resolve_key.nil?, method_call: !@method_name.nil?), + receiver_method_name: @receiver_method_name, + container_key_or_const_name: @const_name || @resolve_key, + method_name: @method_name, + ) end end - private def receiver_method_by_proc(resolver, receiver_method_alias: nil) - unless receiver_method_alias - raise MissingAlias, 'Alias is required while injecting with Proc' - end + VALID_METHOD_NAME = /^[[[:alnum:]]_]+$/ - @receiver_method_name = receiver_method_alias + private def receiver_method_by_key(resolver, receiver_method_alias: nil) + receiver_method_name = receiver_method_alias || resolver - unless VALID_METHOD_NAME.match?(@receiver_method_name.to_s) - raise DependencyNameInvalid, "name +#{@receiver_method_name}+ is not a valid Ruby identifier" + unless VALID_METHOD_NAME.match?(receiver_method_name.to_s) + raise DependencyNameInvalid, "name +#{receiver_method_name}+ is not a valid Ruby identifier" end - @receiver_method = resolver + @receiver_method_name = receiver_method_name.to_sym + @resolve_key = resolver + @method_name = :call end + VALID_CONST_NAME = /^[[:upper:]][[[:alnum:]]:_]*$/ + METHODS_AS_KLASS = %w[perform_later call].freeze + private def receiver_method_by_const_name(resolver, receiver_method_alias: nil) - @const_name, @method_name = resolver.to_s.split('.', 2) + const_name, method_name = resolver.to_s.split('.', 2) - unless VALID_CONST_NAME.match?(@const_name) + unless VALID_CONST_NAME.match?(const_name) raise DependencyNameInvalid, "+#{resolver}+ must contains valid constant name" end - if @method_name && !VALID_METHOD_NAME.match?(@method_name) - raise DependencyNameInvalid, "name +#{@method_name}+ is not a valid Ruby identifier" + if method_name && !VALID_METHOD_NAME.match?(method_name) + raise DependencyNameInvalid, "name +#{method_name}+ is not a valid Ruby identifier" end - @receiver_method_name = + receiver_method_name = if receiver_method_alias receiver_method_alias - elsif @method_name && METHODS_AS_KLASS.exclude?(@method_name) - @method_name - elsif @const_name - @const_name.split('::').last + elsif method_name && METHODS_AS_KLASS.exclude?(method_name) + method_name + elsif const_name + const_name.split('::').last else resolver end - unless VALID_METHOD_NAME.match?(@receiver_method_name.to_s) - raise DependencyNameInvalid, "name +#{@receiver_method_name}+ is not a valid Ruby identifier" + unless VALID_METHOD_NAME.match?(receiver_method_name.to_s) + raise DependencyNameInvalid, "name +#{receiver_method_name}+ is not a valid Ruby identifier" end + + @receiver_method_name = receiver_method_name.to_sym + @const_name = const_name + @method_name = method_name&.to_sym end + METHOD_TEMPLATES = { + { container: true, method_call: true } => <<~RUBY, + # def create_order(...) + # ::ActiveDryDeps::Deps::CONTAINER.resolve("OrderService::Create").call(...) + # end + + def %{receiver_method_name}(...) + ::ActiveDryDeps::Deps::CONTAINER.resolve("%{container_key_or_const_name}").%{method_name}(...) + end + RUBY + { container: true, method_call: false } => <<~RUBY, + # def create_order + # ::ActiveDryDeps::Deps::CONTAINER.resolve("OrderService::Create") + # end + + def %{receiver_method_name} + ::ActiveDryDeps::Deps::CONTAINER.resolve("%{container_key_or_const_name}") + end + RUBY + { container: false, method_call: true } => <<~RUBY, + # def create_order(...) + # OrderService::Create.call(...) + # end + + def %{receiver_method_name}(...) + %{container_key_or_const_name}.%{method_name}(...) + end + RUBY + { container: false, method_call: false } => <<~RUBY, + # def create_order + # OrderService::Create + # end + + def %{receiver_method_name} + %{container_key_or_const_name} + end + RUBY + } + end end diff --git a/lib/active_dry_deps/deps.rb b/lib/active_dry_deps/deps.rb index e3ca62b..d421326 100644 --- a/lib/active_dry_deps/deps.rb +++ b/lib/active_dry_deps/deps.rb @@ -11,30 +11,25 @@ module Deps # include Deps['Lib::Routes.admin'] use as `admin` # include Deps['Lib::Routes'] use as `Routes()` # include Deps['OrderService::Recalculate.call'] use as `Recalculate()` - # include Deps[send_email: -> { 'email-sent' }] use as `send_email` def [](*keys, **aliases) m = Module.new - dependencies = [] - dependencies += keys.map { |resolver| Dependency.new(resolver) } - dependencies += aliases.map { |alias_method, resolver| Dependency.new(resolver, receiver_method_alias: alias_method) } + receiver_methods = +'' - call_dependencies, constant_dependencies = dependencies.partition(&:callable?) - - m.module_eval(constant_dependencies.map(&:receiver_method_string).join("\n")) + keys.each do |resolver| + receiver_methods << Dependency.new(resolver).receiver_method_string << "\n" + end - call_dependencies.each do |dependency| - m.define_method(dependency.receiver_method_name, &dependency.receiver_method) + aliases.each do |alias_method, resolver| + receiver_methods << Dependency.new(resolver, receiver_method_alias: alias_method).receiver_method_string << "\n" end + m.module_eval(receiver_methods) m end - # TODO: необходимость сомнительна - def resolve(resolver) - dependency = Dependency.new(resolver) - m = Module.new { module_function module_eval(dependency.receiver_method_string) } - m.public_send(dependency.receiver_method_name) + def register(...) + CONTAINER.register(...) end end diff --git a/lib/active_dry_deps/stub.rb b/lib/active_dry_deps/stub.rb index 0742064..1aaa68b 100644 --- a/lib/active_dry_deps/stub.rb +++ b/lib/active_dry_deps/stub.rb @@ -4,8 +4,8 @@ module ActiveDryDeps module StubDeps - def stub(const_name, proxy_object, &block) - self::CONTAINER.stub(const_name, proxy_object, &block) + def stub(key, proxy_object, &block) + self::CONTAINER.stub(key, proxy_object, &block) end def unstub(*keys) @@ -16,20 +16,34 @@ def unstub(*keys) module StubContainer - def stub(const_name, proxy_object) + def self.extended(container) + const_set(:CONTAINER_ORIG, container.dup) + end + + def stub(key, proxy_object) if block_given? begin - self[const_name] = proxy_object + self[key] = proxy_object ensure - delete(const_name) + unstub(key) end else - self[const_name] = proxy_object + self[key] = proxy_object end end def unstub(*unstub_keys) - (unstub_keys.empty? ? keys : unstub_keys).each { |const_name| delete(const_name) } + if unstub_keys.empty? + replace(CONTAINER_ORIG) + else + unstub_keys.each do |key| + if CONTAINER_ORIG.key?(key) + self[key] = CONTAINER_ORIG[key] + else + delete(key) + end + end + end end end diff --git a/spec/active_dry_deps_spec.rb b/spec/active_dry_deps_spec.rb index f0d5826..35a861c 100644 --- a/spec/active_dry_deps_spec.rb +++ b/spec/active_dry_deps_spec.rb @@ -31,13 +31,6 @@ }.to raise_error(ActiveDryDeps::DependencyNameInvalid, 'name +!invalid_identifier+ is not a valid Ruby identifier') end - describe '#resolve' do - it 'dependencies resolved' do - expect(Deps.resolve('CreateDeparture')).to eq CreateDeparture - expect(Deps.resolve('SupplierSync::ReserveJob')).to eq SupplierSync::ReserveJob - end - end - describe '#stub' do def expect_call_orig expect(CreateOrder.call).to eq %w[CreateDeparture CreateDeparture job-performed message-ok email-sent] diff --git a/spec/app/create_order.rb b/spec/app/create_order.rb index daec05a..d4515da 100644 --- a/spec/app/create_order.rb +++ b/spec/app/create_order.rb @@ -10,7 +10,7 @@ def self.call 'CreateDeparture', 'Utils.message', 'SupplierSync::ReserveJob.perform_later', - send_mail: -> { 'email-sent' }, + 'send_mail', CreateDepartureCallable: 'CreateDeparture.call', ] diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2dce33b..b7cb097 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,6 +8,8 @@ Rails.application.initialize! +Deps.register(:send_mail) { 'email-sent' } + Dir['./spec/app/**/*.rb'].each { |f| require f } Dir['./spec/support/*.rb'].each { |f| require f }