├── sig
└── .keep
├── .yardopts
├── .rspec
├── logo
├── render_sample_1.JPG
├── render_sample_2.JPG
├── symbiont_logo_circle.ai
├── symbiont_logo_circle.eps
├── symbiont_logo_circle.jpg
├── symbiont_logo_circle.pdf
├── symbiont_logo_circle.png
├── symbiont_logo_circle.psd
├── symbiont_logo_circle.tif
├── symbiont_logo_hexagon.ai
├── symbiont_logo_hexagon.eps
├── symbiont_logo_hexagon.jpg
├── symbiont_logo_hexagon.pdf
├── symbiont_logo_hexagon.png
├── symbiont_logo_hexagon.psd
├── symbiont_logo_hexagon.tif
├── symbiont_logo_circle_black.ai
├── symbiont_logo_circle_black.eps
├── symbiont_logo_circle_black.jpg
├── symbiont_logo_circle_black.pdf
├── symbiont_logo_circle_black.png
├── symbiont_logo_circle_black.psd
├── symbiont_logo_circle_black.tif
├── symbiont_logo_hexagon_black.ai
├── symbiont_logo_hexagon_black.eps
├── symbiont_logo_hexagon_black.jpg
├── symbiont_logo_hexagon_black.pdf
├── symbiont_logo_hexagon_black.png
├── symbiont_logo_hexagon_black.psd
├── symbiont_logo_hexagon_black.tif
├── symbiont_logo_hexagon.svg
├── symbiont_logo_circle.svg
├── symbiont_logo_circle_black.svg
└── symbiont_logo_hexagon_black.svg
├── Steepfile
├── .gitignore
├── bin
├── console
└── setup
├── spec
├── units
│ └── symbiont
│ │ └── version_spec.rb
├── support
│ ├── shared_contexts.rb
│ ├── spec_support.rb
│ ├── spec_support
│ │ ├── fake_data_generator.rb
│ │ └── symbiont_helpers.rb
│ └── shared_contexts
│ │ ├── public_similar_contexts_context.rb
│ │ └── private_similar_contexts_context.rb
├── spec_helper.rb
└── features
│ ├── public_inner_kernel_outer_spec.rb
│ ├── public_inner_outer_kernel_spec.rb
│ ├── public_kernel_inner_outer_spec.rb
│ ├── public_kernel_outer_inner_spec.rb
│ ├── public_outer_inner_kernel_spec.rb
│ ├── public_outer_kernel_inner_spec.rb
│ ├── private_inner_kernel_outer_spec.rb
│ ├── private_inner_outer_kernel_spec.rb
│ ├── private_kernel_inner_outer_spec.rb
│ ├── private_kernel_outer_inner_spec.rb
│ ├── private_outer_inner_kernel_spec.rb
│ ├── private_outer_kernel_inner_spec.rb
│ ├── public_multiple_contexts_spec.rb
│ ├── private_multiple_contexts_spec.rb
│ └── symbiont_like_behaviour_spec.rb
├── lib
├── symbiont
│ ├── version.rb
│ ├── teleport.rb
│ ├── context.rb
│ ├── private_trigger.rb
│ ├── public_trigger.rb
│ ├── executor.rb
│ ├── isolator.rb
│ └── trigger.rb
└── symbiont.rb
├── Gemfile
├── .rubocop.yml
├── LICENSE.txt
├── Rakefile
├── CHANGELOG.md
├── symbiont-ruby.gemspec
├── Gemfile.lock
└── README.md
/sig/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.yardopts:
--------------------------------------------------------------------------------
1 | --protected --private --markup markdown
2 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --format progress
3 | --require spec_helper
4 |
--------------------------------------------------------------------------------
/logo/render_sample_1.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/render_sample_1.JPG
--------------------------------------------------------------------------------
/logo/render_sample_2.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/render_sample_2.JPG
--------------------------------------------------------------------------------
/logo/symbiont_logo_circle.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_circle.ai
--------------------------------------------------------------------------------
/logo/symbiont_logo_circle.eps:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_circle.eps
--------------------------------------------------------------------------------
/logo/symbiont_logo_circle.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_circle.jpg
--------------------------------------------------------------------------------
/logo/symbiont_logo_circle.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_circle.pdf
--------------------------------------------------------------------------------
/logo/symbiont_logo_circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_circle.png
--------------------------------------------------------------------------------
/logo/symbiont_logo_circle.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_circle.psd
--------------------------------------------------------------------------------
/logo/symbiont_logo_circle.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_circle.tif
--------------------------------------------------------------------------------
/logo/symbiont_logo_hexagon.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_hexagon.ai
--------------------------------------------------------------------------------
/Steepfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | target :lib do
4 | signature 'sig'
5 | check 'lib'
6 | end
7 |
--------------------------------------------------------------------------------
/logo/symbiont_logo_hexagon.eps:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_hexagon.eps
--------------------------------------------------------------------------------
/logo/symbiont_logo_hexagon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_hexagon.jpg
--------------------------------------------------------------------------------
/logo/symbiont_logo_hexagon.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_hexagon.pdf
--------------------------------------------------------------------------------
/logo/symbiont_logo_hexagon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_hexagon.png
--------------------------------------------------------------------------------
/logo/symbiont_logo_hexagon.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_hexagon.psd
--------------------------------------------------------------------------------
/logo/symbiont_logo_hexagon.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_hexagon.tif
--------------------------------------------------------------------------------
/logo/symbiont_logo_circle_black.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_circle_black.ai
--------------------------------------------------------------------------------
/logo/symbiont_logo_circle_black.eps:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_circle_black.eps
--------------------------------------------------------------------------------
/logo/symbiont_logo_circle_black.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_circle_black.jpg
--------------------------------------------------------------------------------
/logo/symbiont_logo_circle_black.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_circle_black.pdf
--------------------------------------------------------------------------------
/logo/symbiont_logo_circle_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_circle_black.png
--------------------------------------------------------------------------------
/logo/symbiont_logo_circle_black.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_circle_black.psd
--------------------------------------------------------------------------------
/logo/symbiont_logo_circle_black.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_circle_black.tif
--------------------------------------------------------------------------------
/logo/symbiont_logo_hexagon_black.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_hexagon_black.ai
--------------------------------------------------------------------------------
/logo/symbiont_logo_hexagon_black.eps:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_hexagon_black.eps
--------------------------------------------------------------------------------
/logo/symbiont_logo_hexagon_black.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_hexagon_black.jpg
--------------------------------------------------------------------------------
/logo/symbiont_logo_hexagon_black.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_hexagon_black.pdf
--------------------------------------------------------------------------------
/logo/symbiont_logo_hexagon_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_hexagon_black.png
--------------------------------------------------------------------------------
/logo/symbiont_logo_hexagon_black.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_hexagon_black.psd
--------------------------------------------------------------------------------
/logo/symbiont_logo_hexagon_black.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0exp/symbiont-ruby/HEAD/logo/symbiont_logo_hexagon_black.tif
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /_yardoc/
4 | /coverage/
5 | /doc/
6 | /pkg/
7 | /spec/reports/
8 | /tmp/
9 | .rspec_status
10 | .ruby-version
11 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require 'bundler/setup'
5 | require 'symbiont'
6 |
7 | require 'pry'
8 | Pry.start
9 |
--------------------------------------------------------------------------------
/spec/units/symbiont/version_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe Symbiont::VERSION do
4 | it { is_expected.to eq('0.7.0') } # ¯\_(ツ)_/¯
5 | end
6 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | bundle install
7 |
8 | # Do any other automated setup that you need to do here
9 |
--------------------------------------------------------------------------------
/lib/symbiont/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Symbiont
4 | # Symbiont Version :)
5 | #
6 | # @api public
7 | # @since 0.1.0
8 | VERSION = '0.7.0'
9 | end
10 |
--------------------------------------------------------------------------------
/lib/symbiont/teleport.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Effect API research
4 | #
5 | # @api private
6 | # @since 0.7.0
7 | class Symbiont::Effect
8 | # NOTE: fiber magic :)
9 | end
10 |
--------------------------------------------------------------------------------
/spec/support/shared_contexts.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'shared_contexts/private_similar_contexts_context'
4 | require_relative 'shared_contexts/public_similar_contexts_context'
5 |
--------------------------------------------------------------------------------
/spec/support/spec_support.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SpecSupport
4 | require_relative 'spec_support/fake_data_generator'
5 | require_relative 'spec_support/symbiont_helpers'
6 | end
7 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 |
5 | git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6 |
7 | # Specify your gem's dependencies in symbiont-ruby.gemspec
8 | gemspec
9 |
--------------------------------------------------------------------------------
/spec/support/spec_support/fake_data_generator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SpecSupport::FakeDataGenerator
4 | STR_LETTERS = (('a'..'z').to_a | ('A'..'Z').to_a).freeze
5 | STR_LENGTH = 20
6 |
7 | def gen_str(str_len = STR_LENGTH)
8 | Array.new(str_len) { STR_LETTERS.sample }.join
9 | end
10 |
11 | def gen_symb(str_len = STR_LENGTH)
12 | gen_str(str_len).to_sym
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | inherit_gem:
2 | armitage-rubocop:
3 | - lib/rubocop.general.yml
4 | - lib/rubocop.rspec.yml
5 |
6 | AllCops:
7 | TargetRubyVersion: 3.0.0
8 | Include:
9 | - lib/**/*.rb
10 | - spec/**/*.rb
11 | - bin/console
12 | - Rakefile
13 | - Gemfile
14 | - symbiont-ruby.gemspec
15 | - Steepfile
16 |
17 | Layout/LineLength:
18 | Max: 120
19 |
20 | # NOTE: for code examples in tests
21 | Lint/EmptyBlock:
22 | Exclude:
23 | - spec/**/*.rb
24 |
25 | Style/RedundantBegin:
26 | Enabled: false
27 |
28 | # NOTE: for code clarity in tests
29 | RSpec/LeakyConstantDeclaration:
30 | Enabled: false
31 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'simplecov'
4 |
5 | SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter
6 | SimpleCov.minimum_coverage(100)
7 | SimpleCov.start do
8 | enable_coverage :branch
9 | enable_coverage :line
10 | primary_coverage :line
11 | add_filter 'spec'
12 | end
13 |
14 | require 'bundler/setup'
15 | require 'symbiont'
16 | require 'pry'
17 |
18 | require_relative 'support/spec_support'
19 | require_relative 'support/shared_contexts'
20 |
21 | RSpec.configure do |config|
22 | config.expect_with(:rspec) { |c| c.syntax = :expect }
23 | config.order = :random
24 | Kernel.srand config.seed
25 |
26 | config.include SpecSupport::SymbiontHelpers
27 | config.include SpecSupport::FakeDataGenerator
28 | config.extend SpecSupport::FakeDataGenerator
29 | end
30 |
--------------------------------------------------------------------------------
/spec/support/spec_support/symbiont_helpers.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SpecSupport::SymbiontHelpers
4 | def public_symbiont_eval(*contexts, direction:, &clojure)
5 | Symbiont::Executor.evaluate(
6 | *contexts, context_direction: direction, &clojure
7 | )
8 | end
9 |
10 | def private_symbiont_eval(*contexts, direction:, &clojure)
11 | Symbiont::Executor.evaluate_private(
12 | *contexts, context_direction: direction, &clojure
13 | )
14 | end
15 |
16 | def public_symbiont_method(method_name, *contexts, direction:, &clojure)
17 | Symbiont::Executor.public_method(
18 | method_name, *contexts, context_direction: direction, &clojure
19 | )
20 | end
21 |
22 | def private_symbiont_method(method_name, *contexts, direction:, &clojure)
23 | Symbiont::Executor.private_method(
24 | method_name, *contexts, context_direction: direction, &clojure
25 | )
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017-2024 Rustam Ibragimov
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/spec/support/shared_contexts/public_similar_contexts_context.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | shared_context 'public similar contexts' do
4 | let!(:object_class) do
5 | Class.new do
6 | def object_data
7 | 'inner_data'
8 | end
9 | end
10 | end
11 |
12 | let!(:another_object_class) do
13 | Class.new(BasicObject) do
14 | def object_info
15 | 'inner_info'
16 | end
17 | end
18 | end
19 |
20 | let!(:object) { object_class.new }
21 | let!(:another_object) { another_object_class.new }
22 |
23 | before do
24 | module Kernel
25 | def object_data
26 | 'kernel_data'
27 | end
28 | end
29 |
30 | class << self
31 | def object_data
32 | 'outer_data'
33 | end
34 |
35 | def object_info
36 | 'outer_info'
37 | end
38 | end
39 | end
40 |
41 | after do
42 | module Kernel
43 | begin
44 | undef object_data
45 | rescue NameError # NOTE: it means that object_data is already removed/undefined
46 | end
47 |
48 | begin
49 | undef object_info
50 | rescue NameError # NOTE: it means that object_data is already removed/undefined
51 | end
52 | end
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/spec/support/shared_contexts/private_similar_contexts_context.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | shared_context 'private similar contexts' do
4 | let!(:object_class) do
5 | Class.new do
6 | private
7 |
8 | def object_data(arg, option:)
9 | 'inner_data'
10 | end
11 | end
12 | end
13 |
14 | let!(:another_object_class) do
15 | Class.new(BasicObject) do
16 | private
17 |
18 | def object_info
19 | 'inner_info'
20 | end
21 | end
22 | end
23 |
24 | let!(:object) { object_class.new }
25 | let!(:another_object) { another_object_class.new }
26 |
27 | before do
28 | module Kernel
29 | private
30 |
31 | def object_data(arg, option:)
32 | 'kernel_data'
33 | end
34 | end
35 |
36 | class << self
37 | private
38 |
39 | def object_data(arg, option:)
40 | 'outer_data'
41 | end
42 |
43 | def object_info
44 | 'outer_info'
45 | end
46 | end
47 | end
48 |
49 | after do
50 | module Kernel
51 | begin
52 | undef object_data
53 | rescue NameError # NOTE: it means that object_data is already removed/undefined
54 | end
55 |
56 | begin
57 | undef object_info
58 | rescue NameError # NOTE: it means that object_data is already removed/undefined
59 | end
60 | end
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'bundler/gem_tasks'
4 | require 'rspec/core/rake_task'
5 | require 'rubocop'
6 | require 'rubocop/rake_task'
7 | require 'rubocop-performance'
8 | require 'rubocop-rspec'
9 | require 'rubocop-rake'
10 | require 'yard'
11 |
12 | RuboCop::RakeTask.new(:rubocop) do |t|
13 | config_path = File.expand_path(File.join('.rubocop.yml'), __dir__)
14 | t.options = ['--config', config_path]
15 | t.requires << 'rubocop-rspec'
16 | t.requires << 'rubocop-performance'
17 | t.requires << 'rubocop-rake'
18 | end
19 |
20 | RSpec::Core::RakeTask.new(:rspec)
21 |
22 | YARD::Rake::YardocTask.new(:doc) do |t|
23 | t.files = Dir[Pathname.new(__FILE__).join('../lib/**/*.rb')]
24 | t.options = %w[--protected --private]
25 | end
26 |
27 | task default: :rspec
28 |
29 | desc 'Code documentation coverage check'
30 | task yardoc: :doc do
31 | undocumented_code_objects = YARD::Registry.tap(&:load).select do |code_object|
32 | code_object.docstring.empty?
33 | end
34 |
35 | if undocumented_code_objects.empty?
36 | puts 'YARD COVERAGE [SUCCESS] => 100% documentation coverage!'
37 | else
38 | failing_code_objects = undocumented_code_objects.map do |code_object|
39 | "- #{code_object.class} => #{code_object}"
40 | end.join("\n")
41 |
42 | abort("YARD COVERAGE [FAILURE] => No documentation found for: \n #{failing_code_objects}")
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/spec/features/public_inner_kernel_outer_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe 'Symbiont: inner context (object) => kernel context (kernel) => outer context (proc)' do
4 | include_context 'public similar contexts'
5 |
6 | specify 'public IKO resolution' do
7 | closure = proc { object_data }
8 |
9 | result = public_symbiont_eval(object, direction: Symbiont::IKO, &closure)
10 | method = public_symbiont_method(:object_data, object, direction: Symbiont::IKO, &closure)
11 | expect(result).to eq('inner_data')
12 | expect(method.call).to eq('inner_data')
13 |
14 | object_class.send(:undef_method, :object_data)
15 | result = public_symbiont_eval(object, direction: Symbiont::IKO, &closure)
16 | method = public_symbiont_method(:object_data, object, direction: Symbiont::IKO, &closure)
17 | expect(result).to eq('kernel_data')
18 | expect(method.call).to eq('kernel_data')
19 |
20 | ::Kernel.send(:undef_method, :object_data)
21 | result = public_symbiont_eval(object, direction: Symbiont::IKO, &closure)
22 | method = public_symbiont_method(:object_data, object, direction: Symbiont::IKO, &closure)
23 | expect(result).to eq('outer_data')
24 | expect(method.call).to eq('outer_data')
25 |
26 | undef object_data
27 | expect do
28 | public_symbiont_eval(object, direction: Symbiont::IKO, &closure)
29 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
30 |
31 | expect do
32 | public_symbiont_method(:object_data, object, direction: Symbiont::IKO, &closure)
33 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/features/public_inner_outer_kernel_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe 'Symbiont: inner context (object) => outer context (proc) => kernel context (kernel)' do
4 | include_context 'public similar contexts'
5 |
6 | specify 'public IOK resolution' do
7 | closure = proc { object_data }
8 |
9 | result = public_symbiont_eval(object, direction: Symbiont::IOK, &closure)
10 | method = public_symbiont_method(:object_data, object, direction: Symbiont::IOK, &closure)
11 | expect(result).to eq('inner_data')
12 | expect(method.call).to eq('inner_data')
13 |
14 | object_class.send(:undef_method, :object_data)
15 | result = public_symbiont_eval(object, direction: Symbiont::IOK, &closure)
16 | method = public_symbiont_method(:object_data, object, direction: Symbiont::IOK, &closure)
17 | expect(result).to eq('outer_data')
18 | expect(method.call).to eq('outer_data')
19 |
20 | undef object_data
21 | result = public_symbiont_eval(object, direction: Symbiont::IOK, &closure)
22 | method = public_symbiont_method(:object_data, object, direction: Symbiont::IOK, &closure)
23 | expect(result).to eq('kernel_data')
24 | expect(method.call).to eq('kernel_data')
25 |
26 | ::Kernel.send(:undef_method, :object_data)
27 | expect do
28 | public_symbiont_eval(object, direction: Symbiont::IOK, &closure)
29 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
30 |
31 | expect do
32 | public_symbiont_method(:object_data, object, direction: Symbiont::IOK, &closure)
33 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/features/public_kernel_inner_outer_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe 'Symbiont: kernel context (kernel) => inner context (object) => outer context (proc)' do
4 | include_context 'public similar contexts'
5 |
6 | specify 'public KIO resolution' do
7 | closure = proc { object_data }
8 |
9 | result = public_symbiont_eval(object, direction: Symbiont::KIO, &closure)
10 | method = public_symbiont_method(:object_data, object, direction: Symbiont::KIO, &closure)
11 | expect(result).to eq('kernel_data')
12 | expect(method.call).to eq('kernel_data')
13 |
14 | ::Kernel.send(:undef_method, :object_data)
15 | result = public_symbiont_eval(object, direction: Symbiont::KIO, &closure)
16 | method = public_symbiont_method(:object_data, object, direction: Symbiont::KIO, &closure)
17 | expect(result).to eq('inner_data')
18 | expect(method.call).to eq('inner_data')
19 |
20 | object_class.send(:undef_method, :object_data)
21 | result = public_symbiont_eval(object, direction: Symbiont::KIO, &closure)
22 | method = public_symbiont_method(:object_data, object, direction: Symbiont::KIO, &closure)
23 | expect(result).to eq('outer_data')
24 | expect(method.call).to eq('outer_data')
25 |
26 | undef object_data
27 | expect do
28 | public_symbiont_eval(object, direction: Symbiont::KIO, &closure)
29 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
30 |
31 | expect do
32 | public_symbiont_method(:object_data, object, direction: Symbiont::KIO, &closure)
33 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/features/public_kernel_outer_inner_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe 'Symbiont: kernel context (kernel) => outer context (proc) => inner context (object)' do
4 | include_context 'public similar contexts'
5 |
6 | specify 'public KOI resolution' do
7 | closure = proc { object_data }
8 |
9 | result = public_symbiont_eval(object, direction: Symbiont::KOI, &closure)
10 | method = public_symbiont_method(:object_data, object, direction: Symbiont::KOI, &closure)
11 | expect(result).to eq('kernel_data')
12 | expect(method.call).to eq('kernel_data')
13 |
14 | ::Kernel.send(:undef_method, :object_data)
15 | result = public_symbiont_eval(object, direction: Symbiont::KOI, &closure)
16 | method = public_symbiont_method(:object_data, object, direction: Symbiont::KOI, &closure)
17 | expect(result).to eq('outer_data')
18 | expect(method.call).to eq('outer_data')
19 |
20 | undef object_data
21 | result = public_symbiont_eval(object, direction: Symbiont::KOI, &closure)
22 | method = public_symbiont_method(:object_data, object, direction: Symbiont::KOI, &closure)
23 | expect(result).to eq('inner_data')
24 | expect(method.call).to eq('inner_data')
25 |
26 | object_class.send(:undef_method, :object_data)
27 | expect do
28 | public_symbiont_eval(object, direction: Symbiont::KOI, &closure)
29 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
30 |
31 | expect do
32 | public_symbiont_method(:object_data, object, direction: Symbiont::KOI, &closure)
33 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/features/public_outer_inner_kernel_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe 'Symbiont: outer context (proc) => inner context (object) => kernel context (kernel)' do
4 | include_context 'public similar contexts'
5 |
6 | specify 'public OIK resolution' do
7 | closure = proc { object_data }
8 |
9 | result = public_symbiont_eval(object, direction: Symbiont::OIK, &closure)
10 | method = public_symbiont_method(:object_data, object, direction: Symbiont::OIK, &closure)
11 | expect(result).to eq('outer_data')
12 | expect(method.call).to eq('outer_data')
13 |
14 | undef object_data
15 | result = public_symbiont_eval(object, direction: Symbiont::OIK, &closure)
16 | method = public_symbiont_method(:object_data, object, direction: Symbiont::OIK, &closure)
17 | expect(result).to eq('inner_data')
18 | expect(method.call).to eq('inner_data')
19 |
20 | object_class.send(:undef_method, :object_data)
21 | result = public_symbiont_eval(object, direction: Symbiont::OIK, &closure)
22 | method = public_symbiont_method(:object_data, object, direction: Symbiont::OIK, &closure)
23 | expect(result).to eq('kernel_data')
24 | expect(method.call).to eq('kernel_data')
25 |
26 | ::Kernel.send(:undef_method, :object_data)
27 | expect do
28 | public_symbiont_eval(object, direction: Symbiont::OIK, &closure)
29 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
30 |
31 | expect do
32 | public_symbiont_method(:object_data, object, direction: Symbiont::OIK, &closure)
33 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/features/public_outer_kernel_inner_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe 'Symbiont: outer context (proc) => kernel context (kernel) => inner context (object)' do
4 | include_context 'public similar contexts'
5 |
6 | specify 'public OKI resolution' do
7 | closure = proc { object_data }
8 |
9 | result = public_symbiont_eval(object, direction: Symbiont::OKI, &closure)
10 | method = public_symbiont_method(:object_data, object, direction: Symbiont::OKI, &closure)
11 | expect(result).to eq('outer_data')
12 | expect(method.call).to eq('outer_data')
13 |
14 | undef object_data
15 | result = public_symbiont_eval(object, direction: Symbiont::OKI, &closure)
16 | method = public_symbiont_method(:object_data, object, direction: Symbiont::OKI, &closure)
17 | expect(result).to eq('kernel_data')
18 | expect(method.call).to eq('kernel_data')
19 |
20 | ::Kernel.send(:undef_method, :object_data)
21 | result = public_symbiont_eval(object, direction: Symbiont::OKI, &closure)
22 | method = public_symbiont_method(:object_data, object, direction: Symbiont::OKI, &closure)
23 | expect(result).to eq('inner_data')
24 | expect(method.call).to eq('inner_data')
25 |
26 | object_class.send(:undef_method, :object_data)
27 | expect do
28 | public_symbiont_eval(object, direction: Symbiont::OKI, &closure)
29 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
30 |
31 | expect do
32 | public_symbiont_method(:object_data, object, direction: Symbiont::OKI, &closure)
33 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/features/private_inner_kernel_outer_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe 'Symbiont: inner context (object) => kernel context (kernel) => outer context (proc)' do
4 | include_context 'private similar contexts'
5 |
6 | specify 'private IKO resolution' do
7 | closure = proc { object_data(1, option: 2) }
8 |
9 | result = private_symbiont_eval(object, direction: Symbiont::IKO, &closure)
10 | method = private_symbiont_method(:object_data, object, direction: Symbiont::IKO, &closure)
11 | expect(result).to eq('inner_data')
12 | expect(method.call(1, option: 2)).to eq('inner_data')
13 |
14 | object_class.send(:undef_method, :object_data)
15 | result = private_symbiont_eval(object, direction: Symbiont::IKO, &closure)
16 | method = private_symbiont_method(:object_data, object, direction: Symbiont::IKO, &closure)
17 | expect(result).to eq('kernel_data')
18 | expect(method.call(1, option: 2)).to eq('kernel_data')
19 |
20 | ::Kernel.send(:undef_method, :object_data)
21 | result = private_symbiont_eval(object, direction: Symbiont::IKO, &closure)
22 | method = private_symbiont_method(:object_data, object, direction: Symbiont::IKO, &closure)
23 | expect(result).to eq('outer_data')
24 | expect(method.call(1, option: 2)).to eq('outer_data')
25 |
26 | undef object_data
27 | expect do
28 | private_symbiont_eval(object, direction: Symbiont::IKO, &closure)
29 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
30 |
31 | expect do
32 | private_symbiont_method(:object_data, object, direction: Symbiont::IKO, &closure)
33 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/features/private_inner_outer_kernel_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe 'Symbiont: inner context (object) => outer context (proc) => kernel context (kernel)' do
4 | include_context 'private similar contexts'
5 |
6 | specify 'private IOK resolution' do
7 | closure = proc { object_data(1, option: 2) }
8 |
9 | result = private_symbiont_eval(object, direction: Symbiont::IOK, &closure)
10 | method = private_symbiont_method(:object_data, object, direction: Symbiont::IOK, &closure)
11 | expect(result).to eq('inner_data')
12 | expect(method.call(1, option: 2)).to eq('inner_data')
13 |
14 | object_class.send(:undef_method, :object_data)
15 | result = private_symbiont_eval(object, direction: Symbiont::IOK, &closure)
16 | method = private_symbiont_method(:object_data, object, direction: Symbiont::IOK, &closure)
17 | expect(result).to eq('outer_data')
18 | expect(method.call(1, option: 2)).to eq('outer_data')
19 |
20 | undef object_data
21 | result = private_symbiont_eval(object, direction: Symbiont::IOK, &closure)
22 | method = private_symbiont_method(:object_data, object, direction: Symbiont::IOK, &closure)
23 | expect(result).to eq('kernel_data')
24 | expect(method.call(1, option: 2)).to eq('kernel_data')
25 |
26 | ::Kernel.send(:undef_method, :object_data)
27 | expect do
28 | private_symbiont_eval(object, direction: Symbiont::IOK, &closure)
29 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
30 |
31 | expect do
32 | private_symbiont_method(:object_data, object, direction: Symbiont::IOK, &closure)
33 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/features/private_kernel_inner_outer_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe 'Symbiont: kernel context (kernel) => inner context (object) => outer context (proc)' do
4 | include_context 'private similar contexts'
5 |
6 | specify 'private KIO resolution' do
7 | closure = proc { object_data(1, option: 2) }
8 |
9 | result = private_symbiont_eval(object, direction: Symbiont::KIO, &closure)
10 | method = private_symbiont_method(:object_data, object, direction: Symbiont::KIO, &closure)
11 | expect(result).to eq('kernel_data')
12 | expect(method.call(1, option: 2)).to eq('kernel_data')
13 |
14 | ::Kernel.send(:undef_method, :object_data)
15 | result = private_symbiont_eval(object, direction: Symbiont::KIO, &closure)
16 | method = private_symbiont_method(:object_data, object, direction: Symbiont::KIO, &closure)
17 | expect(result).to eq('inner_data')
18 | expect(method.call(1, option: 2)).to eq('inner_data')
19 |
20 | object_class.send(:undef_method, :object_data)
21 | result = private_symbiont_eval(object, direction: Symbiont::KIO, &closure)
22 | method = private_symbiont_method(:object_data, object, direction: Symbiont::KIO, &closure)
23 | expect(result).to eq('outer_data')
24 | expect(method.call(1, option: 2)).to eq('outer_data')
25 |
26 | undef object_data
27 | expect do
28 | private_symbiont_eval(object, direction: Symbiont::KIO, &closure)
29 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
30 |
31 | expect do
32 | private_symbiont_method(:object_data, object, direction: Symbiont::KIO, &closure)
33 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/features/private_kernel_outer_inner_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe 'Symbiont: kernel context (kernel) => outer context (proc) => inner context (object)' do
4 | include_context 'private similar contexts'
5 |
6 | specify 'private KOI resolution' do
7 | closure = proc { object_data(1, option: 2) }
8 |
9 | result = private_symbiont_eval(object, direction: Symbiont::KOI, &closure)
10 | method = private_symbiont_method(:object_data, object, direction: Symbiont::KOI, &closure)
11 | expect(result).to eq('kernel_data')
12 | expect(method.call(1, option: 2)).to eq('kernel_data')
13 |
14 | ::Kernel.send(:undef_method, :object_data)
15 | result = private_symbiont_eval(object, direction: Symbiont::KOI, &closure)
16 | method = private_symbiont_method(:object_data, object, direction: Symbiont::KOI, &closure)
17 | expect(result).to eq('outer_data')
18 | expect(method.call(1, option: 2)).to eq('outer_data')
19 |
20 | undef object_data
21 | result = private_symbiont_eval(object, direction: Symbiont::KOI, &closure)
22 | method = private_symbiont_method(:object_data, object, direction: Symbiont::KOI, &closure)
23 | expect(result).to eq('inner_data')
24 | expect(method.call(1, option: 2)).to eq('inner_data')
25 |
26 | object_class.send(:undef_method, :object_data)
27 | expect do
28 | private_symbiont_eval(object, direction: Symbiont::KOI, &closure)
29 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
30 |
31 | expect do
32 | private_symbiont_method(:object_data, object, direction: Symbiont::KOI, &closure)
33 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/features/private_outer_inner_kernel_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe 'Symbiont: outer context (proc) => inner context (object) => kernel context (kernel)' do
4 | include_context 'private similar contexts'
5 |
6 | specify 'private OIK resolution' do
7 | closure = proc { object_data(1, option: 2) }
8 |
9 | result = private_symbiont_eval(object, direction: Symbiont::OIK, &closure)
10 | method = private_symbiont_method(:object_data, object, direction: Symbiont::OIK, &closure)
11 | expect(result).to eq('outer_data')
12 | expect(method.call(1, option: 2)).to eq('outer_data')
13 |
14 | undef object_data
15 | result = private_symbiont_eval(object, direction: Symbiont::OIK, &closure)
16 | method = private_symbiont_method(:object_data, object, direction: Symbiont::OIK, &closure)
17 | expect(result).to eq('inner_data')
18 | expect(method.call(1, option: 2)).to eq('inner_data')
19 |
20 | object_class.send(:undef_method, :object_data)
21 | result = private_symbiont_eval(object, direction: Symbiont::OIK, &closure)
22 | method = private_symbiont_method(:object_data, object, direction: Symbiont::OIK, &closure)
23 | expect(result).to eq('kernel_data')
24 | expect(method.call(1, option: 2)).to eq('kernel_data')
25 |
26 | ::Kernel.send(:undef_method, :object_data)
27 | expect do
28 | private_symbiont_eval(object, direction: Symbiont::OIK, &closure)
29 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
30 |
31 | expect do
32 | private_symbiont_method(:object_data, object, direction: Symbiont::OIK, &closure)
33 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/features/private_outer_kernel_inner_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe 'Symbiont: outer context (proc) => kernel context (kernel) => inner context (object)' do
4 | include_context 'private similar contexts'
5 |
6 | specify 'private OKI resolution' do
7 | closure = proc { object_data(1, option: 2) }
8 |
9 | result = private_symbiont_eval(object, direction: Symbiont::OKI, &closure)
10 | method = private_symbiont_method(:object_data, object, direction: Symbiont::OKI, &closure)
11 | expect(result).to eq('outer_data')
12 | expect(method.call(1, option: 2)).to eq('outer_data')
13 |
14 | undef object_data
15 | result = private_symbiont_eval(object, direction: Symbiont::OKI, &closure)
16 | method = private_symbiont_method(:object_data, object, direction: Symbiont::OKI, &closure)
17 | expect(result).to eq('kernel_data')
18 | expect(method.call(1, option: 2)).to eq('kernel_data')
19 |
20 | ::Kernel.send(:undef_method, :object_data)
21 | result = private_symbiont_eval(object, direction: Symbiont::OKI, &closure)
22 | method = private_symbiont_method(:object_data, object, direction: Symbiont::OKI, &closure)
23 | expect(result).to eq('inner_data')
24 | expect(method.call(1, option: 2)).to eq('inner_data')
25 |
26 | object_class.send(:undef_method, :object_data)
27 | expect do
28 | private_symbiont_eval(object, direction: Symbiont::OKI, &closure)
29 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
30 |
31 | expect do
32 | private_symbiont_method(:object_data, object, direction: Symbiont::OKI, &closure)
33 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/lib/symbiont.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Main Symbiont namespace.
4 | #
5 | # @api public
6 | # @since 0.1.0
7 | module Symbiont
8 | require_relative 'symbiont/version'
9 | require_relative 'symbiont/trigger'
10 | require_relative 'symbiont/public_trigger'
11 | require_relative 'symbiont/private_trigger'
12 | require_relative 'symbiont/isolator'
13 | require_relative 'symbiont/executor'
14 | require_relative 'symbiont/context'
15 |
16 | # Method delegation order alias (inner_contexts => outer_context => kernel_context).
17 | #
18 | # @see Symbiont::Trigger::IOK
19 | #
20 | # @api public
21 | # @since 0.1.0
22 | IOK = Trigger::IOK
23 |
24 | # Method delegation order alias (outer_context => inner_contexts => kernel_context).
25 | #
26 | # @see Symbiont::Trigger::OIK
27 | #
28 | # @api public
29 | # @since 0.1.0
30 | OIK = Trigger::OIK
31 |
32 | # Method delegation order alias (outer_context => kernel_context => inner_contexts).
33 | #
34 | # @see Symbiont::Trigger::OKI
35 | #
36 | # @api public
37 | # @since 0.1.0
38 | OKI = Trigger::OKI
39 |
40 | # Method delegation order alias (inner_contexts => kernel_context => outer_context).
41 | #
42 | # @see Symbiont::Trigger::IKO
43 | #
44 | # @api public
45 | # @since 0.1.0
46 | IKO = Trigger::IKO
47 |
48 | # Method delegation order alias (kernel_context => outer_context => inner_contexts).
49 | #
50 | # @see Symbiont::Trigger::IOK
51 | #
52 | # @api public
53 | # @since 0.1.0
54 | KOI = Trigger::KOI
55 |
56 | # Method delegation order alias (kernel_context => inner_contexts => outer_context).
57 | #
58 | # @see Symbiont::Trigger::KIO
59 | #
60 | # @api public
61 | # @since 0.1.0
62 | KIO = Trigger::KIO
63 | end
64 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | ## [0.7.0] 2021-06-22
5 | ### Added
6 | - Support for Ruby@2.7.2, Ruby@3.0.0;
7 |
8 | ### Changed
9 | - No more `TravisCI` (TODO: migrate to `Github Actions`);
10 | - Updated development dependencies;
11 |
12 | ## [0.6.0] 2018-03-28
13 | ### Changed
14 | - Removed verbose code from `#__actual_context__` and `#method` methods of
15 | `Symbiont::PublicTrigger` and `Symbiont::PrivateTrigger` classes.
16 |
17 | ## [0.5.0] 2018-03-28
18 | ### Added
19 | - Support for method dispatching for `BasicObject` instances (which does not support `#respond_to?` method);
20 | - Support for method extracting for `BasicObject` instances (which does not support `#method` method);
21 | - Updated development dependencies;
22 | - Support for `Ruby@2.6.2`, `Ruby@2.5.5`;
23 |
24 | ## [0.4.0] 2018-10-25
25 | ### Added
26 | - Support for Ruby@2.5.3, Ruby@2.4.5, Ruby@2.3.8;
27 | - Updated development dependencies;
28 |
29 | ### Changed
30 | - End of Ruby@2.2;
31 |
32 | ## [0.3.0] 2018-06-15
33 | ### Added
34 | - `Symbiont::Isolator` - proc object isolation layer for delayed invocations;
35 |
36 | ## [0.2.0] 2018-04-22
37 | ### Added
38 | - Logo ^_^ (special thanks to **Viktoria Karaulova**)
39 | - Support for multiple inner contexts: you can pass an array of objects as a context argument
40 | to `Symbiont::Executor`. Each object will be used as an inner context in order they are passed.
41 |
42 | ### Changed
43 | - Method signature: context direction should be passed as a named attribute `context_direction:`.
44 |
45 | Affected methods:
46 | - `Symbiont::Executor.evaluate`
47 | - `Symbiont::Executor.evaluate_private`
48 | - `Symbiont::Executor.public_method`
49 | - `Symbiont::Executor.private_method`
50 |
51 | ## [0.1.0] - 2018-04-08
52 | - Release :)
53 |
--------------------------------------------------------------------------------
/symbiont-ruby.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | # frozen_string_literal: true
3 |
4 | lib = File.expand_path('lib', __dir__)
5 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6 | require 'symbiont/version'
7 |
8 | Gem::Specification.new do |spec|
9 | spec.required_ruby_version = '>= 2.3.8'
10 |
11 | spec.name = 'symbiont-ruby'
12 | spec.version = Symbiont::VERSION
13 | spec.author = 'Rustam Ibragimov'
14 | spec.email = 'iamdaiver@icloud.com'
15 | spec.summary = 'Evaluate proc-objects in many contexts simultaneously'
16 | spec.description = 'Symbiont is a cool implementation of proc-objects execution algorithm: ' \
17 | 'in the context of other object, but with the preservation of ' \
18 | 'the closed environment of the proc object and with the ability of ' \
19 | 'control the method dispatch inside it. A proc object is executed in ' \
20 | 'three contexts: in the context of required object, in the context of '\
21 | 'a closed proc\'s environment and in the global (Kernel) context.'
22 |
23 | spec.homepage = 'https://github.com/0exp/symbiont-ruby'
24 | spec.license = 'MIT'
25 | spec.bindir = 'bin'
26 | spec.require_paths = ['lib']
27 |
28 | spec.files = `git ls-files -z`.split("\x0").reject do |f|
29 | f.match(%r{^(spec|features)/})
30 | end
31 |
32 | spec.add_development_dependency 'rspec', '~> 3.10'
33 | spec.add_development_dependency 'simplecov', '~> 0.21'
34 | spec.add_development_dependency 'armitage-rubocop', '~> 1.8'
35 | spec.add_development_dependency 'rbs', '~> 1.0'
36 | spec.add_development_dependency 'typeprof', '~> 0.12'
37 | spec.add_development_dependency 'steep', '~> 0.40'
38 |
39 | spec.add_development_dependency 'pry'
40 | spec.add_development_dependency 'rake'
41 | spec.add_development_dependency 'bundler'
42 | spec.add_development_dependency 'yard'
43 | end
44 |
--------------------------------------------------------------------------------
/lib/symbiont/context.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Symbiont
4 | class << self
5 | # Factory method for a mixin module that provides an ability to invoke procs and lambdas
6 | # in many contexts to any object. Mixes up special methods that delegate execution logic to
7 | # to a special mediator object (`Symbiont::Executor`).
8 | #
9 | # @param default_context_direction [Array]
10 | # Delegation order `for Symbiont::Executor`. Trigger::IOK is used by default.
11 | # @return [Module]
12 | #
13 | # @see Symbiont::Executor
14 | # @see Symbiont::Trigger
15 | # @see Symbiont::PublicTrigger
16 | # @see Symbiont::PrivateTrigger
17 | #
18 | # @api public
19 | # @since 0.1.0
20 | #
21 | # rubocop:disable Naming/MethodName
22 | def Context(default_context_direction = Trigger::IOK)
23 | Module.new do
24 | define_method :evaluate do |context_direction = default_context_direction, &closure|
25 | Executor.evaluate(self, context_direction: context_direction, &closure)
26 | end
27 |
28 | define_method :evaluate_private do |context_direction = default_context_direction, &closure|
29 | Executor.evaluate_private(self, context_direction: context_direction, &closure)
30 | end
31 |
32 | define_method :public_method do |method_name, context_direction = default_context_direction, &closure|
33 | Executor.public_method(self, method_name, context_direction: context_direction, &closure)
34 | end
35 |
36 | define_method :private_method do |method_name, context_direction = default_context_direction, &closure|
37 | Executor.private_method(self, method_name, context_direction: context_direction, &closure)
38 | end
39 | end
40 | end
41 | # rubocop:enable Naming/MethodName
42 | end
43 |
44 | # Default Context mixin that provides an ability to invoke procs and lambdas
45 | # in many contexts to any object. Mixes up special methods that delegate execution logic to
46 | # to a special mediator object (`Symbiont::Executor`). Uses Symbiont::Trigger::IOK delegation order.
47 | #
48 | # @see Symbiont.Context
49 | #
50 | # @api public
51 | # @since 0.1.0
52 | Context = Context(Trigger::IOK)
53 | end
54 |
--------------------------------------------------------------------------------
/lib/symbiont/private_trigger.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Symbiont
4 | # A trigger that considers both public and private methods of executable contexts
5 | # during method dispatching.
6 | #
7 | # @see Symbiont::Trigger
8 | #
9 | # @api private
10 | # @since 0.1.0
11 | class PrivateTrigger < Trigger
12 | # Returns the first context that is able to respond to the required method.
13 | # The context is chosen in the context direction order (see #__context_direction__).
14 | # Raises NoMethodError excepition when no one of the contexts are able to respond to
15 | # the required method.
16 | # Basicaly (in #super), abstract implementation raises NoMethodError.
17 | #
18 | # @param method_name [String,Symbol] Method that a context should respond to.
19 | # @raise NoMethodError
20 | # Is raised when no one of the contexts are able to respond to the required method.
21 | # @return [Objcet]
22 | #
23 | # @see Symbiont::Trigger#__actual_context__
24 | #
25 | # @api private
26 | # @since 0.1.0
27 | def __actual_context__(method_name)
28 | __directed_contexts__.find do |context|
29 | begin
30 | context.respond_to?(method_name, true)
31 | rescue ::NoMethodError
32 | # NOTE:
33 | # this situation is caused when the context object does not respodond to
34 | # #resond_to? method (BasicObject instances for example)
35 |
36 | context_singleton = __extract_singleton_class__(context)
37 |
38 | context_singleton.private_instance_methods(true).include?(method_name) ||
39 | context_singleton.instance_methods(true).include?(method_name)
40 | end
41 | end || super
42 | end
43 |
44 | # Returns a corresponding public/private method object of the actual context.
45 | #
46 | # @param method_name [String,Symbol] Method name
47 | # @return [Method]
48 | #
49 | # @see [Symbiont::Trigger#method]
50 | #
51 | # @api private
52 | # @since 0.5.0
53 | def method(method_name)
54 | __context__ = __actual_context__(method_name)
55 |
56 | # NOTE:
57 | # block is used cuz #__actual_context__can raise
58 | # ::NoMethodError (ContextNoMethodError) too (and we should raise it).
59 | begin
60 | __context__.method(method_name)
61 | rescue ::NoMethodError
62 | # NOTE:
63 | # this situation is caused when the context object does not respond
64 | # to #method method (BasicObject instances for example). We can extract
65 | # method objects via it's singleton class.
66 |
67 | __context_singleton__ = __extract_singleton_class__(__context__)
68 | __context_singleton__.instance_method(method_name).bind(__context__)
69 | end
70 | end
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/lib/symbiont/public_trigger.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Symbiont
4 | # A trigger that considers only public methods of executable contexts
5 | # during method dispatching.
6 | #
7 | # @see Symbiont::Trigger
8 | #
9 | # @api private
10 | # @since 0.1.0
11 | class PublicTrigger < Trigger
12 | # Returns the first context that is able to respond to the required method.
13 | # The context is chosen in the context direction order (see #__context_direction__).
14 | # Raises NoMethodError excepition when no one of the contexts are able to respond to
15 | # the required method.
16 | # Basicaly (in #super), abstract implementation raises NoMethodError.
17 | #
18 | # @param method_name [String,Symbol] Method that a context should respond to.
19 | # @raise NoMethodError
20 | # Is raised when no one of the contexts are able to respond to the required method.
21 | # @return [Objcet]
22 | #
23 | # @see Symbiont::Trigger#__actual_context__
24 | #
25 | # @api private
26 | # @since 0.1.0
27 | def __actual_context__(method_name)
28 | __directed_contexts__.find do |context|
29 | begin
30 | context.respond_to?(method_name, false)
31 | rescue ::NoMethodError
32 | # NOTE:
33 | # this situation is caused when the context object does not respodond to
34 | # #resond_to? method (BasicObject instances for example)
35 |
36 | context_singleton = __extract_singleton_class__(context)
37 | context_singleton.public_instance_methods(true).include?(method_name)
38 | end
39 | end || super
40 | end
41 |
42 | # Returns a corresponding public method object of the actual context.
43 | #
44 | # @param method_name [String,Symbol] Method name
45 | # @raise [::NameError]
46 | # @raise [Symbiont::Trigger::ContextNoMethodError, ::NoMethodError]
47 | # @return [Method]
48 | #
49 | # @see [Symbiont::Trigger#method]
50 | #
51 | # @api private
52 | # @since 0.5.0
53 | def method(method_name)
54 | __context__ = __actual_context__(method_name)
55 |
56 | # NOTE:
57 | # block is used cuz #__actual_context__can raise
58 | # ::NoMethodError (ContextNoMethodError) too (and we should raise it)
59 | begin
60 | __context__.method(method_name)
61 | rescue ::NoMethodError
62 | # NOTE:
63 | # this situation is caused when the context object does not respond
64 | # to #method method (BasicObject instances for example). We can extract
65 | # method objects via it's singleton class.
66 |
67 | __context_singleton__ = __extract_singleton_class__(__context__)
68 | __context_singleton__.public_instance_method(method_name).bind(__context__)
69 | end
70 | end
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | symbiont-ruby (0.7.0)
5 |
6 | GEM
7 | remote: https://rubygems.org/
8 | specs:
9 | activesupport (6.1.3.2)
10 | concurrent-ruby (~> 1.0, >= 1.0.2)
11 | i18n (>= 1.6, < 2)
12 | minitest (>= 5.1)
13 | tzinfo (~> 2.0)
14 | zeitwerk (~> 2.3)
15 | armitage-rubocop (1.10.0)
16 | rubocop (= 1.10.0)
17 | rubocop-performance (= 1.9.2)
18 | rubocop-rails (= 2.9.1)
19 | rubocop-rake (= 0.5.1)
20 | rubocop-rspec (= 2.2.0)
21 | ast (2.4.2)
22 | coderay (1.1.3)
23 | concurrent-ruby (1.1.9)
24 | diff-lcs (1.4.4)
25 | docile (1.4.0)
26 | ffi (1.15.3)
27 | i18n (1.8.10)
28 | concurrent-ruby (~> 1.0)
29 | language_server-protocol (3.16.0.1)
30 | listen (3.5.1)
31 | rb-fsevent (~> 0.10, >= 0.10.3)
32 | rb-inotify (~> 0.9, >= 0.9.10)
33 | method_source (1.0.0)
34 | minitest (5.14.4)
35 | parallel (1.20.1)
36 | parser (3.0.1.1)
37 | ast (~> 2.4.1)
38 | pry (0.14.1)
39 | coderay (~> 1.1)
40 | method_source (~> 1.0)
41 | rack (2.2.3)
42 | rainbow (3.0.0)
43 | rake (13.0.3)
44 | rb-fsevent (0.11.0)
45 | rb-inotify (0.10.1)
46 | ffi (~> 1.0)
47 | rbs (1.2.1)
48 | regexp_parser (2.1.1)
49 | rexml (3.2.5)
50 | rspec (3.10.0)
51 | rspec-core (~> 3.10.0)
52 | rspec-expectations (~> 3.10.0)
53 | rspec-mocks (~> 3.10.0)
54 | rspec-core (3.10.1)
55 | rspec-support (~> 3.10.0)
56 | rspec-expectations (3.10.1)
57 | diff-lcs (>= 1.2.0, < 2.0)
58 | rspec-support (~> 3.10.0)
59 | rspec-mocks (3.10.2)
60 | diff-lcs (>= 1.2.0, < 2.0)
61 | rspec-support (~> 3.10.0)
62 | rspec-support (3.10.2)
63 | rubocop (1.10.0)
64 | parallel (~> 1.10)
65 | parser (>= 3.0.0.0)
66 | rainbow (>= 2.2.2, < 4.0)
67 | regexp_parser (>= 1.8, < 3.0)
68 | rexml
69 | rubocop-ast (>= 1.2.0, < 2.0)
70 | ruby-progressbar (~> 1.7)
71 | unicode-display_width (>= 1.4.0, < 3.0)
72 | rubocop-ast (1.7.0)
73 | parser (>= 3.0.1.1)
74 | rubocop-performance (1.9.2)
75 | rubocop (>= 0.90.0, < 2.0)
76 | rubocop-ast (>= 0.4.0)
77 | rubocop-rails (2.9.1)
78 | activesupport (>= 4.2.0)
79 | rack (>= 1.1)
80 | rubocop (>= 0.90.0, < 2.0)
81 | rubocop-rake (0.5.1)
82 | rubocop
83 | rubocop-rspec (2.2.0)
84 | rubocop (~> 1.0)
85 | rubocop-ast (>= 1.1.0)
86 | ruby-progressbar (1.11.0)
87 | simplecov (0.21.2)
88 | docile (~> 1.1)
89 | simplecov-html (~> 0.11)
90 | simplecov_json_formatter (~> 0.1)
91 | simplecov-html (0.12.3)
92 | simplecov_json_formatter (0.1.3)
93 | steep (0.44.1)
94 | activesupport (>= 5.1)
95 | language_server-protocol (>= 3.15, < 4.0)
96 | listen (~> 3.0)
97 | parallel (>= 1.0.0)
98 | parser (>= 2.7)
99 | rainbow (>= 2.2.2, < 4.0)
100 | rbs (>= 1.2.0)
101 | terminal-table (>= 2, < 4)
102 | terminal-table (3.0.1)
103 | unicode-display_width (>= 1.1.1, < 3)
104 | typeprof (0.14.1)
105 | rbs (>= 1.2.0)
106 | tzinfo (2.0.4)
107 | concurrent-ruby (~> 1.0)
108 | unicode-display_width (2.0.0)
109 | yard (0.9.26)
110 | zeitwerk (2.4.2)
111 |
112 | PLATFORMS
113 | ruby
114 | x86_64-darwin-20
115 |
116 | DEPENDENCIES
117 | armitage-rubocop (~> 1.8)
118 | bundler
119 | pry
120 | rake
121 | rbs (~> 1.0)
122 | rspec (~> 3.10)
123 | simplecov (~> 0.21)
124 | steep (~> 0.40)
125 | symbiont-ruby!
126 | typeprof (~> 0.12)
127 | yard
128 |
129 | BUNDLED WITH
130 | 2.2.20
131 |
--------------------------------------------------------------------------------
/spec/features/public_multiple_contexts_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe 'Symbiont: public multiple contexts' do
4 | include_context 'public similar contexts'
5 |
6 | specify 'corresponding method resolution' do
7 | closure = proc { "#{object_data} #{object_info}" }
8 |
9 | ::Kernel.send(:undef_method, :object_data) rescue nil
10 | ::Kernel.send(:undef_method, :object_info) rescue nil
11 | result = public_symbiont_eval(object, another_object, direction: Symbiont::IOK, &closure)
12 | expect(result).to eq('inner_data inner_info')
13 |
14 | data_method = public_symbiont_method(:object_data, object, another_object, direction: Symbiont::IOK, &closure)
15 | info_method = public_symbiont_method(:object_info, object, another_object, direction: Symbiont::IOK, &closure)
16 | expect(data_method.call).to eq('inner_data')
17 | expect(info_method.call).to eq('inner_info')
18 |
19 | result = public_symbiont_eval(object, another_object, direction: Symbiont::OIK, &closure)
20 | expect(result).to eq('outer_data outer_info')
21 |
22 | data_method = public_symbiont_method(:object_data, object, another_object, direction: Symbiont::OIK, &closure)
23 | info_method = public_symbiont_method(:object_info, object, another_object, direction: Symbiont::OIK, &closure)
24 | expect(data_method.call).to eq('outer_data')
25 | expect(info_method.call).to eq('outer_info')
26 |
27 | module Kernel
28 | def object_data
29 | 'kernel_data'
30 | end
31 |
32 | def object_info
33 | 'kernel_info'
34 | end
35 | end
36 | result = public_symbiont_eval(object, another_object, direction: Symbiont::KIO, &closure)
37 | expect(result).to eq('kernel_data kernel_info')
38 |
39 | data_method = public_symbiont_method(:object_data, object, another_object, direction: Symbiont::KIO, &closure)
40 | info_method = public_symbiont_method(:object_info, object, another_object, direction: Symbiont::KIO, &closure)
41 | expect(data_method.call).to eq('kernel_data')
42 | expect(info_method.call).to eq('kernel_info')
43 |
44 | ::Kernel.send(:undef_method, :object_data)
45 | result = public_symbiont_eval(object, another_object, direction: Symbiont::KIO, &closure)
46 | expect(result).to eq('inner_data kernel_info')
47 |
48 | data_method = public_symbiont_method(:object_data, object, another_object, direction: Symbiont::KIO, &closure)
49 | info_method = public_symbiont_method(:object_info, object, another_object, direction: Symbiont::KIO, &closure)
50 | expect(data_method.call).to eq('inner_data')
51 | expect(info_method.call).to eq('kernel_info')
52 |
53 | ::Kernel.send(:undef_method, :object_info)
54 | undef object_info
55 | result = public_symbiont_eval(object, another_object, direction: Symbiont::KOI, &closure)
56 | expect(result).to eq('outer_data inner_info')
57 |
58 | data_method = public_symbiont_method(:object_data, object, another_object, direction: Symbiont::KOI, &closure)
59 | info_method = public_symbiont_method(:object_info, object, another_object, direction: Symbiont::KOI, &closure)
60 | expect(data_method.call).to eq('outer_data')
61 | expect(info_method.call).to eq('inner_info')
62 |
63 | undef object_data
64 | result = public_symbiont_eval(object, another_object, direction: Symbiont::KOI, &closure)
65 | expect(result).to eq('inner_data inner_info')
66 |
67 | data_method = public_symbiont_method(:object_data, object, another_object, direction: Symbiont::KIO, &closure)
68 | info_method = public_symbiont_method(:object_info, object, another_object, direction: Symbiont::KIO, &closure)
69 | expect(data_method.call).to eq('inner_data')
70 | expect(info_method.call).to eq('inner_info')
71 |
72 | object_class.send(:undef_method, :object_data)
73 |
74 | expect do
75 | public_symbiont_method(:object_data, object, another_object, direction: Symbiont::KOI, &closure)
76 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
77 |
78 | info_method = public_symbiont_method(:object_info, object, another_object, direction: Symbiont::KOI, &closure)
79 | expect(info_method.call).to eq('inner_info')
80 |
81 | another_object_class.send(:undef_method, :object_info)
82 |
83 | expect do
84 | public_symbiont_method(:object_info, object, another_object, direction: Symbiont::KOI, &closure)
85 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
86 |
87 | expect do
88 | public_symbiont_eval(object, another_object, direction: Symbiont::KOI, &closure)
89 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
90 | end
91 | end
92 |
--------------------------------------------------------------------------------
/spec/features/private_multiple_contexts_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | describe 'Symbiont: private multiple contexts' do
4 | include_context 'private similar contexts'
5 |
6 | specify 'corresponding method resolution' do
7 | closure = proc { "#{object_data(1, option: 2)} #{object_info}" }
8 |
9 | ::Kernel.send(:undef_method, :object_data) rescue nil
10 | ::Kernel.send(:undef_method, :object_info) rescue nil
11 | result = private_symbiont_eval(object, another_object, direction: Symbiont::IOK, &closure)
12 | expect(result).to eq('inner_data inner_info')
13 |
14 | data_method = private_symbiont_method(:object_data, object, another_object, direction: Symbiont::IOK, &closure)
15 | info_method = private_symbiont_method(:object_info, object, another_object, direction: Symbiont::IOK, &closure)
16 | expect(data_method.call(1, option: 2)).to eq('inner_data')
17 | expect(info_method.call).to eq('inner_info')
18 |
19 | result = private_symbiont_eval(object, another_object, direction: Symbiont::OIK, &closure)
20 | expect(result).to eq('outer_data outer_info')
21 |
22 | data_method = private_symbiont_method(:object_data, object, another_object, direction: Symbiont::OIK, &closure)
23 | info_method = private_symbiont_method(:object_info, object, another_object, direction: Symbiont::OIK, &closure)
24 | expect(data_method.call(1, option: 2)).to eq('outer_data')
25 | expect(info_method.call).to eq('outer_info')
26 |
27 | module Kernel
28 | private
29 |
30 | def object_data(arg, option:)
31 | 'kernel_data'
32 | end
33 |
34 | def object_info
35 | 'kernel_info'
36 | end
37 | end
38 | result = private_symbiont_eval(object, another_object, direction: Symbiont::KIO, &closure)
39 | expect(result).to eq('kernel_data kernel_info')
40 |
41 | data_method = private_symbiont_method(:object_data, object, another_object, direction: Symbiont::KIO, &closure)
42 | info_method = private_symbiont_method(:object_info, object, another_object, direction: Symbiont::KIO, &closure)
43 | expect(data_method.call(1, option: 2)).to eq('kernel_data')
44 | expect(info_method.call).to eq('kernel_info')
45 |
46 | ::Kernel.send(:undef_method, :object_data)
47 | result = private_symbiont_eval(object, another_object, direction: Symbiont::KIO, &closure)
48 | expect(result).to eq('inner_data kernel_info')
49 |
50 | data_method = private_symbiont_method(:object_data, object, another_object, direction: Symbiont::KIO, &closure)
51 | info_method = private_symbiont_method(:object_info, object, another_object, direction: Symbiont::KIO, &closure)
52 | expect(data_method.call(1, option: 2)).to eq('inner_data')
53 | expect(info_method.call).to eq('kernel_info')
54 |
55 | ::Kernel.send(:undef_method, :object_info)
56 | undef object_info
57 | result = private_symbiont_eval(object, another_object, direction: Symbiont::KOI, &closure)
58 | expect(result).to eq('outer_data inner_info')
59 |
60 | data_method = private_symbiont_method(:object_data, object, another_object, direction: Symbiont::KOI, &closure)
61 | info_method = private_symbiont_method(:object_info, object, another_object, direction: Symbiont::KOI, &closure)
62 | expect(data_method.call(1, option: 2)).to eq('outer_data')
63 | expect(info_method.call).to eq('inner_info')
64 |
65 | undef object_data
66 | result = private_symbiont_eval(object, another_object, direction: Symbiont::KOI, &closure)
67 | expect(result).to eq('inner_data inner_info')
68 |
69 | data_method = private_symbiont_method(:object_data, object, another_object, direction: Symbiont::KIO, &closure)
70 | info_method = private_symbiont_method(:object_info, object, another_object, direction: Symbiont::KIO, &closure)
71 | expect(data_method.call(1, option: 2)).to eq('inner_data')
72 | expect(info_method.call).to eq('inner_info')
73 |
74 | object_class.send(:undef_method, :object_data)
75 |
76 | expect do
77 | private_symbiont_method(:object_data, object, another_object, direction: Symbiont::KOI, &closure)
78 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
79 |
80 | info_method = private_symbiont_method(:object_info, object, another_object, direction: Symbiont::KOI, &closure)
81 | expect(info_method.call).to eq('inner_info')
82 |
83 | another_object_class.send(:undef_method, :object_info)
84 |
85 | expect do
86 | private_symbiont_method(:object_info, object, another_object, direction: Symbiont::KOI, &closure)
87 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
88 |
89 | expect do
90 | private_symbiont_eval(object, another_object, direction: Symbiont::KOI, &closure)
91 | end.to raise_error(Symbiont::Trigger::ContextNoMethodError)
92 | end
93 | end
94 |
--------------------------------------------------------------------------------
/lib/symbiont/executor.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Symbiont
4 | # Mediator service object that controls the logic of creating triggers and calling them.
5 | # Acts as a factory for trigerrs and an execution-mediator for procs.
6 | #
7 | # @api public
8 | # @since 0.1.0
9 | module Executor
10 | class << self
11 | # Starts execution of a proc object in the context of the passed object with the selected
12 | # direction of method dispatching. Delegates execution to a public trigger.
13 | #
14 | # @param required_contexts [Array
8 |
9 | **Symbiont** is a cool implementation of proc-objects execution algorithm: in the context of other object,
10 | but with the preservation of the captured environment of the proc object and with the ability of control the method dispatch
11 | inside it. A proc object is executed in three contexts: in the context of required object, in the context of
12 | a captured proc's environment and in the global (Kernel) context.
13 |
14 | # Installation
15 |
16 | Add this line to your application's Gemfile:
17 |
18 | ```ruby
19 | gem 'symbiont-ruby'
20 | ```
21 |
22 | And then execute:
23 |
24 | ```shell
25 | $ bundle install
26 | ```
27 |
28 | Or install it yourself as:
29 |
30 | ```shell
31 | $ gem install symbiont-ruby
32 | ```
33 |
34 | After all:
35 |
36 | ```ruby
37 | require 'symbiont'
38 | ```
39 |
40 | # Table of contents
41 |
42 | - [Problem and motivation](#problems-and-motivaiton)
43 | - [Usage](#usage)
44 | - [Context management](#context-management)
45 | - [Supported method delegation directions](#supported-methods-delegation-directions)
46 | - [Proc object invocation](#proc-object-invocation)
47 | - [Considering public methods only (.evaluate)](#considering-public-methods-only-evaluate)
48 | - [Considering private and public methods (.evaluate_private)](#considering-private-and-public-methods-evaluate_private)
49 | - [Getting method-objects (Method)](#getting-method-objects-method)
50 | - [Considering public methods only (.public_method)](#considering-public-methods-only-public_method)
51 | - [Considering public and private methods (.private_method)](#considering-public-and-private-methods-private_method)
52 | - [Symbiont Mixin](#symbiont-mixin)
53 | - [Mixing a module with default delegation direction](#mixing-a-module-with-default-delegation-direction)
54 | - [Mixing a module with certain delegation direction](#mixing-a-module-with-certain-delegation-direction)
55 | - [Multiple inner contexts](#multiple-inner-contexts)
56 | - [Isolator - proc object isolation layer for delayed invocations](#isolator---proc-object-isolation-layer-for-delayed-invocations)
57 |
58 | # Problems and motivaiton
59 |
60 | The main problem of `instance_eval` / `instance exec` / `class_eval` / `class_exec` is that the binding (self)
61 | inside a proc-object is replaced with the object in where a proc object is executed. This allows you to delegate all methods captured by a proc to another object.
62 | But this leads to the fact that the proc object loses access to the original captured environment.
63 | Symbiont solves this problem by allowing the proc to be executed in the required context while maintaining access to the methods of the captured environment
64 | (including the global context).
65 |
66 | ---
67 |
68 | A problem with `instance_eval` / `instance_exec` / `class_eval` / `class_exec`:
69 |
70 | ```ruby
71 | class TableFactory
72 | def initialize
73 | @primary_key = nil
74 | end
75 |
76 | def primary_key(key)
77 | @primary_key = key
78 | end
79 | end
80 |
81 | class Migration
82 | class << self
83 | def create_table(&block)
84 | TableFactory.new.tap do |table|
85 | table.instance_eval(&block) # NOTE: closure invocation
86 | end
87 | end
88 | end
89 | end
90 |
91 | class CreateUsers < Migration
92 | class << self
93 | def up
94 | create_table do # NOTE: failing closure
95 | primary_key(generate_pk(:id))
96 | end
97 | end
98 |
99 | def generate_pk(name)
100 | "secure_#{name}"
101 | end
102 | end
103 | end
104 |
105 | CreateUsers.up
106 | # => NoMethodError: undefined method `generate_pk' for #
107 | ```
108 |
109 | Symbiont solves this:
110 |
111 | ```ruby
112 | require 'symbiont'
113 |
114 | class Migration
115 | class << self
116 | def create_table(&block)
117 | TableFactory.new.tap do |table|
118 | Symbiont::Executor.evaluate(table, &block) # NOTE: intercept closure invocation by Symbiont
119 | end
120 | end
121 | end
122 | end
123 |
124 | CreateUsers.up
125 | # => #
126 | ```
127 |
128 | ---
129 |
130 | Proc-object is executed in three contexts at the same time:
131 |
132 | - in the context of closure;
133 | - in the context of required object;
134 | - in the global context (Kernel).
135 |
136 | Methods (called internally) are delegated to the context that is first able to respond.
137 | The order of context selection depends on the corresponding context direction parameter.
138 | By default the delegation order is: object context => closure context => global context.
139 | If no context is able to respond to the method, an exception is raised (`Symbiont::Trigger::ContextNoMethodError`).
140 | Symbiont can consider the visibility of methods when executing.
141 |
142 |
143 | # Usage
144 |
145 | ## Context management
146 |
147 | Imagine having the following code and the corresponding clojure in the real application:
148 |
149 | ```ruby
150 | def object_data
151 | 'outer_context'
152 | end
153 |
154 | class SimpleObject
155 | def format_data(data)
156 | "Data: #{data}"
157 | end
158 |
159 | def object_data
160 | 'inner_context'
161 | end
162 | end
163 |
164 | class << Kernel
165 | def object_data
166 | 'kernel_data'
167 | end
168 | end
169 |
170 | object = SimpleObject.new
171 |
172 | closure = proc { format_data(object_data) } # NOTE: our closure
173 | ```
174 |
175 | How a proc object will be processed, which context will be selected, how to make sure that nothing is broken - welcome to Symbiont.
176 |
177 | ## Supported methods delegation directions
178 |
179 | Delegation order might be passed as a parameter to the proc execution
180 | and to the special mixin module, allowing any class or instance to become a symbiont.
181 |
182 | - Supported contexts:
183 | - inner context - an object where proc is executed;
184 | - outer context - external environment of the proc object;
185 | - kernel context - global Kernel context.
186 |
187 | Symbiont::IOK is chosen by default (`inner context => outer context => kernel context`).
188 |
189 | ```ruby
190 | Symbiont::IOK # Inner Context => Outer Context => Kernel Context (DEFAULT)
191 | Symbiont::OIK # Outer Context => Inner Context => Kernel Context
192 | Symbiont::OKI # Outer Context => Kernel Context => Inner Context
193 | Symbiont::IKO # Inner Context => Kernel Context => Outer Context
194 | Symbiont::KOI # Kernel Context => Outer Context => Inner Context
195 | Symbiont::KIO # Kernel Context => Inner Context => Outer Context
196 | ```
197 |
198 | ## Proc object invocation
199 |
200 | `Symbiont::Executor` allows you to execute proc objects in two modes of the delegation:
201 |
202 | - only public methods:
203 | - `evaluate(*required_contexts, [context_direction:], &closure)`
204 | - public and private methods:
205 | - `evaluate_private(*required_contexts, [context_direction:], &closure)`
206 |
207 | If none of contexts is able to respond to the required method - `Symbiont::Trigger::ContextNoMethodError` exception is raised.
208 |
209 | In the case an unsupported direction value is used - `Symbiont::Trigger::IncompatibleContextDirectionError` exception is raised.
210 |
211 | If proc object isn't passed to the executor - `Symbiont::Trigger::UnprovidedClosureAttributeError` exception is raised.
212 |
213 | #### Considering public methods only (.evaluate)
214 |
215 | ```ruby
216 | # with default delegation order (Symbiont::IOK)
217 | Symbiont::Executor.evaluate(object) do
218 | format_data(object_data)
219 | end
220 | # => "Data: inner_context"
221 |
222 | # with a custom delegation order
223 | Symbiont::Executor.evaluate(object, context_direction: Symbiont::KIO) do
224 | format_data(object_data)
225 | end
226 | # => "Data: kernel_context"
227 |
228 | # SimpleObject#object_data is a private method (inner_context)
229 | Symbiont::Executor.evaluate(object, context_direction: Symbiont::IOK) do
230 | format_data(object_data)
231 | end
232 | # => "Data: outer_context"
233 | ```
234 |
235 | #### Considering private and public methods (.evaluate_private)
236 |
237 | ```ruby
238 | # with default delegation order (Symbiont::IOK)
239 | Symbiont::Executor.evaluate_private(object) do
240 | format_data(object_data)
241 | end
242 | # => "Data: inner_context"
243 |
244 | # with a custom delegation order
245 | Symbiont::Executor.evaluate_private(object, context_direction: Symbiont::KIO) do
246 | format_data(object_data)
247 | end
248 | # => "Data: kernel_context"
249 |
250 | # SimpleObject#object_data is a private method (inner_context)
251 | Symbiont::Executor.evaluate_private(object, context_direction: Symbiont::IOK) do
252 | format_data(object_data)
253 | end
254 | # => "Data: inner_data"
255 | ```
256 |
257 | ## Getting method-objects (Method)
258 |
259 | `Symbiont::Executor` provides the possibility of obtaining the method object with consideration of the chosen delegation order:
260 |
261 | - only public methods:
262 | - `public_method(method_name, *required_contexts, [context_direction:], &clojure)`
263 | - public and private methods:
264 | - `private_method(method_name, *required_contexts, [context_direction:], &clojure)`
265 |
266 | If none of contexts is able to respond to the required method - `Symbiont::Trigger::ContextNoMethodError` exception is raised.
267 |
268 | In the case an unsupported direction value is used - `Symbiont::Trigger::IncompatibleContextDirectionError` exception is raised.
269 |
270 | #### Considering public methods only (.public_method)
271 |
272 | ```ruby
273 | # with default delegation order (Symbiont::IOK)
274 | Symbiont::Executor.public_method(:object_data, object, &closure)
275 | # => #
276 |
277 | # with a custom delegation order
278 | Symbiont::Executor.public_method(:object_data, object, context_direction: Symbiont::OIK, &closure)
279 | # => (main) #
280 |
281 | # SimpleObject#object_data is a private method
282 | Symbiont::Executor.public_method(:object_data, object, context_direction: Symbiont::IOK, &closure)
283 | # => (main) #
284 | ```
285 |
286 | #### Considering public and private methods (.private_method)
287 |
288 | ```ruby
289 | # with default delegation order (Symbiont::IOK)
290 | Symbiont::Executor.private_method(:object_data, object, &clojure)
291 | # => #
292 |
293 | # with a custom delegation order
294 | Symbiont::Executor.private_method(:object_data, object, context_direction: Symbiont::KIO, &clojure)
295 | # => #
296 |
297 | # SimpleObject#object_data is a private_method
298 | Symbiont::Executor.private_method(:object_data, object, context_direction: Symbiotn::IKO, &clojure)
299 | # => #
300 | ```
301 |
302 | ## Symbiont Mixin
303 |
304 | `Symbiont::Context` is a mixin that allows any object to call proc objects in the context of itself as `Symbiont::Executor`.
305 |
306 | You can specify the direction of the context delegation. `Symbiont::IOK` is used by default.
307 |
308 | #### Mixing a module with default delegation direction
309 |
310 | ```ruby
311 | class SimpleObject
312 | include Symbiont::Context # Symbiont::IOK direction is used by default
313 |
314 | # #evaluate([context_direction = Symbiont::IOK], &closure)
315 | # #evaluate_private([context_direction = Symbiont::IOK], &closure)
316 | # #public_method(method_name, [context_direction = Symbiont::IOK])
317 | # #private_method(method_name, [context_direction = Symbiont::IOK])
318 |
319 | extend Symbiont::Context # Symbiont::IOK direction is used by default
320 |
321 | # .evaluate([context_direction = Symbiont::IOK], &closure)
322 | # .evaluate_private([context_direction = Symbiont::IOK], &closure)
323 | # .public_method(method_name, [context_direction = Symbiont::IOK])
324 | # .private_method(method_name, [context_direction = Symbiont::IOK])
325 | end
326 |
327 | def object_data
328 | 'outer_context'
329 | end
330 |
331 | SimpleObject.new.evaluate { object_data }
332 | # => object.object_data => "inner_context"
333 |
334 | SimpleObject.new.evaluate(Symbiont::OIK) { object_data }
335 | # => object_data() => "outer_context"
336 | ```
337 |
338 | #### Mixing a module with certain delegation direction
339 |
340 | ```ruby
341 | class SimpleObject
342 | include Symbiont::Context(Symboiont::KOI) # use a custom direction
343 |
344 | # #evaluate([context_direction = Symbiont::KOI], &closure)
345 | # #evaluate_private([context_direction = Symbiont::KOI], &closure)
346 | # #public_method(method_name, [context_direction = Symbiont::KOI])
347 | # #private_method(method_name, [context_direction = Symbiont::KOI])
348 |
349 | extend Symbiont::Context(Symbiont::KOI) # use a custom direction
350 |
351 | # .evaluate([context_direction = Symbiont::KOI], &closure)
352 | # .evaluate_private([context_direction = Symbiont::KOI], &closure)
353 | # .public_method(method_name, [context_direction = Symbiont::KOI])
354 | # .private_method(method_name, [context_direction = Symbiont::KOI])
355 | end
356 |
357 | SimpleObject.new.evaluate { object_data }
358 | # => Kernel.object_data => "kernel_context"
359 |
360 | SimpleObject.new.evaluate(Symbiont::IOK) { object_data }
361 | # => object.object_data => "inner_context"
362 | ```
363 |
364 | ## Multiple inner contexts
365 |
366 |
367 | `Symbiont::Executor` allows you to work with multiple inner contexts (can receive a set of objects instead of the one main object).
368 | Each object will be used as an inner context in order they are passed.
369 | The method will be addressed to the object that responds first (according to a chosen delegation order).
370 |
371 | ```ruby
372 | # Usage:
373 |
374 | Symbiont::Executor.evaluate(object_a, object_b, context_direction: Symbiont::IOK, &closure)
375 | Symbiont::Executor.evaluate_private(object_a, object_b, context_direction: Symbiont::IOK, &closure)
376 | Symbiont::Executor.publc_method(:method_name, object_a, object_b, context_direction: Symbiont::IOK, &closure)
377 | Symbiont::Executor.private_method(:method_name, object_a, object_b, context_direction: Symbiont::IOK, &closure)
378 |
379 | # Example
380 |
381 | object_a.info # => "object_info"
382 | object_b.data # => "object_data"
383 |
384 | closure = proc { "#{info} #{data}" }
385 |
386 | Symbiont::Executor.evaluate(object_a, object_b, &closure) # => "object_info object_data"
387 | Symbiont::Executor.public_method(:data, object_a, object_b, &closure).call # => "object_data"
388 | Symbiont::Executor.public_method(:info, object_a, object_b, &closure).call # => "object_info"
389 | ```
390 |
391 | ## Isolator - proc object isolation layer for delayed invocations
392 |
393 | `Symbiont::Isolator` is a special object that wraps your proc object from any place and provides
394 | an ability to invoke this proc object lazily inside an any series of contexts.
395 | All `Symbiont::Executor` features are supported (by the way, `Symbiont::Executor`
396 | uses `Symbiont::Isolator` under the hood).
397 |
398 | ```ruby
399 | # Usage:
400 |
401 | # with default direction (Symbiont::IOK)
402 | isolator = Symbiont::Isolator.new { call_any_method }
403 |
404 | # with custom direction
405 | isolator = Symbiont::Isolator.new(default_direction: Symbiont::KIO) { call_any_method }
406 |
407 | # invocation
408 | isolator.evaluate(object_a, object_b) # use default direction defined on instantiation
409 | isolator.evaluate(object_a, object_b, direction: Symbiont::KOI) # use custom direction
410 | # same for #.evaluate_private
411 |
412 | # getting a method object
413 | isolator.public_method(:call_any_method, object_a, object_b) # use default direction defined on instantiation
414 | isolator.public_method(:call_any_method, object_a, object_b, direction: Symbiont::KIO) # use custom direction
415 | isolator.private_method(...)
416 | # same for #.private_method
417 | ```
418 |
419 | # Roadmap
420 |
421 | - support for toplevel context (`TOPLEVEL_BINDING`);
422 | - `fiber teleports`;
423 | - official support for **Ruby@3**;
424 |
425 | # Build
426 |
427 | ```shell
428 | bundle exec rake rubocop
429 | bundle exec rake yardoc
430 | bundle exec rake rspec
431 | ```
432 |
433 | # Contributing
434 |
435 | - Fork it ( https://github.com/0exp/symbiont-ruby/fork )
436 | - Create your feature branch (`git checkout -b my-new-feature`)
437 | - Commit your changes (`git commit -am 'Add some feature'`)
438 | - Push to the branch (`git push origin my-new-feature`)
439 | - Create new Pull Request
440 |
441 | # License
442 |
443 | Released under MIT License.
444 |
445 | # Authors
446 |
447 | [Logo](https://github.com/0exp/symbiont-ruby/tree/master/logo) was created by **Viktoria Izmaylova** (my special thanks ^_^).
448 |
449 | Project was created by **Rustam Ibragimov**.
450 |
451 |
--------------------------------------------------------------------------------