├── 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] 15 | # A set of objects that should be used as the main context series for method resolving 16 | # algorithm. 17 | # @param context_direction [Array] 18 | # An array of symbols that represents the direction of contexts. Possible values: 19 | # 20 | # - Symbiont::IOK 21 | # - Symbiont::OIK 22 | # - Symbiont::OKI 23 | # - Symbiont::IKO 24 | # - Symbiont::KOI 25 | # - Symbiont::KIO 26 | # @param closure [Proc] 27 | # Proc object that will be evaluated in many contexts: initial, outer and kernel. 28 | # @return void 29 | # 30 | # @see Symbiont::Trigger#__evaluate__ 31 | # 32 | # @api public 33 | # @since 0.1.0 34 | def evaluate(*required_contexts, context_direction: Trigger::IOK, &closure) 35 | Isolator.new(default_direction: context_direction, &closure) 36 | .evaluate(*required_contexts) 37 | end 38 | 39 | # Starts execution of a proc object in the context of the passed object with the selected 40 | # direction of method dispatching. Delegates execution to a private trigger. 41 | # 42 | # @param required_contexts [Array] 43 | # A set of objects that should be used as the main context series for method resolving 44 | # algorithm. 45 | # @param context_direction [Array] 46 | # An array of symbols that represents the direction of contexts. Possible values: 47 | # 48 | # - Symbiont::IOK 49 | # - Symbiont::OIK 50 | # - Symbiont::OKI 51 | # - Symbiont::IKO 52 | # - Symbiont::KOI 53 | # - Symbiont::KIO 54 | # @param closure [Proc] 55 | # Proc object that will be evaluated in many contexts: initial, outer and kernel. 56 | # @return void 57 | # 58 | # @see Symbiont::Trigger#__evaluate__ 59 | # 60 | # @api public 61 | # @since 0.1.0 62 | def evaluate_private(*required_contexts, context_direction: Trigger::IOK, &closure) 63 | Isolator.new(default_direction: context_direction, &closure) 64 | .evaluate_private(*required_contexts) 65 | end 66 | 67 | # Gets the method object taken from the context that can respond to it. 68 | # Considers only public methods. 69 | # 70 | # @param method_name [Symbol,String] A name of required method. 71 | # @param required_contexts [Array] 72 | # A set of objects that should be used as the main context series for method resolving 73 | # algorithm. 74 | # An array of symbols that represents the direction of contexts. Possible values: 75 | # 76 | # - Symbiont::IOK 77 | # - Symbiont::OIK 78 | # - Symbiont::OKI 79 | # - Symbiont::IKO 80 | # - Symbiont::KOI 81 | # - Symbiont::KIO 82 | # @param closure [Proc] Proc object that will be used as outer-context for method resolution. 83 | # @return [Method] 84 | # 85 | # @see Symbiont::PublicTrigger 86 | # @see Symbiont::Trigger#method 87 | # 88 | # @api public 89 | # @since 0.1.0 90 | def public_method(method_name, *required_contexts, context_direction: Trigger::IOK, &closure) 91 | Isolator.new(default_direction: context_direction, &closure) 92 | .public_method(method_name, *required_contexts) 93 | end 94 | 95 | # Gets the method object taken from the context that can respond to it. 96 | # Considers private methods and public methods. 97 | # 98 | # @param method_name [Symbol,String] A name of required method. 99 | # @param required_contexts [Array] 100 | # A set of objects that should be used as the main context series for method resolving 101 | # algorithm. 102 | # An array of symbols that represents the direction of contexts. Possible values: 103 | # 104 | # - Symbiont::IOK 105 | # - Symbiont::OIK 106 | # - Symbiont::OKI 107 | # - Symbiont::IKO 108 | # - Symbiont::KOI 109 | # - Symbiont::KIO 110 | # @param closure [Proc] Proc object that will be used as outer-context for method resolution. 111 | # @return [Method] 112 | # 113 | # @see Symbiont::PrivateTrigger 114 | # @see Symbiont::Trigger#method 115 | # 116 | # @api public 117 | # @since 0.1.0 118 | def private_method(method_name, *required_contexts, context_direction: Trigger::IOK, &closure) 119 | Isolator.new(default_direction: context_direction, &closure) 120 | .private_method(method_name, *required_contexts) 121 | end 122 | end 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /lib/symbiont/isolator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Symbiont 4 | # Special object that wraps your proc object from any place and provides 5 | # an ability to invoke this proc object lazily inside an any series of contexts. 6 | # 7 | # @api public 8 | # @since 0.3.0 9 | class Isolator 10 | # Is raised when closure is not provided. 11 | # 12 | # @see #initialize 13 | # 14 | # @api public 15 | # @since 0.3.0 16 | UnprovidedClosureAttributeError = Class.new(ArgumentError) 17 | 18 | # Proc object that will be evaluated in many contexts: initial, outer and kernel. 19 | # Will be used as an outer-context for the method resolution. 20 | # 21 | # @return [Proc] 22 | # 23 | # @api public 24 | # @since 0.3.0 25 | attr_reader :closure 26 | 27 | # An array of symbols that represents the direction of contexts. Used by default. 28 | # 29 | # @return [Array] 30 | # 31 | # @api public 32 | # @since 0.3.0 33 | attr_reader :default_direction 34 | 35 | # Instantiates isolator object with corresponding default direction and closure. 36 | # 37 | # @option default_direction [Array] 38 | # An array of symbols that represents the direction of contexts which is used as default 39 | # context direction. Symbiont::Trigger::IOK is chosen by default. 40 | # @param closure [Proc] 41 | # Proc object that will be evaluated in many contexts: initial, outer and kernel. 42 | # Will be used as an outer-context for the method resolution. 43 | # 44 | # @api public 45 | # @since 0.3.0 46 | def initialize(default_direction: Trigger::IOK, &closure) 47 | raise UnprovidedClosureAttributeError, 'You should provide a closure' unless block_given? 48 | 49 | @default_direction = default_direction 50 | @closure = closure 51 | end 52 | 53 | # Starts execution of a proc object in the context of the passed object with the selected 54 | # direction of method dispatching. Delegates execution to a public trigger. 55 | # 56 | # @param required_contexts [Array] 57 | # A set of objects that should be used as the main context series for method resolving 58 | # algorithm. 59 | # @param direction [Array] 60 | # An array of symbols that represents the direction of contexts. 61 | # @return [void] 62 | # 63 | # @see Symbiont::Trigger#__evaluate__ 64 | # @see Symbiont::PublicTrigger 65 | # 66 | # @api public 67 | # @since 0.3.0 68 | def evaluate(*required_contexts, direction: default_direction) 69 | public_trigger(*required_contexts, direction: direction).__evaluate__ 70 | end 71 | 72 | # Starts execution of a proc object in the context of the passed object with the selected 73 | # direction of method dispatching. Delegates execution to a private trigger. 74 | # 75 | # @param required_contexts [Array] 76 | # A set of objects that should be used as the main context series for method resolving 77 | # algorithm. 78 | # @param direction [Array] 79 | # An array of symbols that represents the direction of contexts. 80 | # @return [void] 81 | # 82 | # @see Symbiont::Trigger#__evaluate__ 83 | # @see Symbiont::PrivateTrigger 84 | # 85 | # @api public 86 | # @since 0.3.0 87 | def evaluate_private(*required_contexts, direction: default_direction) 88 | private_trigger(*required_contexts, direction: direction).__evaluate__ 89 | end 90 | 91 | # Gets the method object taken from the context that can respond to it. 92 | # Considers only public methods. 93 | # 94 | # @param method_name [Symbol,String] A name of required method. 95 | # @param required_contexts [Array] 96 | # A set of objects that should be used as the main context series for method resolving 97 | # algorithm. 98 | # @param direction [Array] 99 | # An array of symbols that represents the direction of contexts. 100 | # @return [Method] 101 | # 102 | # @see Symbiont::PublicTrigger 103 | # @see Symbiont::Trigger#method 104 | # 105 | # @api public 106 | # @since 0.3.0 107 | def public_method(method_name, *required_contexts, direction: default_direction) 108 | public_trigger(*required_contexts, direction: direction).method(method_name) 109 | end 110 | 111 | # Gets the method object taken from the context that can respond to it. 112 | # Considers private methods and public methods. 113 | # 114 | # @param method_name [Symbol,String] A name of required method. 115 | # @param required_contexts [Array] 116 | # A set of objects that should be used as the main context series for method resolving 117 | # algorithm. 118 | # @param direction [Array] 119 | # An array of symbols that represents the direction of contexts. 120 | # @return [Method] 121 | # 122 | # @see Symbiont::PrivateTrigger 123 | # @see Symbiont::Trigger#method 124 | # 125 | # @api public 126 | # @since 0.3.0 127 | def private_method(method_name, *required_contexts, direction: default_direction) 128 | private_trigger(*required_contexts, direction: direction).method(method_name) 129 | end 130 | 131 | private 132 | 133 | # Factory method that instantiates a public trigger with the desired execution context, 134 | # the direction of method dispatching and the closure that needs to be performed. 135 | # 136 | # @param required_contexts [Array] 137 | # A set of objects that should be used as the main context series for method resolving 138 | # algorithm. 139 | # @param direction [Array] 140 | # An array of symbols that represents the direction of contexts. Possible values: 141 | # 142 | # - Symbiont::IOK 143 | # - Symbiont::OIK 144 | # - Symbiont::OKI 145 | # - Symbiont::IKO 146 | # - Symbiont::KOI 147 | # - Symbiont::KIO 148 | # @return [Symbiont::PublicTrigger] 149 | # 150 | # @see Symbiont::PublicTrigger 151 | # @see Symbiont::Trigger 152 | # 153 | # @api private 154 | # @since 0.3.0 155 | def public_trigger(*required_contexts, direction: default_direction) 156 | PublicTrigger.new(*required_contexts, context_direction: direction, &closure) 157 | end 158 | 159 | # Factory method that instantiates a private trigger with the desired execution context, 160 | # the direction of method dispatching and the closure that needs to be performed. 161 | # 162 | # @param required_contexts [Array] 163 | # A set of objects that should be used as the main context series for method resolving 164 | # algorithm. 165 | # @param direction [Array] 166 | # An array of symbols that represents the direction of contexts. Possible values: 167 | # 168 | # - Symbiont::IOK 169 | # - Symbiont::OIK 170 | # - Symbiont::OKI 171 | # - Symbiont::IKO 172 | # - Symbiont::KOI 173 | # - Symbiont::KIO 174 | # @return [Symbiont::PrivateTrigger] 175 | # 176 | # @see Symbiont::PrivateTrigger 177 | # @see Symbiont::Trigger 178 | # 179 | # @api private 180 | # @since 0.3.0 181 | def private_trigger(*required_contexts, direction: default_direction) 182 | PrivateTrigger.new(*required_contexts, context_direction: direction, &closure) 183 | end 184 | end 185 | end 186 | -------------------------------------------------------------------------------- /logo/symbiont_logo_hexagon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 32 | 33 | 66 | 67 | 68 | 71 | 72 | 74 | 75 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /logo/symbiont_logo_circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 33 | 34 | 68 | 69 | 70 | 71 | 72 | 73 | 75 | 76 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 102 | 103 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /logo/symbiont_logo_circle_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 37 | 38 | 72 | 73 | 74 | 75 | 76 | 77 | 79 | 80 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 106 | 107 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /lib/symbiont/trigger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # A class that controls the logic of closure execution in the context of other objects. 4 | # Responsible for dispatching the methods which should be executed in a certain context. 5 | # Delegation variations depends on the order of contexts. 6 | # 7 | # Trigger supports 3 contexts: 8 | # 9 | # * closure context; 10 | # * passed object's context (or context of the each passed object); 11 | # * global ::Kernel context. 12 | # 13 | # If no context is able to respond to the required method - ContextNoMethodError exception is raised 14 | # (ContextNoMethodError inherits from NoMethodError). 15 | # 16 | # @api private 17 | # @since 0.1.0 18 | class Symbiont::Trigger < BasicObject 19 | # Indicates the direction of context method resolving algorithm. 20 | # Direction: initial context => outer context => kernel context. 21 | # 22 | # @return [Array] 23 | # 24 | # @api public 25 | # @since 0.1.0 26 | IOK = %i[__inner_contexts__ __outer_context__ __kernel_context__].freeze 27 | 28 | # Indicates the direction of context method resolving algorithm. 29 | # Direction: outer context => initial contexts => kernel context. 30 | # 31 | # @return [Array] 32 | # 33 | # @api public 34 | # @since 0.1.0 35 | OIK = %i[__outer_context__ __inner_contexts__ __kernel_context__].freeze 36 | 37 | # Indicates the direction of context method resolving algorithm. 38 | # Direction: outer context => kernel context => initial contexts. 39 | # 40 | # @return [Array] 41 | # 42 | # @api public 43 | # @since 0.1.0 44 | OKI = %i[__outer_context__ __kernel_context__ __inner_contexts__].freeze 45 | 46 | # Indicates the direction of context method resolving algorithm. 47 | # Direction: initial contexts => kernel context => outer context. 48 | # 49 | # @return [Array] 50 | # 51 | # @api public 52 | # @since 0.1.0 53 | IKO = %i[__inner_contexts__ __kernel_context__ __outer_context__].freeze 54 | 55 | # Indicates the direction of context method resolving algorithm. 56 | # Direction: kernel context => outer context => initial contexts. 57 | # 58 | # @return [Array] 59 | # 60 | # @api public 61 | # @since 0.1.0 62 | KOI = %i[__kernel_context__ __outer_context__ __inner_contexts__].freeze 63 | 64 | # Indicates the direction of context method resolving algorithm. 65 | # Direction: kernel context => initial contexts => outer context. 66 | # 67 | # @return [Array] 68 | # 69 | # api public 70 | # @since 0.1.0 71 | KIO = %i[__kernel_context__ __inner_contexts__ __outer_context__].freeze 72 | 73 | # Is raised when chosen direction (__context_direction__ instance attribute) is not supported 74 | # by a trigger. Supports only: OIK, OKI, IOK, IKO, KOI, KIO. 75 | # 76 | # @api public 77 | # @since 0.1.0 78 | IncompatibleContextDirectionError = ::Class.new(::ArgumentError) 79 | 80 | # Is raised when closure isnt passed. 81 | # 82 | # @api public 83 | # @since 0.2.0 84 | UnprovidedClosureAttributeError = ::Class.new(::ArgumentError) 85 | 86 | # Is raised when no one is able to respond to the required method. 87 | # 88 | # @see #__actual_context__ 89 | # 90 | # @api public 91 | # @since 0.1.0 92 | ContextNoMethodError = ::Class.new(::NoMethodError) 93 | 94 | # Returns a set of objects that should be used as the main context series for 95 | # context method resolving algorithm. The order of object selection depends on 96 | # their order in a set. 97 | # 98 | # @return [Array] 99 | # 100 | # @since 0.1.0 101 | attr_reader :__inner_contexts__ 102 | 103 | # Returns a binding object of corresponding closure (see __closure__). 104 | # Used as an outer context for context method resolving algorithm. 105 | # 106 | # @return [Object] 107 | # 108 | # @api private 109 | # @since 0.1.0 110 | attr_reader :__outer_context__ 111 | 112 | # Returns Kernel object that will be used as Kernel context for 113 | # context method resolving algorithm. 114 | # 115 | # @return [::Kernel] 116 | # 117 | # @api private 118 | # @since 0.1.0 119 | attr_reader :__kernel_context__ 120 | 121 | # Returns proc object that will be triggered in many contexts: initial, outer and kernel. 122 | # 123 | # @return [Proc] 124 | # 125 | # @api private 126 | # @since 0.1.0 127 | attr_reader :__closure__ 128 | 129 | # Returns an array of symbols that represents the direction of contexts. 130 | # that represents an access method to each of them. 131 | # 132 | # @return [Array] 133 | # 134 | # @api private 135 | # @since 0.1.0 136 | attr_reader :__context_direction__ 137 | 138 | # Instantiates trigger object with corresponding initial contexts, closure and context resolving 139 | # direction. 140 | # 141 | # @param initial_contexts [Array] 142 | # A set of main context objects which should be used for instance_eval on. 143 | # An order of object selection depends on oredr which they are passed. 144 | # @param closure [Proc] 145 | # closure that will be executed in a set of contexts (initial => outer => kernel by default). 146 | # An actual context (#__actual_context__) will be passed to a closure as an attribute. 147 | # @raise UnprovidedClosureAttributeError 148 | # Raises when closure attribte isnt passed. 149 | # @raise IncompatibleContextDirectionError 150 | # Is raised when chosen direction is not supported by a trigger. 151 | # Supports only OIK, OKI, IOK, IKO, KOI, KIO (see corresponding constant value above). 152 | # 153 | # @api private 154 | # @since 0.1.0 155 | def initialize(*initial_contexts, context_direction: IOK, &closure) 156 | # :nocov: 157 | unless ::Kernel.block_given? 158 | ::Kernel.raise(UnprovidedClosureAttributeError, 'block attribute should be provided') 159 | end 160 | # :nocov: 161 | 162 | # rubocop:disable Layout/SpaceAroundKeyword 163 | unless(context_direction == IOK || context_direction == OIK || context_direction == OKI || 164 | context_direction == IKO || context_direction == KOI || context_direction == KIO) 165 | ::Kernel.raise( 166 | IncompatibleContextDirectionError, 167 | 'Incompatible context direction attribute. ' \ 168 | 'You should use one of this: OIK, OKI, IOK, IKO, KOI, KIO.' 169 | ) 170 | end 171 | # rubocop:enable Layout/SpaceAroundKeyword 172 | 173 | @__closure__ = closure 174 | @__context_direction__ = context_direction 175 | @__inner_contexts__ = initial_contexts 176 | @__outer_context__ = ::Kernel.eval('self', closure.binding) 177 | @__kernel_context__ = ::Kernel 178 | end 179 | 180 | # Triggers a closure in multiple contexts. 181 | # 182 | # @return void 183 | # 184 | # @see #method_missing 185 | # 186 | # @api private 187 | # @since 0.1.0 188 | def __evaluate__ 189 | instance_eval(&__closure__) 190 | end 191 | 192 | # Returns a collection of the all contexts sorted by chosen direction. 193 | # Represents ordered single-dimentional array of objects (contexts). 194 | # 195 | # @return [Array] 196 | # 197 | # @see #__context_direction__ 198 | # 199 | # @api private 200 | # @since 0.1.0 201 | def __directed_contexts__ 202 | __context_direction__.map { |direction| __send__(direction) }.flatten 203 | end 204 | 205 | # Returns the first context that is able to respond to the required method. 206 | # The context is chosen in the context direction order (see #__context_direction__). 207 | # Raises NoMethodError excepition when no one of the contexts are able to respond to 208 | # the required method. 209 | # Basicaly, abstract implementation raises NoMethodError. 210 | # 211 | # @param method_name [Symbol,String] Method that a context should respond to. 212 | # @raise ContextNoMethodError 213 | # 214 | # @see #__context_direction__ 215 | # 216 | # @api private 217 | # @since 0.1.0 218 | def __actual_context__(method_name) 219 | ::Kernel.raise ContextNoMethodError, "No one is able to respond to #{method_name}" 220 | end 221 | 222 | # Delegates method invocation to the corresponding actual context. 223 | # 224 | # @param method_name [String,Symbol] Method name 225 | # @param arguments [Mixed] Method arguments 226 | # @param block [Proc] Block 227 | # @raise ContextNoMethodError 228 | # Is rased when no one of the contexts are able to respond tothe required method. 229 | # @return void 230 | # 231 | # @see #__actual_context__ 232 | # 233 | # @api private 234 | # @since 0.1.0 235 | def method_missing(method_name, *arguments, **options, &block) 236 | __actual_context__(method_name).__send__(method_name, *arguments, **options, &block) 237 | end 238 | 239 | # Checks that the actual context is able to respond to a required method. 240 | # 241 | # @param method_name [String,Symbol] Method name 242 | # @param _include_private [Boolean] Include private methods 243 | # @raise NoMethodError 244 | # Is raised when no one of the contexts are able to respond to the required method. 245 | # @return [Boolean] Is the actual context able to respond to the required method. 246 | # 247 | # @see #method_missing 248 | # @see #__actual_context__ 249 | # 250 | # @api private 251 | # @since 0.1.0 252 | # :nocov: 253 | def respond_to_missing?(method_name, _include_private = false) 254 | !!__actual_context__(method_name) 255 | end 256 | # :nocov: 257 | 258 | # Should return a corresponding method object of the actual context. 259 | # 260 | # @param method_name [String,Symbol] Method name 261 | # @raise ContextNoMethodError 262 | # Is raised when no one of the contexts able to respond to the required method. 263 | # @return [Method] 264 | # 265 | # @see #method_missing 266 | # @see #respond_to_missing? 267 | # @see #__actual_context__ 268 | # 269 | # @see [Symbiont::PrivateTrigget#method] 270 | # @see [Symbiont::PublicTrigger#method] 271 | # 272 | # @api private 273 | # @since 0.1.0 274 | def method(method_name); end # :nocov: 275 | 276 | # Returns an internal singleton object of the passed object. 277 | # 278 | # @param object [Any] Extractable object 279 | # @return [Class] Singleton class of the passed object 280 | # 281 | # @api private 282 | # @sionce 0.5.0 283 | def __extract_singleton_class__(object) 284 | # NOTE: `<<` approach is used cuz BasicObject does not support #singleton_class method. 285 | class << object; self; end 286 | end 287 | end 288 | -------------------------------------------------------------------------------- /logo/symbiont_logo_hexagon_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 43 | 44 | 77 | 78 | 79 | 82 | 83 | 85 | 86 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 112 | 113 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /spec/features/symbiont_like_behaviour_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # TODO: refactor with shared examples / shared contexts 4 | 5 | describe 'Symbiont: symbiont-like behaviour (by Symbiont::Context mixin)' do 6 | let(:symbiont_directions) do 7 | [ 8 | Symbiont::IKO, 9 | Symbiont::IOK, 10 | Symbiont::KIO, 11 | Symbiont::KOI, 12 | Symbiont::OIK, 13 | Symbiont::OKI 14 | ] 15 | end 16 | 17 | shared_examples 'symbiont direction option' do 18 | specify 'instance#evaluate fails on incompatible direction option' do 19 | expect do 20 | instance_object.evaluate(gen_symb) {} 21 | end.to raise_error(Symbiont::Trigger::IncompatibleContextDirectionError) 22 | end 23 | 24 | specify 'instance#evaluate_private fails on incompatible direction option' do 25 | expect do 26 | instance_object.evaluate(gen_symb) {} 27 | end.to raise_error(Symbiont::Trigger::IncompatibleContextDirectionError) 28 | end 29 | 30 | specify 'module#evaluate fails on incompatible direction option' do 31 | expect do 32 | instance_object.evaluate(gen_symb) {} 33 | end.to raise_error(Symbiont::Trigger::IncompatibleContextDirectionError) 34 | end 35 | 36 | specify 'module#evaluate_private fails on incompatible direction option' do 37 | expect do 38 | instance_object.evaluate(gen_symb) {} 39 | end.to raise_error(Symbiont::Trigger::IncompatibleContextDirectionError) 40 | end 41 | 42 | specify 'instance#evaluate provides an ability to use any direction' do 43 | symbiont_directions.each do |symbiont_direction| 44 | expect(Symbiont::Executor).to( 45 | receive(:evaluate).with( 46 | instance_object, 47 | context_direction: symbiont_direction 48 | ) 49 | ) 50 | 51 | instance_object.evaluate(symbiont_direction) 52 | end 53 | end 54 | 55 | specify 'instance#evaluate_private provides an ability to use any direction' do 56 | symbiont_directions.each do |symbiont_direction| 57 | expect(Symbiont::Executor).to( 58 | receive(:evaluate_private).with( 59 | instance_object, 60 | context_direction: symbiont_direction 61 | ) 62 | ) 63 | 64 | instance_object.evaluate_private(symbiont_direction) 65 | end 66 | end 67 | 68 | specify 'module#evaluate provides an ability to use any direction' do 69 | symbiont_directions.each do |symbiont_direction| 70 | expect(Symbiont::Executor).to( 71 | receive(:evaluate).with( 72 | module_object, 73 | context_direction: symbiont_direction 74 | ) 75 | ) 76 | 77 | module_object.evaluate(symbiont_direction) 78 | end 79 | end 80 | 81 | specify 'module#evaluate_private provides an ability to use any direction' do 82 | symbiont_directions.each do |symbiont_direction| 83 | expect(Symbiont::Executor).to( 84 | receive(:evaluate_private).with( 85 | module_object, 86 | context_direction: symbiont_direction 87 | ) 88 | ) 89 | 90 | module_object.evaluate_private(symbiont_direction) 91 | end 92 | end 93 | end 94 | 95 | shared_examples 'symbiont context mixin' do |symbiont_direction| 96 | let(:instance_object) { Class.new { include Symbiont::Context(symbiont_direction) }.new } 97 | let(:module_object) { Class.new { extend Symbiont::Context(symbiont_direction) } } 98 | 99 | include_examples 'symbiont direction option' 100 | 101 | specify "instance#evaluate => invokes public evaluator " \ 102 | "with #{symbiont_direction} direction inside the self" do 103 | expect(Symbiont::Executor).to( 104 | receive(:evaluate).with( 105 | instance_object, 106 | context_direction: symbiont_direction 107 | ) 108 | ) 109 | 110 | instance_object.evaluate {} 111 | end 112 | 113 | specify 'instance#evaluate => fails when proc isnt passed' do 114 | expect do 115 | instance_object.evaluate 116 | end.to raise_error(Symbiont::Isolator::UnprovidedClosureAttributeError) 117 | end 118 | 119 | # rubocop:disable Layout/EmptyLinesAroundBlockBody 120 | specify "instance#evaluate_private => invokes private evaluator " \ 121 | "with #{symbiont_direction} direction inside the self" do 122 | 123 | expect(Symbiont::Executor).to( 124 | receive(:evaluate_private).with( 125 | instance_object, 126 | context_direction: symbiont_direction 127 | ) 128 | ) 129 | 130 | instance_object.evaluate_private {} 131 | end 132 | # rubocop:enable Layout/EmptyLinesAroundBlockBody 133 | 134 | specify 'instnace#evaluate_private => fails when proc isnt passed' do 135 | expect do 136 | instance_object.evaluate_private 137 | end.to raise_error(Symbiont::Isolator::UnprovidedClosureAttributeError) 138 | end 139 | 140 | # rubocop:disable Layout/EmptyLinesAroundBlockBody 141 | specify "module#evaluate => invokes public evaluator " \ 142 | "with #{symbiont_direction} direction inside the self" do 143 | 144 | expect(Symbiont::Executor).to( 145 | receive(:evaluate).with( 146 | module_object, 147 | context_direction: symbiont_direction 148 | ) 149 | ) 150 | 151 | module_object.evaluate {} 152 | end 153 | # rubocop:enable Layout/EmptyLinesAroundBlockBody 154 | 155 | specify 'module#evaluate => fails when proc isnt passed' do 156 | expect do 157 | module_object.evaluate 158 | end.to raise_error(Symbiont::Isolator::UnprovidedClosureAttributeError) 159 | end 160 | 161 | # rubocop:disable Layout/EmptyLinesAroundBlockBody 162 | specify "module#evaluate_private => invokes private evaluator " \ 163 | "with #{symbiont_direction} direction inside the self" do 164 | 165 | expect(Symbiont::Executor).to( 166 | receive(:evaluate).with( 167 | module_object, 168 | context_direction: symbiont_direction 169 | ) 170 | ) 171 | 172 | module_object.evaluate {} 173 | end 174 | # rubocop:enable Layout/EmptyLinesAroundBlockBody 175 | 176 | specify 'module#evaluate_private => fails when proc isnt passed' do 177 | expect do 178 | module_object.evaluate_private 179 | end.to raise_error(Symbiont::Isolator::UnprovidedClosureAttributeError) 180 | end 181 | 182 | specify "module#public_method => resolves corresponding method " \ 183 | "with #{symbiont_direction} direction" do 184 | corresponding_method = gen_symb 185 | 186 | expect(Symbiont::Executor).to( 187 | receive(:public_method).with( 188 | module_object, 189 | corresponding_method, 190 | context_direction: symbiont_direction 191 | ) 192 | ) 193 | 194 | module_object.public_method(corresponding_method, symbiont_direction) {} 195 | end 196 | 197 | specify "module#private_method => resolves corresponding method " \ 198 | "with #{symbiont_direction} direction" do 199 | corresponding_method = gen_symb 200 | 201 | expect(Symbiont::Executor).to( 202 | receive(:private_method).with( 203 | module_object, 204 | corresponding_method, 205 | context_direction: symbiont_direction 206 | ) 207 | ) 208 | 209 | module_object.private_method(corresponding_method, symbiont_direction) {} 210 | end 211 | 212 | specify "instance#public_method => resolves corresponding method " \ 213 | "with #{symbiont_direction} direction" do 214 | corresponding_method = gen_symb 215 | 216 | expect(Symbiont::Executor).to( 217 | receive(:public_method).with( 218 | instance_object, 219 | corresponding_method, 220 | context_direction: symbiont_direction 221 | ) 222 | ) 223 | 224 | instance_object.public_method(corresponding_method, symbiont_direction) {} 225 | end 226 | 227 | specify "instance#private_method => resolves corresponding method " \ 228 | "with #{symbiont_direction} direction" do 229 | corresponding_method = gen_symb 230 | 231 | expect(Symbiont::Executor).to( 232 | receive(:private_method).with( 233 | instance_object, 234 | corresponding_method, 235 | context_direction: symbiont_direction 236 | ) 237 | ) 238 | 239 | instance_object.private_method(corresponding_method, symbiont_direction) {} 240 | end 241 | end 242 | 243 | describe 'default context direction (include Context mixin without attributes)' do 244 | let(:instance_object) { Class.new { include Symbiont::Context }.new } 245 | let(:module_object) { Class.new { extend Symbiont::Context } } 246 | 247 | include_examples 'symbiont direction option' 248 | 249 | specify 'isntance#evaluate without direction uses Symbiont::IOK by default' do 250 | expect(Symbiont::Executor).to receive(:evaluate).with(instance_object, context_direction: Symbiont::IOK) 251 | instance_object.evaluate {} 252 | end 253 | 254 | specify 'instance#evaluate_private without direction uses Symbiont::IOK by default' do 255 | expect(Symbiont::Executor).to receive(:evaluate_private).with(instance_object, context_direction: Symbiont::IOK) 256 | instance_object.evaluate_private {} 257 | end 258 | 259 | specify 'module#evaluate without direction uses Symbiont::IOK by default' do 260 | expect(Symbiont::Executor).to receive(:evaluate).with(module_object, context_direction: Symbiont::IOK) 261 | module_object.evaluate {} 262 | end 263 | 264 | specify 'module#evaluate_private without direction uses Symbiont::IOK by default' do 265 | expect(Symbiont::Executor).to receive(:evaluate_private).with(module_object, context_direction: Symbiont::IOK) 266 | module_object.evaluate_private {} 267 | end 268 | 269 | specify 'module#evaluate fails when proc isnt possed' do 270 | expect do 271 | instance_object.evaluate 272 | end.to raise_error(Symbiont::Isolator::UnprovidedClosureAttributeError) 273 | end 274 | 275 | specify 'module#evaluate_private fails when proc isnt possed' do 276 | expect do 277 | instance_object.evaluate_private 278 | end.to raise_error(Symbiont::Isolator::UnprovidedClosureAttributeError) 279 | end 280 | 281 | specify 'instance#evaluate fails when proc isnt possed' do 282 | expect do 283 | module_object.evaluate 284 | end.to raise_error(Symbiont::Isolator::UnprovidedClosureAttributeError) 285 | end 286 | 287 | specify 'instance#evaluate_private fails when proc isnt possed' do 288 | expect do 289 | module_object.evaluate_private 290 | end.to raise_error(Symbiont::Isolator::UnprovidedClosureAttributeError) 291 | end 292 | end 293 | 294 | it_behaves_like 'symbiont context mixin', Symbiont::IKO 295 | it_behaves_like 'symbiont context mixin', Symbiont::IOK 296 | it_behaves_like 'symbiont context mixin', Symbiont::KIO 297 | it_behaves_like 'symbiont context mixin', Symbiont::KOI 298 | it_behaves_like 'symbiont context mixin', Symbiont::OIK 299 | it_behaves_like 'symbiont context mixin', Symbiont::OKI 300 | end 301 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

3 |

4 | Symbiont
5 | 6 |

7 |

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 | --------------------------------------------------------------------------------