as_method 0.1.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE +21 -0
- data/README.md +54 -0
- data/lib/as_method/define_includable_module.rb +45 -0
- data/lib/as_method/find_or_define_includable_module.rb +62 -0
- data/lib/as_method/method_name/generate_from_module_name.rb +16 -0
- data/lib/as_method/method_name/validate_method_name.rb +20 -0
- data/lib/as_method/module_name/generate_for_includable_module.rb +15 -0
- data/lib/as_method/module_name/generate_from_underscored.rb +36 -0
- data/lib/as_method/module_name/strip_namespace.rb +13 -0
- data/lib/as_method/registration_methods.rb +34 -0
- data/lib/as_method/setup.rb +11 -0
- data/lib/as_method/version.rb +7 -0
- data/lib/as_method.rb +29 -0
- metadata +88 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e0e9e993c21de8b7a1d579d9228ee8901f7b18cc892f37a0e0b1f9a6ef6a50a7
|
4
|
+
data.tar.gz: 2f7506bb9f6ed58793c2b4f606035baffc3fb8f608d88d778047ccd9e769c1fe
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b94dd8cc2f9049a71fcbea9bf789ce2794df3d5593b390aa558eeaf7837a12c015c9e8e265898ad1751ccd7effb397e2f43c78eb92d503c628f123747d58f09e
|
7
|
+
data.tar.gz: 98c396c3768c0ca040f25d5d103d18fe71b05baec6571ff3da5396c608a63c3fedf60f226d77325dd1c83ef2c3bc1bc55ec1111abd836ddf9aa62583983f6da5
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2022 V.G.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# Includable Service Objects
|
2
|
+
|
3
|
+
Usage example:
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
class CreateUser
|
7
|
+
include as_method ValidateUserAttributes, name: :validate!
|
8
|
+
include as_method Generators::GeneratePassword
|
9
|
+
include as_method SaveModel, name: :save
|
10
|
+
|
11
|
+
def call(name, email)
|
12
|
+
@name = name
|
13
|
+
@email = email
|
14
|
+
user = User.new(attributes)
|
15
|
+
validate!(user)
|
16
|
+
save(user)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def attributes
|
22
|
+
{
|
23
|
+
name: @name,
|
24
|
+
email: @email,
|
25
|
+
password: generate_password(length: 30),
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
## Why?
|
32
|
+
|
33
|
+
This approach allows you to:
|
34
|
+
- keep bringing reusable methods in the old-fashioned way via `include`, just like our ancestors did;
|
35
|
+
- make methods extracted to SOs look and feel just like regular methods;
|
36
|
+
- have all used Service Objects (SOs) listed as explicit dependencies;
|
37
|
+
|
38
|
+
## Compatibility
|
39
|
+
|
40
|
+
Tested on Ruby v2.4 .. v3.1, but it is expected to work on all 2.x versions.
|
41
|
+
|
42
|
+
## Installation
|
43
|
+
|
44
|
+
Add to your `Gemfile`
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
gem "as_method"
|
48
|
+
```
|
49
|
+
|
50
|
+
To make `as_method` class method available in all classes, add this line to your application loader:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
require "as_method/setup"
|
54
|
+
```
|
@@ -0,0 +1,45 @@
|
|
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
|
@@ -0,0 +1,62 @@
|
|
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
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AsMethod
|
4
|
+
module MethodName
|
5
|
+
module GenerateFromModuleName
|
6
|
+
|
7
|
+
def self.call(module_name)
|
8
|
+
ModuleName::StripNamespace.call(module_name)
|
9
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
10
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
11
|
+
.downcase
|
12
|
+
end
|
13
|
+
|
14
|
+
end # ... GenerateFromModuleName
|
15
|
+
end # ... MethodName
|
16
|
+
end # ... AsMethod
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AsMethod
|
4
|
+
module MethodName
|
5
|
+
module ValidateMethodName
|
6
|
+
|
7
|
+
REGULAR_NAME_REGEX = /\A[a-z_]+[a-z0-9_]*[?!=]{0,1}\z/
|
8
|
+
|
9
|
+
SPECIAL_NAMES = %w{[] ! ~ + ** - * / % << >> & | ^ < <= >= > == === != =~ !~ <=>}
|
10
|
+
|
11
|
+
def self.call(name)
|
12
|
+
return true if name.to_s =~ REGULAR_NAME_REGEX
|
13
|
+
return true if SPECIAL_NAMES.include?(name.to_s)
|
14
|
+
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
end # ... ValidateMethodName
|
19
|
+
end # ... MethodName
|
20
|
+
end # ... AsMethod
|
@@ -0,0 +1,15 @@
|
|
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
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AsMethod
|
4
|
+
module ModuleName
|
5
|
+
module GenerateFromUnderscored
|
6
|
+
|
7
|
+
REPLACEMENTS = {
|
8
|
+
"-" => "_Min",
|
9
|
+
"!" => "_Exc",
|
10
|
+
"?" => "_Que",
|
11
|
+
"[" => "_Sbl",
|
12
|
+
"]" => "_Sbr",
|
13
|
+
"*" => "_Ast",
|
14
|
+
"/" => "_Sla",
|
15
|
+
"&" => "_Amp",
|
16
|
+
"%" => "_Pct",
|
17
|
+
"^" => "_Pwr",
|
18
|
+
"+" => "_Plu",
|
19
|
+
"<" => "_Lst",
|
20
|
+
"=" => "_Eql",
|
21
|
+
">" => "_Gtt",
|
22
|
+
"|" => "_Vbr",
|
23
|
+
"~" => "_Til",
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
def self.call(string)
|
27
|
+
return unless string
|
28
|
+
|
29
|
+
string.to_s
|
30
|
+
.gsub(/(?:_|(^))([a-z\d]{1})/) { "#{$1}#{$2.capitalize}" }
|
31
|
+
.gsub(/[#{Regexp.escape(REPLACEMENTS.keys.join)}]/, REPLACEMENTS)
|
32
|
+
end
|
33
|
+
|
34
|
+
end # ... GenerateFromUnderscored
|
35
|
+
end # ... ModuleName
|
36
|
+
end # ... AsMethod
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AsMethod
|
4
|
+
module ModuleName
|
5
|
+
module StripNamespace
|
6
|
+
|
7
|
+
def self.call(module_or_class)
|
8
|
+
module_or_class.to_s.match(/[^:]+\z/)&.to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
end # ... StripNamespace
|
12
|
+
end # ... ModuleName
|
13
|
+
end # ... AsMethod
|
@@ -0,0 +1,34 @@
|
|
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
|
data/lib/as_method.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AsMethod
|
4
|
+
autoload :DefineIncludableModule, "as_method/define_includable_module"
|
5
|
+
autoload :FindOrDefineIncludableModule, "as_method/find_or_define_includable_module"
|
6
|
+
autoload :RegistrationMethods, "as_method/registration_methods"
|
7
|
+
autoload :VERSION, "as_method/version"
|
8
|
+
|
9
|
+
module ModuleName
|
10
|
+
autoload :GenerateForIncludableModule, "as_method/module_name/generate_for_includable_module"
|
11
|
+
autoload :GenerateFromUnderscored, "as_method/module_name/generate_from_underscored"
|
12
|
+
autoload :StripNamespace, "as_method/module_name/strip_namespace"
|
13
|
+
end
|
14
|
+
|
15
|
+
module MethodName
|
16
|
+
autoload :ValidateMethodName, "as_method/method_name/validate_method_name"
|
17
|
+
autoload :GenerateFromModuleName, "as_method/method_name/generate_from_module_name"
|
18
|
+
end
|
19
|
+
|
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
|
+
end # ... AsMethod
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: as_method
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Vladimir Gorodulin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-10-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
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
|
42
|
+
email:
|
43
|
+
- ru.hostmaster@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- CHANGELOG.md
|
49
|
+
- LICENSE
|
50
|
+
- README.md
|
51
|
+
- lib/as_method.rb
|
52
|
+
- lib/as_method/define_includable_module.rb
|
53
|
+
- lib/as_method/find_or_define_includable_module.rb
|
54
|
+
- lib/as_method/method_name/generate_from_module_name.rb
|
55
|
+
- lib/as_method/method_name/validate_method_name.rb
|
56
|
+
- lib/as_method/module_name/generate_for_includable_module.rb
|
57
|
+
- lib/as_method/module_name/generate_from_underscored.rb
|
58
|
+
- lib/as_method/module_name/strip_namespace.rb
|
59
|
+
- lib/as_method/registration_methods.rb
|
60
|
+
- lib/as_method/setup.rb
|
61
|
+
- lib/as_method/version.rb
|
62
|
+
homepage: https://github.com/gorodulin/as_method
|
63
|
+
licenses:
|
64
|
+
- MIT
|
65
|
+
metadata:
|
66
|
+
changelog_uri: https://github.com/gorodulin/as_method/CHANGELOG.md
|
67
|
+
homepage_uri: https://github.com/gorodulin/as_method
|
68
|
+
source_code_uri: https://github.com/gorodulin/as_method
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 2.7.0
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubygems_version: 3.1.6
|
85
|
+
signing_key:
|
86
|
+
specification_version: 4
|
87
|
+
summary: Call Service Objects with ease. Include them to your classes as methods!
|
88
|
+
test_files: []
|