Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal implementation #5

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2e9c219
Proposal of Reflect improvements in markdown doc
arepnikov Jun 17, 2024
486b1a9
Proposal of Reflect more examples and improvements
arepnikov Jul 4, 2024
eb47f0b
RecordInvocation development dependency added
arepnikov Aug 1, 2024
a2fabb1
Improvements for Reflect library - tests are added
arepnikov Aug 1, 2024
22b9c62
Actuate stale tests removed
arepnikov Aug 1, 2024
e42029d
Proposal implementation
arepnikov Aug 6, 2024
abb9f22
Missing tests added to arity and parameters methods
arepnikov Aug 6, 2024
af33058
Actuator and variant actuator tests clarified
arepnikov Aug 6, 2024
e1b9e46
target_method? rather than target_accessor?
arepnikov Sep 12, 2024
342c071
get_target with `public_send` rather than `send`
arepnikov Sep 12, 2024
751c1d2
assure_target_method added
arepnikov Sep 12, 2024
1f87e75
proposal clarified
arepnikov Sep 12, 2024
03bd708
Target controls added and used in Subject example
arepnikov Sep 14, 2024
fd71552
MixedParameters test clarified
arepnikov Sep 14, 2024
f228c29
Actuator and Variant Actuator test clarified
arepnikov Sep 14, 2024
b8d4e59
Parameters test clarified
arepnikov Sep 14, 2024
2a7959c
target_method introduced
arepnikov Sep 14, 2024
1254461
redundant method_is_not_implemented tests removed
arepnikov Sep 14, 2024
aa334dd
arity and parameters tests moved
arepnikov Sep 14, 2024
a02b0b3
redundant strict argument removed
arepnikov Sep 14, 2024
3c383f1
arity and target_method? tests clarified
arepnikov Sep 14, 2024
4d32664
Target control clarified
arepnikov Sep 16, 2024
4ccc6c4
target_accessor? method reverted to master state and comment added
arepnikov Sep 16, 2024
eec9eed
target_method? clarified by removing redundant optional argument
arepnikov Sep 16, 2024
2db9302
porposam removed and contnet used as pull request desciption
arepnikov Sep 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Gemfile.lock
gems
.DS_store
*.log
*scratch.*
*sketch*
materials
*.gem
*notes.*
1 change: 1 addition & 0 deletions lib/reflect/controls.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'securerandom'

require 'reflect/controls/namespace'
require 'reflect/controls/target'
require 'reflect/controls/subject'
require 'reflect/controls/ancestor'
18 changes: 1 addition & 17 deletions lib/reflect/controls/subject.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,7 @@ def self.example

class Example
module SomeConstant
def self.some_accessor
SomeInnerConstant
end

def self.some_object_accessor
SomeInnerClass.new
end

def self.some_method(arg)
arg
end

module SomeInnerConstant
end

class SomeInnerClass
end
extend Target::Methods
end

module ConstantWithoutAccessor
Expand Down
95 changes: 95 additions & 0 deletions lib/reflect/controls/target.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
module Reflect
module Controls
module Target
def self.example(constant_name: nil, subject: nil)
constant_name ||= Namespace::Random.get
subject ||= Subject.example

target =
Module.new do
extend Methods
end

subject_constant = Reflect.constant(subject)
subject_constant.const_set(constant_name, target)

target
end

module Methods
def self.extended(cls)
cls.extend(SomeMethod)
cls.extend(SomeAccessor)
cls.extend(SomeObjectAccessor)
end

module SomeMethod
def some_method(some_parameter, some_other_parameter)
end
end

module SomeAccessor
def self.extended(cls)
some_inner_module = Module.new
cls.const_set(:SomeInnerConstant, some_inner_module)
end

def some_accessor
some_inner_module = const_get(:SomeInnerConstant)
some_inner_module
end
end

module SomeObjectAccessor
def self.extended(cls)
some_inner_class = Class.new
cls.const_set(:SomeInnerClass, some_inner_class)
end

def some_object_accessor
some_inner_class = const_get(:SomeInnerClass)
some_inner_class.new
end
end
end

module MixedParameters
def self.example(constant_name: nil, subject: nil)
constant_name ||= Namespace::Random.get
subject ||= Subject.example

target =
Module.new do
extend Methods
end

subject_constant = Reflect.constant(subject)
subject_constant.const_set(constant_name, target)

target
end

module Methods
def self.extended(cls)
cls.extend(SomeMethod)
cls.extend(Target::Methods::SomeAccessor)
cls.extend(Target::Methods::SomeObjectAccessor)
end

module SomeMethod
def some_method(
some_parameter,
some_optional_parameter=nil,
*some_multiple_assignment_parameter,
some_keyword_parameter:,
some_optional_keyword_parameter: nil,
**some_multiple_assignment_keyword_parameter,
&some_block
)
end
end
end
end
end
end
end
37 changes: 29 additions & 8 deletions lib/reflect/reflection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,41 @@ def self.build(subject, constant_name, strict: nil, ancestors: nil)
new(subject, target, strict)
end

def call(method_name, arg=nil)
unless target.respond_to?(method_name)
def call(method_name, ...)
target_method = target_method(method_name)
target_method.(...)
end

def !(method_name, ...)
call(method_name, subject, ...)
end

def target_method?(method_name, subject=nil)
subject ||= target
subject.respond_to?(method_name)
end

def target_accessor?(method_name, subject=nil)
target_method?(method_name, subject)
end

def target_method(method_name)
if !target_method?(method_name)
target_name = Reflect.constant(target).name
raise Reflect::Error, "#{target_name} does not define method #{method_name}"
end

arg ||= subject
target.public_method(method_name)
end

target.send(method_name, arg)
def arity(method_name)
target_method = target_method(method_name)
target_method.arity
end

def target_accessor?(name, subject=nil)
subject ||= constant
subject.respond_to?(name)
def parameters(method_name)
target_method = target_method(method_name)
target_method.parameters
end

def get(accessor_name, strict: nil, coerce_constant: nil)
Expand Down Expand Up @@ -69,7 +90,7 @@ def get_target(accessor_name, strict: nil)
end
end

target.send(accessor_name)
target.public_send(accessor_name)
end

module Default
Expand Down
189 changes: 189 additions & 0 deletions proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# Reflect Library Improvements Proposal

## Background

In Eventide's libraries, Reflect has 6 use cases, but 4 of them do not utilize it in the designed way. Instead of making a use of the reflection's actuator, the constant is pulled from the reflection.

The actuator is designed to pass the subject into the method being invoked. The subject can also be overridden, but either way, exactly one argument is always supplied to the protocol method:

```ruby
class SomeThing
module Validate
def self.call(some_thing)
# validate some_thing
end
end
end

some_thing = SomeThing.new

validator_reflection = Reflect.(some_thing, :Validate)

validator_reflection.(:call) # validates some_thing

some_other_thing = SomeThing.new
validator_reflection.(:call, some_other_thing) # validates some_other_thing
```

However, the actuator doesn't support methods with zero arity or methods with arity greater than one. Due to this limitation, the constant is often pulled from the reflection, bypassing the actuator.

If the reflection's actuator were to support invoking protocol methods with any arity, then most use cases of the reflect library would be able to use the actuator and avoid disencapsulating the reflection's target constant.

## Reflection's Actuator

### Example: Method Without Arguments

#### Current Way Around

```ruby
class SomeClass
module Substitute
def self.build
# build the substitute
end
end
end

some_instance = SomeClass.new
some_reflection = Reflect.(some_instance, :Substitute)

constant = some_reflection.constant
constant.build
# => some substitute
```

#### Proposed Change

```ruby
class SomeClass
module Substitute
def self.build
# build the substitute
end
end
end

some_instance = SomeClass.new
some_reflection = Reflect.(some_instance, :Substitute)

some_reflection.(:build)
# => some substitute
```

### Example: Method With More Than One Argument

#### Current Way Around

```ruby
class SomeThing
module Validate
def self.call(some_thing, state)
# validate some_thing
end
end
end

some_thing = SomeThing.new
validator_reflection = Reflect.(some_thing, :Validate)

validator = validator_reflection.constant
validator.call(some_thing, :some_state)
# => validate some_thing
```

#### Proposed Change

```ruby
class SomeThing
module Validate
def self.call(some_thing, state)
# validate some_thing
end
end
end

some_thing = SomeThing.new
validator_reflection = Reflect.(some_thing, :Validate)

validator_reflection.(:call, some_thing, :some_state)
# => validate some_thing
```

## Reflection's Variant Actuator

Support for methods with any parameter signature is proposed. This necessitates a way to invoke the reflection's actuator so that it supplies the reflection's subject into the protocol method.

To address this, the introduction of a variant actuator method `.!()` is proposed. The variant actuator will pass the subject into the invoked method as the first positional argument, but will otherwise behave the same as the primary actuator:

```ruby
class SomeThing
module Validate
def self.call(some_thing, state)
# validate some_thing
end
end
end

some_thing = SomeThing.new
validator_reflection = Reflect.(some_thing, :Validate)

# validator_reflection.(:call, some_thing, :some_state)
validator_reflection.!(:call, :some_state)
# => validate some_thing
```

# Additional Changes

## Target Method Predicate

``` ruby
class SomeThing
module Validate
def self.call(some_thing, state=nil)
# validate some_thing, taking an account optional state
end
end
end

some_thing = SomeThing.new
validate_reflection = Reflect.(some_thing, :Validate)

validate_reflection.target_method?(:call)
# => true
```

## Arity Method

``` ruby
class SomeThing
module Validate
def self.call(some_thing, state=nil)
# validate some_thing, taking an account optional state
end
end
end

some_thing = SomeThing.new
validate_reflection = Reflect.(some_thing, :Validate)

validate_reflection.arity(:call)
# => -2
```

## Parameters Method

``` ruby
class SomeThing
module Validate
def self.call(some_thing, state=nil)
# validate some_thing, taking an account optional state
end
end
end

some_thing = SomeThing.new
validate_reflection = Reflect.(some_thing, :Validate)

validate_reflection.parameters(:call)
# => [[:some_thing, :req], [:state, :opt]]
```
1 change: 1 addition & 0 deletions reflect.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.required_ruby_version = '>= 2.3.3'

s.add_development_dependency 'evt-record_invocation'
s.add_development_dependency 'test_bench'
end
Loading