as_method 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +48 -9
- data/lib/as_method/allow.rb +20 -0
- data/lib/as_method/find_or_define_module.rb +36 -0
- data/lib/as_method/get_service_object.rb +60 -0
- data/lib/as_method/helpers.rb +47 -0
- data/lib/as_method/method_name/{generate_from_module_name.rb → generate_from_class_name.rb} +4 -4
- data/lib/as_method/method_name/{validate_method_name.rb → validate.rb} +7 -7
- data/lib/as_method/module_name/{generate_from_underscored.rb → generate_from_snake_case.rb} +7 -7
- data/lib/as_method/module_name/strip_namespace.rb +4 -2
- data/lib/as_method/setup.rb +5 -4
- data/lib/as_method/validate_service_object.rb +17 -0
- data/lib/as_method/version.rb +1 -1
- data/lib/as_method.rb +8 -16
- metadata +14 -41
- data/lib/as_method/define_includable_module.rb +0 -45
- data/lib/as_method/find_or_define_includable_module.rb +0 -62
- data/lib/as_method/module_name/generate_for_includable_module.rb +0 -15
- data/lib/as_method/registration_methods.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d9af230121d5102e4fe40bad36aa60286115dd5ffc13fb69c2f7228d3d27d7a
|
4
|
+
data.tar.gz: 53d7e4db5c3ea9c77d767794fbee5466f205aa4038e2e25753a605dc40ae6813
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b76457eed2af6cc5ef73eaa254d3beb927bfe06bd3a1fed6b4eb9ddc6e01e3767ec3585b13e14885594bd11b61f2ec0ab3aec8cf1b562f278a0c2092b877b88
|
7
|
+
data.tar.gz: e8dd515b04ee6afd28b7b18ff8e2f0a32f0f0ef7f64636517c9b99f40ae66b25780a81118eab4c0dbd8c54a4ba1fad950a7421503799d58f9b494d254509a33c
|
data/README.md
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
#
|
1
|
+
# Service Object Injection
|
2
|
+
|
3
|
+
Static dependency injection at its best.
|
2
4
|
|
3
5
|
Usage example:
|
4
6
|
|
5
7
|
```ruby
|
6
8
|
class CreateUser
|
7
|
-
include as_method
|
8
|
-
include as_method Generators::GeneratePassword
|
9
|
-
include as_method
|
9
|
+
include as_method ValidateUserEntity, name: :validate!
|
10
|
+
include as_method Generators::GeneratePassword # -> generate_password()
|
11
|
+
include as_method SaveEntity, name: :save
|
10
12
|
|
11
13
|
def call(name, email)
|
12
14
|
@name = name
|
@@ -16,6 +18,8 @@ class CreateUser
|
|
16
18
|
save(user)
|
17
19
|
end
|
18
20
|
|
21
|
+
def self.call(...) = new.call(...)
|
22
|
+
|
19
23
|
private
|
20
24
|
|
21
25
|
def attributes
|
@@ -31,24 +35,59 @@ end
|
|
31
35
|
## Why?
|
32
36
|
|
33
37
|
This approach allows you to:
|
34
|
-
- keep bringing reusable methods in the old-fashioned way via `include`, just like our ancestors did;
|
35
|
-
- make
|
36
|
-
- have all used
|
38
|
+
- keep bringing reusable methods in the old-fashioned way via `include` or `extend`, just like our ancestors did;
|
39
|
+
- make Service Objects (SOs) look and feel just like regular methods;
|
40
|
+
- have all used SOs listed as explicit dependencies in-place;
|
41
|
+
|
42
|
+
## YouTube video about the Gem
|
43
|
+
|
44
|
+
Click on the image below:
|
45
|
+
|
46
|
+
<div align="left">
|
47
|
+
<a href="https://www.youtube.com/watch?v=eX7DLJJUEI8">
|
48
|
+
<img src="https://img.youtube.com/vi/eX7DLJJUEI8/0.jpg" style="width:100%;">
|
49
|
+
</a>
|
50
|
+
</div>
|
37
51
|
|
38
52
|
## Compatibility
|
39
53
|
|
40
|
-
Tested on Ruby v2.4 .. v3.
|
54
|
+
Tested on Ruby v2.4 .. v3.x, but it is expected to work on all 2.x versions.
|
41
55
|
|
42
56
|
## Installation
|
43
57
|
|
58
|
+
> [!IMPORTANT]
|
59
|
+
> Due to a temporary issue the RubyGems currently installs an outdated version of the gem.
|
60
|
+
>
|
61
|
+
> **For the Latest Version:** To access the most recent version of the gem, please clone it directly from this GitHub repository.
|
62
|
+
|
63
|
+
|
44
64
|
Add to your `Gemfile`
|
45
65
|
|
46
66
|
```ruby
|
47
67
|
gem "as_method"
|
48
68
|
```
|
49
69
|
|
50
|
-
To make `as_method` class method available in
|
70
|
+
To make `as_method` class method available in _all classes and modules_, add this line to your application loader:
|
51
71
|
|
52
72
|
```ruby
|
53
73
|
require "as_method/setup"
|
54
74
|
```
|
75
|
+
|
76
|
+
or enable it in place:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
class MyClass
|
80
|
+
extend AsMethod::Allow
|
81
|
+
...
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
## Alternatives
|
86
|
+
|
87
|
+
If you require dependency injection _during object construction_, you might consider using [dry-auto_inject](https://dry-rb.org/gems/dry-auto_inject).
|
88
|
+
|
89
|
+
However, be aware of the following points:
|
90
|
+
|
91
|
+
- Dependencies are somewhat implicit as they are defined in a separate file.
|
92
|
+
- Object constructors get modified, as explained in detail [here](https://dry-rb.org/gems/dry-auto_inject/0.6/how-does-it-work/)."
|
93
|
+
- Resulting methods don't invoke 'call' on objects, they return callables instead.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AsMethod
|
4
|
+
module Allow
|
5
|
+
|
6
|
+
def self.included(_)
|
7
|
+
raise "Don't include, extend instead"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.extended(base)
|
11
|
+
if base.is_a?(Class)
|
12
|
+
base.extend(Helpers::ClassMethods)
|
13
|
+
base.include(Helpers::InstanceMethods)
|
14
|
+
else
|
15
|
+
base.singleton_class.include(Helpers::ClassMethods)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end # ... Allow
|
20
|
+
end # ... AsMethod
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "erb"
|
4
|
+
|
5
|
+
module AsMethod
|
6
|
+
class FindOrDefineModule
|
7
|
+
def self.call(**args)
|
8
|
+
new.call(**args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(module_template:, module_name:, vars:)
|
12
|
+
@vars = vars
|
13
|
+
@module_name = module_name
|
14
|
+
@module_template = module_template
|
15
|
+
|
16
|
+
instance_eval(generate_module_source_code) unless module_exists?
|
17
|
+
|
18
|
+
includable_module
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def generate_module_source_code
|
24
|
+
payload = @vars.merge(module_name: @module_name)
|
25
|
+
::ERB.new(@module_template).result_with_hash(payload)
|
26
|
+
end
|
27
|
+
|
28
|
+
def includable_module
|
29
|
+
Object.const_get(@module_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def module_exists?
|
33
|
+
Object.const_defined?(@module_name)
|
34
|
+
end
|
35
|
+
end # ... FindOrDefineModule
|
36
|
+
end # ... AsMethod
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AsMethod
|
4
|
+
class GetServiceObject
|
5
|
+
AMBIGUOUS_NAME_ERROR_MSG = <<-MSG
|
6
|
+
Ambiguous name. %s has both class and instance methods named %s.
|
7
|
+
Please use '::method_name' or '#method_name' to specify which one you want.
|
8
|
+
MSG
|
9
|
+
|
10
|
+
def self.call(obj, method_name)
|
11
|
+
new.call(obj, method_name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(obj, method_name)
|
15
|
+
raise ArgumentError, "expected #{obj} to be a Module or Class" unless obj.is_a?(Module)
|
16
|
+
|
17
|
+
@obj = obj
|
18
|
+
@method_name = method_name
|
19
|
+
|
20
|
+
if method_name[0, 2] == "::"
|
21
|
+
get_method(nil, method_name[2..-1])
|
22
|
+
elsif method_name[0] == "#"
|
23
|
+
get_method(method_name[1..-1], nil)
|
24
|
+
else
|
25
|
+
get_method(method_name, method_name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def get_instance_method(name)
|
32
|
+
return unless name
|
33
|
+
return unless @obj.method_defined?(name) || @obj.private_method_defined?(name)
|
34
|
+
|
35
|
+
@obj.instance_method(name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_class_method(name)
|
39
|
+
return unless name
|
40
|
+
return unless @obj.singleton_class.method_defined?(name) || @obj.singleton_class.private_method_defined?(name)
|
41
|
+
|
42
|
+
@obj.method(name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_method(instance_method_name, class_method_name)
|
46
|
+
imethod = get_instance_method(instance_method_name)
|
47
|
+
cmethod = get_class_method(class_method_name)
|
48
|
+
|
49
|
+
if [imethod, cmethod].none?
|
50
|
+
raise NameError, "undefined method #{@method_name.inspect} for #{@obj.inspect}"
|
51
|
+
end
|
52
|
+
|
53
|
+
if [imethod, cmethod].all?
|
54
|
+
raise NameError, format(AMBIGUOUS_NAME_ERROR_MSG, @obj.inspect, @method_name.inspect)
|
55
|
+
end
|
56
|
+
|
57
|
+
(cmethod || imethod).owner.instance_variable_get(:@_injectable_object)
|
58
|
+
end
|
59
|
+
end # ... GetServiceObject
|
60
|
+
end # ... AsMethod
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AsMethod
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
module InstanceMethods
|
7
|
+
def service_object_for(method_name)
|
8
|
+
self.class.service_object_for(method_name)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def service_object_for(method_name)
|
14
|
+
GetServiceObject.call(self, method_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
# rubocop:disable Metrics/AbcSize
|
18
|
+
def as_method(object, name: nil, access: :private)
|
19
|
+
raise ArgumentError unless %w[private public protected].include?(access.to_s)
|
20
|
+
|
21
|
+
ValidateServiceObject.call(object)
|
22
|
+
|
23
|
+
# generate method name from object, if none given
|
24
|
+
method_name = name&.to_s || MethodName::GenerateFromClassName.call(object.name)
|
25
|
+
|
26
|
+
MethodName::Validate.call(method_name)
|
27
|
+
|
28
|
+
module_name = "::#{object.name}::As#{access.capitalize}Method__#{ModuleName::GenerateFromSnakeCase.call(method_name)}" # rubocop:disable Layout/LineLength
|
29
|
+
|
30
|
+
# define module, or return existing module:
|
31
|
+
includable_module = FindOrDefineModule.call \
|
32
|
+
module_template: File.read(File.join(__dir__, "templates", "includable_module.rb.erb")),
|
33
|
+
module_name: module_name,
|
34
|
+
vars: {
|
35
|
+
object: object,
|
36
|
+
method_name: method_name,
|
37
|
+
method_access: access,
|
38
|
+
}
|
39
|
+
|
40
|
+
# return generated module ready for inclusion:
|
41
|
+
included_modules.include?(includable_module) ? Kernel : includable_module
|
42
|
+
end
|
43
|
+
# rubocop:enable Metrics/AbcSize
|
44
|
+
|
45
|
+
end # ... ClassMethods
|
46
|
+
end # ... Helpers
|
47
|
+
end # ... AsMethod
|
@@ -2,15 +2,15 @@
|
|
2
2
|
|
3
3
|
module AsMethod
|
4
4
|
module MethodName
|
5
|
-
module
|
5
|
+
module GenerateFromClassName
|
6
6
|
|
7
7
|
def self.call(module_name)
|
8
8
|
ModuleName::StripNamespace.call(module_name)
|
9
|
-
.gsub(/([A-Z]+)([A-Z][a-z])/,
|
10
|
-
.gsub(/([a-z\d])([A-Z])/,
|
9
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, %q(\1_\2))
|
10
|
+
.gsub(/([a-z\d])([A-Z])/, %q(\1_\2))
|
11
11
|
.downcase
|
12
12
|
end
|
13
13
|
|
14
|
-
end # ...
|
14
|
+
end # ... GenerateFromClassName
|
15
15
|
end # ... MethodName
|
16
16
|
end # ... AsMethod
|
@@ -2,19 +2,19 @@
|
|
2
2
|
|
3
3
|
module AsMethod
|
4
4
|
module MethodName
|
5
|
-
module
|
5
|
+
module Validate
|
6
6
|
|
7
|
-
REGULAR_NAME_REGEX = /\A[a-z_]+[a-z0-9_]*[?!=]{0,1}\z
|
7
|
+
REGULAR_NAME_REGEX = /\A[a-z_]+[a-z0-9_]*[?!=]{0,1}\z/.freeze
|
8
8
|
|
9
|
-
SPECIAL_NAMES = %w{[] ! ~ + ** - * / % << >> & | ^ < <= >= > == === != =~ !~ <=>}
|
9
|
+
SPECIAL_NAMES = %w{[] ! ~ + ** - * / % << >> & | ^ < <= >= > == === != =~ !~ <=>}.freeze
|
10
10
|
|
11
11
|
def self.call(name)
|
12
12
|
return true if name.to_s =~ REGULAR_NAME_REGEX
|
13
13
|
return true if SPECIAL_NAMES.include?(name.to_s)
|
14
|
-
|
15
|
-
|
14
|
+
|
15
|
+
fail ArgumentError, "invalid method name #{name.inspect}"
|
16
16
|
end
|
17
17
|
|
18
|
-
end # ...
|
18
|
+
end # ... Validate
|
19
19
|
end # ... MethodName
|
20
|
-
end # ... AsMethod
|
20
|
+
end # ... AsMethod
|
@@ -2,8 +2,7 @@
|
|
2
2
|
|
3
3
|
module AsMethod
|
4
4
|
module ModuleName
|
5
|
-
module
|
6
|
-
|
5
|
+
module GenerateFromSnakeCase
|
7
6
|
REPLACEMENTS = {
|
8
7
|
"-" => "_Min",
|
9
8
|
"!" => "_Exc",
|
@@ -25,12 +24,13 @@ module AsMethod
|
|
25
24
|
|
26
25
|
def self.call(string)
|
27
26
|
return unless string
|
28
|
-
|
27
|
+
|
28
|
+
escaped_replacements = REPLACEMENTS.keys.map { Regexp.escape(_1) }
|
29
29
|
string.to_s
|
30
|
-
.
|
30
|
+
.sub(/^(_|#{escaped_replacements.join('|')})/, "SPECIAL\\1")
|
31
|
+
.gsub(/(^|_)([a-z])/) { ::Regexp.last_match(2).capitalize.to_s }
|
31
32
|
.gsub(/[#{Regexp.escape(REPLACEMENTS.keys.join)}]/, REPLACEMENTS)
|
32
33
|
end
|
33
|
-
|
34
|
-
end # ... GenerateFromUnderscored
|
34
|
+
end # ... GenerateFromSnakeCase
|
35
35
|
end # ... ModuleName
|
36
|
-
end
|
36
|
+
end
|
@@ -4,8 +4,10 @@ module AsMethod
|
|
4
4
|
module ModuleName
|
5
5
|
module StripNamespace
|
6
6
|
|
7
|
-
def self.call(
|
8
|
-
|
7
|
+
def self.call(module_or_class_name)
|
8
|
+
name = String(module_or_class_name)
|
9
|
+
index = name.rindex("::")
|
10
|
+
index ? name[(index + 2)..-1] : name
|
9
11
|
end
|
10
12
|
|
11
13
|
end # ... StripNamespace
|
data/lib/as_method/setup.rb
CHANGED
@@ -3,9 +3,10 @@
|
|
3
3
|
# Usage:
|
4
4
|
#
|
5
5
|
# Require this file to add #as_method class method to all classes:
|
6
|
-
# require
|
6
|
+
# require "as_method/setup"
|
7
7
|
#
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
require "as_method"
|
10
|
+
|
11
|
+
Module.extend(AsMethod::Allow)
|
12
|
+
Object.extend(AsMethod::Allow)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AsMethod
|
4
|
+
module ValidateServiceObject
|
5
|
+
|
6
|
+
def self.call(module_or_class)
|
7
|
+
unless module_or_class.is_a?(Module)
|
8
|
+
fail TypeError, "#{module_or_class} must be a Class or a Module"
|
9
|
+
end
|
10
|
+
|
11
|
+
return if module_or_class.respond_to?(:call)
|
12
|
+
|
13
|
+
fail NoMethodError, "Expected #{module_or_class} to respond to #call method"
|
14
|
+
end
|
15
|
+
|
16
|
+
end # ... ValidateServiceObject
|
17
|
+
end # ... AsMethod
|
data/lib/as_method/version.rb
CHANGED
data/lib/as_method.rb
CHANGED
@@ -1,29 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AsMethod
|
4
|
-
autoload :
|
5
|
-
autoload :
|
6
|
-
autoload :
|
4
|
+
autoload :Allow, "as_method/allow"
|
5
|
+
autoload :Helpers, "as_method/helpers"
|
6
|
+
autoload :FindOrDefineModule, "as_method/find_or_define_module"
|
7
|
+
autoload :ValidateServiceObject, "as_method/validate_service_object"
|
8
|
+
autoload :GetServiceObject, "as_method/get_service_object"
|
7
9
|
autoload :VERSION, "as_method/version"
|
8
10
|
|
9
11
|
module ModuleName
|
10
|
-
autoload :
|
11
|
-
autoload :GenerateFromUnderscored, "as_method/module_name/generate_from_underscored"
|
12
|
+
autoload :GenerateFromSnakeCase, "as_method/module_name/generate_from_snake_case"
|
12
13
|
autoload :StripNamespace, "as_method/module_name/strip_namespace"
|
13
14
|
end
|
14
15
|
|
15
16
|
module MethodName
|
16
|
-
autoload :
|
17
|
-
autoload :
|
17
|
+
autoload :Validate, "as_method/method_name/validate"
|
18
|
+
autoload :GenerateFromClassName, "as_method/method_name/generate_from_class_name"
|
18
19
|
end
|
19
20
|
|
20
|
-
def as_method(service_object, name: nil)
|
21
|
-
includable_module = FindOrDefineIncludableModule.call(service_object, name)
|
22
|
-
unless self.included_modules.include?(includable_module)
|
23
|
-
includable_module
|
24
|
-
else
|
25
|
-
Kernel # a bit faster
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
29
21
|
end # ... AsMethod
|
metadata
CHANGED
@@ -1,44 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: as_method
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vladimir Gorodulin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
13
|
-
|
14
|
-
name: rspec
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '3.2'
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '3.2'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: pry
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0.14'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0.14'
|
41
|
-
description: Make callable Service Objects includable as methods
|
11
|
+
date: 2024-01-09 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Bring Service Objects to your classes as methods.
|
42
14
|
email:
|
43
15
|
- ru.hostmaster@gmail.com
|
44
16
|
executables: []
|
@@ -49,15 +21,16 @@ files:
|
|
49
21
|
- LICENSE
|
50
22
|
- README.md
|
51
23
|
- lib/as_method.rb
|
52
|
-
- lib/as_method/
|
53
|
-
- lib/as_method/
|
54
|
-
- lib/as_method/
|
55
|
-
- lib/as_method/
|
56
|
-
- lib/as_method/
|
57
|
-
- lib/as_method/
|
24
|
+
- lib/as_method/allow.rb
|
25
|
+
- lib/as_method/find_or_define_module.rb
|
26
|
+
- lib/as_method/get_service_object.rb
|
27
|
+
- lib/as_method/helpers.rb
|
28
|
+
- lib/as_method/method_name/generate_from_class_name.rb
|
29
|
+
- lib/as_method/method_name/validate.rb
|
30
|
+
- lib/as_method/module_name/generate_from_snake_case.rb
|
58
31
|
- lib/as_method/module_name/strip_namespace.rb
|
59
|
-
- lib/as_method/registration_methods.rb
|
60
32
|
- lib/as_method/setup.rb
|
33
|
+
- lib/as_method/validate_service_object.rb
|
61
34
|
- lib/as_method/version.rb
|
62
35
|
homepage: https://github.com/gorodulin/as_method
|
63
36
|
licenses:
|
@@ -74,14 +47,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
47
|
requirements:
|
75
48
|
- - ">="
|
76
49
|
- !ruby/object:Gem::Version
|
77
|
-
version: 2.
|
50
|
+
version: '2.3'
|
78
51
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
52
|
requirements:
|
80
53
|
- - ">="
|
81
54
|
- !ruby/object:Gem::Version
|
82
55
|
version: '0'
|
83
56
|
requirements: []
|
84
|
-
rubygems_version: 3.
|
57
|
+
rubygems_version: 3.4.22
|
85
58
|
signing_key:
|
86
59
|
specification_version: 4
|
87
60
|
summary: Call Service Objects with ease. Include them to your classes as methods!
|
@@ -1,45 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AsMethod
|
4
|
-
module DefineIncludableModule
|
5
|
-
|
6
|
-
PASS = if RUBY_VERSION < "2.7"
|
7
|
-
"*args, &block"
|
8
|
-
else
|
9
|
-
"..."
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.call(includable_module_name, service_object_class, method_name)
|
13
|
-
puts "-- DefineIncludableModule #{includable_module_name}"
|
14
|
-
code = <<~RUBY
|
15
|
-
module #{includable_module_name}
|
16
|
-
|
17
|
-
def self.ruby2_keywords(*)
|
18
|
-
puts "-- ruby2_keywords"
|
19
|
-
end if RUBY_VERSION < "2.7"
|
20
|
-
|
21
|
-
ruby2_keywords def #{method_name}(*args, &block)
|
22
|
-
self.class.registered_service_objects[:"#{method_name}"].call(*args, &block)
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.included(base)
|
26
|
-
if base.instance_of?(Module)
|
27
|
-
fail TypeError, "Can't be included into Module \#{base.inspect}"
|
28
|
-
end
|
29
|
-
unless base.included_modules.include?(RegistrationMethods)
|
30
|
-
#puts "-- included #{includable_module_name} into \#{base}"
|
31
|
-
base.include RegistrationMethods
|
32
|
-
end
|
33
|
-
base.register_service_object(:"#{method_name}", ::#{service_object_class})
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.extended(_base)
|
37
|
-
raise "Do not extend, include!"
|
38
|
-
end
|
39
|
-
end
|
40
|
-
RUBY
|
41
|
-
instance_eval code
|
42
|
-
end
|
43
|
-
|
44
|
-
end # ... DefineIncludableModule
|
45
|
-
end # ... AsMethod
|
@@ -1,62 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AsMethod
|
4
|
-
class FindOrDefineIncludableModule
|
5
|
-
|
6
|
-
def self.call(service_object, method_name)
|
7
|
-
new.call(service_object, method_name)
|
8
|
-
end
|
9
|
-
|
10
|
-
def call(service_object, method_name)
|
11
|
-
# Note: order matters:
|
12
|
-
self.service_object = service_object
|
13
|
-
self.method_name = method_name
|
14
|
-
self.includable_module_name = generate_includable_module_name(service_object, method_name)
|
15
|
-
|
16
|
-
define_includable_module unless Object.const_defined?(includable_module_name)
|
17
|
-
|
18
|
-
Object.const_get(includable_module_name)
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
attr_accessor :includable_module_name
|
24
|
-
attr_reader :method_name
|
25
|
-
attr_reader :service_object
|
26
|
-
|
27
|
-
def define_includable_module
|
28
|
-
DefineIncludableModule.call(includable_module_name, service_object, method_name)
|
29
|
-
end
|
30
|
-
|
31
|
-
def generate_method_name_from_so_name
|
32
|
-
MethodName::GenerateFromModuleName.call(service_object.name)
|
33
|
-
end
|
34
|
-
|
35
|
-
def generate_includable_module_name(service_object, method_name)
|
36
|
-
ModuleName::GenerateForIncludableModule.call(service_object, method_name)
|
37
|
-
end
|
38
|
-
|
39
|
-
def method_name=(name)
|
40
|
-
validate_method_name!(name.to_s) if name
|
41
|
-
|
42
|
-
@method_name = name&.to_s || generate_method_name_from_so_name
|
43
|
-
end
|
44
|
-
|
45
|
-
def service_object=(module_or_class)
|
46
|
-
unless module_or_class.respond_to?(:call)
|
47
|
-
fail NoMethodError, "Expected #{module_or_class} to respond to #call method"
|
48
|
-
end
|
49
|
-
unless module_or_class.is_a?(Module)
|
50
|
-
fail TypeError, "#{module_or_class} must be a Class or a Module"
|
51
|
-
end
|
52
|
-
@service_object = module_or_class
|
53
|
-
end
|
54
|
-
|
55
|
-
def validate_method_name!(name)
|
56
|
-
return if MethodName::ValidateMethodName.call(name)
|
57
|
-
|
58
|
-
fail NameError, "wrong method name #{name.inspect}"
|
59
|
-
end
|
60
|
-
|
61
|
-
end # ... FindOrDefineIncludableModule
|
62
|
-
end # ... AsMethod
|
@@ -1,15 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AsMethod
|
4
|
-
module ModuleName
|
5
|
-
module GenerateForIncludableModule
|
6
|
-
|
7
|
-
def self.call(module_or_class, method_name = nil)
|
8
|
-
part = GenerateFromUnderscored.call(method_name) || StripNamespace.call(module_or_class)
|
9
|
-
|
10
|
-
"::#{module_or_class}::As#{part}Method"
|
11
|
-
end
|
12
|
-
|
13
|
-
end # ... GenerateForIncludableModule
|
14
|
-
end # ... ModuleName
|
15
|
-
end # ... AsMethod
|
@@ -1,34 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AsMethod
|
4
|
-
module RegistrationMethods
|
5
|
-
|
6
|
-
module ClassMethods
|
7
|
-
def registered_service_objects
|
8
|
-
@registered_service_objects ||= {}
|
9
|
-
end
|
10
|
-
|
11
|
-
def register_service_objects(hash)
|
12
|
-
hash.each { |name, so| register_service_object(name, so) }
|
13
|
-
end
|
14
|
-
|
15
|
-
def register_service_object(name, so)
|
16
|
-
registered_service_objects[name.to_sym]&.tap do |registered_so|
|
17
|
-
return if so == registered_so
|
18
|
-
raise "#{so} clashes with #{registered_so} in #{self}" if so != registered_so
|
19
|
-
end
|
20
|
-
|
21
|
-
# puts "--- register #{self.name}##{name} => #{so}#call"
|
22
|
-
registered_service_objects[name.to_sym] = so
|
23
|
-
end
|
24
|
-
end # ... ClassMethods
|
25
|
-
|
26
|
-
def self.included(base)
|
27
|
-
return if base.singleton_class.included_modules.include?(ClassMethods)
|
28
|
-
|
29
|
-
puts "-- #{base.name} extend with RegistrationMethods::ClassMethods"
|
30
|
-
base.extend ClassMethods
|
31
|
-
end
|
32
|
-
|
33
|
-
end # ... RegistrationMethods
|
34
|
-
end # ... AsMethod
|