as_method 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b333db53f2557a64198bbd51b766bd2aa2335703bef4f294e3afc92b8dbeb51c
4
- data.tar.gz: a81108e81da82e920f4b1c5818fdced96800e44bc1e2ecb33110e00b78c51eb3
3
+ metadata.gz: c681302ea5c6e934c05ec30e0b38cdbfd47d18edd7018eccf845f3f806dc0f10
4
+ data.tar.gz: 1884622331ca3549ec834b64d314b6b322707e0264357e931eaff341466a55ef
5
5
  SHA512:
6
- metadata.gz: bfeb95266b9c1a2895d4a6daee298f0538c552035fabadb47b7997aaacd33306be68e92f96794ed9295915583203787693ab4721ebba5e265a597ab2bfe64b75
7
- data.tar.gz: fcdff0761a4717339688d8787d8baaa8fbdb1038d5fbefd5f8ac9b21d0621e4cf3ed9f2a80469b6b182e468aa902c04535f44b7ccdce1f01a200fd2a8f96b6b0
6
+ metadata.gz: 597472c6887db1c5a53d95f09d152b9cdef12734ba04f390707fef324b4e45d373caab26bd7099b89ac50d0328ed6c6455b9e91316055f8c2ec7900f4f6df965
7
+ data.tar.gz: abee934d4c0da5f4e3fa1f85ed9a74107c76fc1a61e0c597801944e725bde6e5a81fd1405f642fd6ae65db87abe431a41afb8354edb6a4f76d730f07ccd2bd23
data/README.md CHANGED
@@ -85,3 +85,22 @@ However, be aware of the following points:
85
85
  - Dependencies are somewhat implicit as they are defined in a separate file.
86
86
  - Object constructors get modified, as explained in detail [here](https://dry-rb.org/gems/dry-auto_inject/0.6/how-does-it-work/)."
87
87
  - Resulting methods don't invoke 'call' on objects, they return callables instead.
88
+
89
+ ## Benchmark results:
90
+
91
+ 1000 iterations of a Test Service Object call. It does nothing but calls two other service objects, they call two other service objects each, and so on. (Depth 11 levels, 2049 unique classes, 4095 calls).
92
+
93
+ ```
94
+ user system total real memory
95
+ pure Ruby 3.689327 0.047354 3.736681 ( 3.744128) 27440
96
+ as_method 6.219795 0.038531 6.258326 ( 6.298066) 37652
97
+ dry-auto_inject 22.121456 0.119032 22.240488 (22.467116) 55408
98
+ ```
99
+
100
+ Ruby version: 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-darwin22]
101
+
102
+ ## TODO:
103
+
104
+ - extra spec for circular dependencies (SO1 includes SO2, SO2 includes SO1, directly and via modules)
105
+ - extra spec for Base Service Object class that includes another SO
106
+ - spec for AsMethod::Allow (extending a base class and a module)
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable
4
+ module AsMethod
5
+
6
+ Configuration = Struct.new(
7
+ :object_selector,
8
+ :object_validator,
9
+ :method_name_generator,
10
+ :method_name_validator,
11
+ :module_template
12
+ )
13
+
14
+ DEFAULT_CONFIGURATION = Configuration.new.tap do |config|
15
+ config.object_selector = ->(object) { object }
16
+ config.object_validator = ValidateServiceObject
17
+ config.method_name_generator = MethodName::GenerateFromClassName
18
+ config.method_name_validator = MethodName::Validate
19
+ config.module_template = <<~RUBY
20
+ module <%= module_name %>
21
+
22
+ def self.object
23
+ ::<%= object %>
24
+ end
25
+
26
+ def self.ruby2_keywords(*); end if RUBY_VERSION < "2.7"
27
+
28
+ ruby2_keywords def <%= method_name %>(*args, &block)
29
+ ::<%= object %>.call(*args, &block)
30
+ end
31
+
32
+ <%= method_access %> :<%= method_name %>
33
+
34
+ end
35
+ RUBY
36
+ end.freeze
37
+
38
+ end
39
+ # rubocop:enable
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module AsMethod
6
+ class GenerateModule
7
+ def self.call(**args)
8
+ new(**args).call
9
+ end
10
+
11
+ def initialize(object:, method_name: nil, method_access: :private)
12
+ @_object = object
13
+ @_method_name = method_name
14
+ @_method_access = method_access
15
+ end
16
+
17
+ def call
18
+ instance_eval(generate_module_source_code) unless module_exists?
19
+
20
+ includable_module
21
+ end
22
+
23
+ private
24
+
25
+ def includable_module
26
+ Object.const_get(module_name)
27
+ end
28
+
29
+ def generate_module_source_code
30
+ payload = {
31
+ object: object,
32
+ module_name: module_name,
33
+ method_name: method_name,
34
+ method_access: method_access,
35
+ }
36
+ ::ERB.new(config.module_template).result_with_hash(payload)
37
+ end
38
+
39
+ def object
40
+ @object ||= validate_object!(select_object(@_object))
41
+ end
42
+
43
+ def select_object(object)
44
+ config.object_selector.call(object)
45
+ end
46
+
47
+ def validate_object!(object)
48
+ config.object_validator.call(object)
49
+ end
50
+
51
+ def generate_method_name(object_name)
52
+ config.method_name_generator.call(object_name)
53
+ end
54
+
55
+ def validate_method_name!(method_name)
56
+ config.method_name_validator.call(method_name)
57
+ end
58
+
59
+ def method_access
60
+ @method_access ||= @_method_access.to_s.tap do |access|
61
+ raise ArgumentError unless %w[private public protected].include?(access)
62
+ end
63
+ end
64
+
65
+ def method_name
66
+ @method_name ||= validate_method_name!(@_method_name&.to_s || generate_method_name(object.name))
67
+ end
68
+
69
+ def module_name
70
+ @module_name ||= "::#{object.name}::As#{method_access.capitalize}Method__#{ModuleName::GenerateFromSnakeCase.call(method_name)}" # rubocop:disable Layout/LineLength
71
+ end
72
+
73
+ def module_exists?
74
+ Object.const_defined?(module_name)
75
+ end
76
+
77
+ def config
78
+ AsMethod.config
79
+ end
80
+ end # ... GenerateModule
81
+ end # ... AsMethod
@@ -54,7 +54,7 @@ module AsMethod
54
54
  raise NameError, format(AMBIGUOUS_NAME_ERROR_MSG, @obj.inspect, @method_name.inspect)
55
55
  end
56
56
 
57
- (cmethod || imethod).owner.instance_variable_get(:@_injectable_object)
57
+ (cmethod || imethod).owner.object
58
58
  end
59
59
  end # ... GetServiceObject
60
60
  end # ... AsMethod
@@ -14,34 +14,10 @@ module AsMethod
14
14
  GetServiceObject.call(self, method_name)
15
15
  end
16
16
 
17
- # rubocop:disable Metrics/AbcSize
18
17
  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:
18
+ includable_module = GenerateModule.call(object: object, method_name: name, method_access: access)
41
19
  included_modules.include?(includable_module) ? Kernel : includable_module
42
20
  end
43
- # rubocop:enable Metrics/AbcSize
44
-
45
21
  end # ... ClassMethods
46
22
  end # ... Helpers
47
23
  end # ... AsMethod
@@ -9,8 +9,10 @@ module AsMethod
9
9
  SPECIAL_NAMES = %w{[] ! ~ + ** - * / % << >> & | ^ < <= >= > == === != =~ !~ <=>}.freeze
10
10
 
11
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)
12
+ fail ArgumentError unless name.is_a?(String)
13
+
14
+ return name if name =~ REGULAR_NAME_REGEX
15
+ return name if SPECIAL_NAMES.include?(name)
14
16
 
15
17
  fail ArgumentError, "invalid method name #{name.inspect}"
16
18
  end
@@ -8,7 +8,7 @@ module AsMethod
8
8
  fail TypeError, "#{module_or_class} must be a Class or a Module"
9
9
  end
10
10
 
11
- return if module_or_class.respond_to?(:call)
11
+ return module_or_class if module_or_class.respond_to?(:call)
12
12
 
13
13
  fail NoMethodError, "Expected #{module_or_class} to respond to #call method"
14
14
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module AsMethod
4
4
 
5
- VERSION = "0.3.1"
5
+ VERSION = "0.3.2"
6
6
 
7
7
  end
data/lib/as_method.rb CHANGED
@@ -2,9 +2,12 @@
2
2
 
3
3
  module AsMethod
4
4
  autoload :Allow, "as_method/allow"
5
+ autoload :Configuration, "as_method/configuration"
6
+ autoload :DEFAULT_CONFIGURATION, "as_method/configuration"
5
7
  autoload :Helpers, "as_method/helpers"
6
8
  autoload :FindOrDefineModule, "as_method/find_or_define_module"
7
9
  autoload :ValidateServiceObject, "as_method/validate_service_object"
10
+ autoload :GenerateModule, "as_method/generate_module"
8
11
  autoload :GetServiceObject, "as_method/get_service_object"
9
12
  autoload :VERSION, "as_method/version"
10
13
 
@@ -18,4 +21,12 @@ module AsMethod
18
21
  autoload :GenerateFromClassName, "as_method/method_name/generate_from_class_name"
19
22
  end
20
23
 
24
+ def self.configure
25
+ yield(config)
26
+ end
27
+
28
+ def self.config
29
+ @config ||= DEFAULT_CONFIGURATION.dup
30
+ end
31
+
21
32
  end # ... AsMethod
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: as_method
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Gorodulin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-19 00:00:00.000000000 Z
11
+ date: 2024-05-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Bring Service Objects to your classes as methods.
14
14
  email:
@@ -22,7 +22,8 @@ files:
22
22
  - README.md
23
23
  - lib/as_method.rb
24
24
  - lib/as_method/allow.rb
25
- - lib/as_method/find_or_define_module.rb
25
+ - lib/as_method/configuration.rb
26
+ - lib/as_method/generate_module.rb
26
27
  - lib/as_method/get_service_object.rb
27
28
  - lib/as_method/helpers.rb
28
29
  - lib/as_method/method_name/generate_from_class_name.rb
@@ -30,7 +31,6 @@ files:
30
31
  - lib/as_method/module_name/generate_from_snake_case.rb
31
32
  - lib/as_method/module_name/strip_namespace.rb
32
33
  - lib/as_method/setup.rb
33
- - lib/as_method/templates/includable_module.rb.erb
34
34
  - lib/as_method/validate_service_object.rb
35
35
  - lib/as_method/version.rb
36
36
  homepage: https://github.com/gorodulin/as_method
@@ -1,36 +0,0 @@
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
@@ -1,15 +0,0 @@
1
-
2
- module <%= module_name %>
3
-
4
- @_injectable_method = "<%= method_name %>"
5
- @_injectable_object = <%= object %>
6
-
7
- def self.ruby2_keywords(*); end if RUBY_VERSION < "2.7"
8
-
9
- ruby2_keywords def <%= method_name %>(*args, &block)
10
- ::<%= object %>.call(*args, &block)
11
- end
12
-
13
- <%= method_access %> :<%= method_name %>
14
-
15
- end