Skip to content

Commit

Permalink
refactor container
Browse files Browse the repository at this point in the history
  • Loading branch information
xronos-i-am committed Dec 9, 2024
1 parent 29dbccd commit d5f99e7
Show file tree
Hide file tree
Showing 14 changed files with 208 additions and 131 deletions.
2 changes: 0 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,4 @@ gem 'rspec', '~> 3.0'

gem 'activesupport'
gem 'combustion'
gem 'dry-container'
gem 'dry-system'
gem 'rubocop-gp', github: 'corp-gp/rubocop-gp', require: false
33 changes: 9 additions & 24 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -37,44 +37,31 @@ GEM
tzinfo (~> 2.0)
ast (2.4.2)
base64 (0.1.1)
builder (3.2.4)
combustion (1.3.7)
builder (3.3.0)
combustion (1.5.0)
activesupport (>= 3.0.0)
railties (>= 3.0.0)
thor (>= 0.14.6)
concurrent-ruby (1.2.2)
crass (1.0.6)
diff-lcs (1.5.0)
dry-auto_inject (1.0.1)
dry-core (~> 1.0)
zeitwerk (~> 2.6)
dry-configurable (1.1.0)
dry-core (~> 1.0, < 2)
zeitwerk (~> 2.6)
dry-container (0.11.0)
concurrent-ruby (~> 1.0)
dry-core (1.0.1)
concurrent-ruby (~> 1.0)
zeitwerk (~> 2.6)
dry-inflector (1.0.0)
dry-system (1.0.1)
dry-auto_inject (~> 1.0, < 2)
dry-configurable (~> 1.0, < 2)
dry-core (~> 1.0, < 2)
dry-inflector (~> 1.0, < 2)
erubi (1.12.0)
erubi (1.13.0)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
json (2.6.3)
language_server-protocol (3.17.0.3)
loofah (2.21.3)
loofah (2.23.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
method_source (1.0.0)
method_source (1.1.0)
minitest (5.19.0)
nokogiri (1.15.4-arm64-darwin)
racc (~> 1.4)
nokogiri (1.15.4-x86_64-linux)
nokogiri (1.16.8-x86_64-linux)
racc (~> 1.4)
parallel (1.23.0)
parser (3.2.2.3)
Expand All @@ -88,9 +75,9 @@ GEM
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0)
rails-html-sanitizer (1.6.1)
loofah (~> 2.21)
nokogiri (~> 1.14)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
railties (7.0.7)
actionpack (= 7.0.7)
activesupport (= 7.0.7)
Expand Down Expand Up @@ -145,7 +132,7 @@ GEM
rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22)
ruby-progressbar (1.13.0)
thor (1.2.2)
thor (1.3.2)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.4.2)
Expand All @@ -159,8 +146,6 @@ DEPENDENCIES
active_dry_deps!
activesupport
combustion
dry-container
dry-system
rake (~> 13.0)
rspec (~> 3.0)
rubocop-gp!
Expand Down
8 changes: 5 additions & 3 deletions active_dry_deps.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ Gem::Specification.new do |spec|
spec.email = ['[email protected]']

spec.summary = 'Dependency injection and resolution support for classes and modules.'
spec.description = 'ActiveDryDeps not modify constructor and support Dependency Injection for modules.
Also you can import method from any object in your container.
Adding extra dependencies is easy and improve readability your code.'
spec.description = <<~DESCRIPTION
ActiveDryDeps does not modify constructor and supports Dependency Injection for modules.
Also you can import method from any object in your container.
Adding extra dependencies is easy and improve readability your code.
DESCRIPTION
spec.homepage = 'https://github.com/corp-gp/active_dry_deps'
spec.license = 'MIT'
spec.required_ruby_version = '>= 3.0.0'
Expand Down
4 changes: 3 additions & 1 deletion lib/active_dry_deps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

module ActiveDryDeps

autoload :Deps, 'active_dry_deps/deps'
autoload :Deps, 'active_dry_deps/deps'
autoload :Dependency, 'active_dry_deps/dependency'
autoload :Container, 'active_dry_deps/container'

class Error < StandardError; end
class DependencyNameInvalid < Error; end
Expand Down
1 change: 0 additions & 1 deletion lib/active_dry_deps/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ module ActiveDryDeps

extend Dry::Configurable

setting :container
setting :inflector, default: ActiveSupport::Inflector
setting :inject_global_constant, default: 'Deps'

Expand Down
15 changes: 15 additions & 0 deletions lib/active_dry_deps/container.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module ActiveDryDeps
class Container < Hash

def resolve(const_name)
unless key?(const_name)
self[const_name] = Object.const_get(const_name)
end

self[const_name]
end

end
end
90 changes: 90 additions & 0 deletions lib/active_dry_deps/dependency.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# frozen_string_literal: true

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

attr_reader :receiver_method_name, :receiver_method, :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)
else
receiver_method_by_const_name(resolver, receiver_method_alias: 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
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

@receiver_method_name = receiver_method_alias

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
end

private def receiver_method_by_const_name(resolver, receiver_method_alias: nil)
@const_name, @method_name = resolver.to_s.split('.', 2)

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"
end

@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
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"
end
end

end
end
55 changes: 16 additions & 39 deletions lib/active_dry_deps/deps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,39 @@
module ActiveDryDeps
module Deps

VALID_NAME = /([a-zA-Z_0-9]*)$/
METHODS_AS_KLASS = %w[perform_later call].freeze
CONTAINER = Container.new

module_function

# include Deps[routes_admin: 'Lib::Routes.admin'] use as `routes_admin`
# 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)
str_methods = +''

keys.each { |resolver| str_methods << str_method(resolver, nil) }
aliases.each { |alias_method, resolver| str_methods << str_method(resolver, alias_method) }

m = Module.new
m.module_eval(str_methods)
m
end

private def str_method(resolve, alias_method)
resolve_klass, extract_method = resolve.split('.')

alias_method ||=
if extract_method && METHODS_AS_KLASS.exclude?(extract_method)
extract_method
else
resolve_klass.split('::').last
end
dependencies = []
dependencies += keys.map { |resolver| Dependency.new(resolver) }
dependencies += aliases.map { |alias_method, resolver| Dependency.new(resolver, receiver_method_alias: alias_method) }

if alias_method && !VALID_NAME.match?(alias_method.to_s)
raise DependencyNameInvalid, "name +#{alias_method}+ is not a valid Ruby identifier"
end
call_dependencies, constant_dependencies = dependencies.partition(&:callable?)

key = resolve_key(resolve_klass)
m.module_eval(constant_dependencies.map(&:receiver_method_string).join("\n"))

if extract_method
%(def #{alias_method}(...); ::#{ActiveDryDeps.config.container}['#{key}'].#{extract_method}(...) end\n)
else
%(def #{alias_method}; ::#{ActiveDryDeps.config.container}['#{key}'] end\n)
call_dependencies.each do |dependency|
m.define_method(dependency.receiver_method_name, &dependency.receiver_method)
end
end

def resolve_key(key)
ActiveDryDeps.config.inflector.underscore(key).tr('/', '.')
m
end

instance_eval <<~RUBY, __FILE__, __LINE__ + 1
# def resolve(key)
# ::MyApp::Container[resolve_key(key)]
# end
def resolve(key)
::#{ActiveDryDeps.config.container}[resolve_key(key)]
end
RUBY
# 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)
end

end
end
3 changes: 0 additions & 3 deletions lib/active_dry_deps/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ module ActiveDryDeps
class Railtie < ::Rails::Railtie

config.to_prepare do
app_namespace = ::Rails.application.class.to_s.split('::').first
ActiveDryDeps.config.container ||= "#{app_namespace}::Container"

Object.const_set(ActiveDryDeps.config.inject_global_constant, ::ActiveDryDeps::Deps)
ActiveDryDeps.config.finalize!(freeze_values: true)
end
Expand Down
33 changes: 26 additions & 7 deletions lib/active_dry_deps/stub.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,43 @@

module ActiveDryDeps

module Stub
module StubDeps

CONTAINER_CONST = Object.const_get(ActiveDryDeps.config.container)

def stub(path, ...)
CONTAINER_CONST.stub(Deps.resolve_key(path), ...)
def stub(const_name, proxy_object, &block)
self::CONTAINER.stub(const_name, proxy_object, &block)
end

def unstub(*keys)
CONTAINER_CONST.unstub(*keys.map { resolve_key(_1) })
self::CONTAINER.unstub(*keys)
end

end

module StubContainer

def stub(const_name, proxy_object)
if block_given?
begin
self[const_name] = proxy_object
ensure
delete(const_name)
end
else
self[const_name] = proxy_object
end
end

def unstub(*unstub_keys)
(unstub_keys.empty? ? keys : unstub_keys).each { |const_name| delete(const_name) }
end

end

module Deps

def self.enable_stubs!
extend Stub
Deps::CONTAINER.extend(StubContainer)
Deps.extend StubDeps
end

end
Expand Down
Loading

0 comments on commit d5f99e7

Please sign in to comment.