as_method 0.1.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|