use_context 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fefdb2f5210efd1e2cc56fe41dc465cb61aa711fc658f29d606221335dfcaa51
4
+ data.tar.gz: 4e429a7afe8d22962dc58109379fbf85cc8135d1713ed1d30b25ce7f16fa1222
5
+ SHA512:
6
+ metadata.gz: c05082dfc9a79b6f92504d2f1a5928abae3b3fb7d2c38fba6c8c617031d80e0afe5d93dd21c4fe2d07e8fef00420c68fc6e3da824813e762f6f8cae439ce1ad1
7
+ data.tar.gz: fe5474e402f9bf88b3d1f9738c46b2cb07884adee0cfd9314c5a6273a959a93169375b7097a068b867f30bf421e86af447393ec43756a6b2a0e3cab583616358
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ # 1.0.0
2
+ * Birthday!
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem "rake"
7
+ gem "debug"
8
+ end
9
+
10
+ group :test do
11
+ gem "minitest"
12
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Cameron Dutro
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,102 @@
1
+ ## use_context
2
+
3
+ ![Unit Tests](https://github.com/camertron/use_context/actions/workflows/unit_tests.yml/badge.svg?branch=main)
4
+
5
+ ## What is this thing?
6
+
7
+ use_context is a tool for providing block-level context to Ruby programs in a thread-safe and fiber-safe way. Internally it leverages fiber-local storage. It can be used to provide data, etc to parts of your program that might be difficult or inconvenient to reach with eg. normal argument passing.
8
+
9
+ use_context was inspired by this use-case, and originally proposed as an addition to ViewComponent: https://github.com/ViewComponent/view_component/discussions/2327
10
+
11
+ ## Usage
12
+
13
+ There are three ways to use this gem. As some folks are understandably uncomfortable using monkeypatches - especially ones applied to core classes and modules - you are free to choose the one that fits your preferences.
14
+
15
+ ### As a monkeypatch
16
+
17
+ Add the following to your program or application somewhere:
18
+
19
+ ```ruby
20
+ require "use_context/ext/kernel"
21
+ ```
22
+
23
+ This will add two methods to `Kernel` so they are available everywhere: `provide_context`, and `use_context`. Contexts consist of a name and a hash of key/value pairs.
24
+
25
+ ```ruby
26
+ def speak
27
+ use_context(:welcome) do |context|
28
+ puts context[:salutation]
29
+ end
30
+ end
31
+
32
+ provide_context(:welcome, { salutation: "Hello, world!" }) do
33
+ speak # prints "Hello, world!"
34
+ end
35
+ ```
36
+
37
+ Values are available in the current context only for the duration of the block passed to `provide_context`, and reset after the block returns.
38
+
39
+ ### As a refinement
40
+
41
+ Add the following to your program or application somewhere:
42
+
43
+ ```ruby
44
+ require "use_context/ext/kernel_refinement"
45
+ ```
46
+
47
+ The refinement can be enabled at the class, module, or file level, and adds the same two methods to `Kernel`. Refinements work differently than monkeypatching in that their changes are not globally applied and are only visible within the scope they are enabled in.
48
+
49
+ ```ruby
50
+ # Makes provide_context and use_context available on Kernel, but only within this file
51
+ using UseContext::KernelRefinement
52
+
53
+ provide_context(:welcome, { salutation: "Hello, world!" }) do
54
+ ...
55
+ end
56
+ ```
57
+
58
+ ### No magic
59
+
60
+ If you'd rather avoid modifying `Kernel` altogether, use_context can also be used via the `UseContext` constant.
61
+
62
+ Add the following to your program or application somewhere:
63
+
64
+ ```ruby
65
+ require "use_context"
66
+ ```
67
+
68
+ Then simply call `UseContext.provide_context` and `UseContext.use_context`:
69
+
70
+ ```ruby
71
+ UseContext.provide_context(:welcome, { salutation: "Hello, world!" }) do
72
+ UseContext.use_context(:welcome) do |context|
73
+ puts context[:salutation] # prints "Hello, world!"
74
+ end
75
+ end
76
+ ```
77
+
78
+ ## Overriding context
79
+
80
+ If a key already exists in the current context, it will be overridden, but only for the duration of the block:
81
+
82
+ ```ruby
83
+ provide_context(:welcome, { salutation: "Hello, world!" }) do
84
+ provide_context(:welcome, { salutation: "Hola, mundo!" }) do
85
+ speak # prints "Hola, mundo!"
86
+ end
87
+
88
+ speak # prints "Hello, world!"
89
+ end
90
+ ```
91
+
92
+ ## Running Tests
93
+
94
+ `bundle exec rake` should do the trick.
95
+
96
+ ## License
97
+
98
+ Licensed under the MIT license. See LICENSE for details.
99
+
100
+ ## Authors
101
+
102
+ * Cameron C. Dutro: http://github.com/camertron
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake/testtask"
4
+ require "rubygems/package_task"
5
+
6
+ Bundler::GemHelper.install_tasks
7
+
8
+ Rake::TestTask.new("test") do |t|
9
+ t.warning = false
10
+ t.libs << "test"
11
+ t.test_files = FileList[
12
+ "test/**/*_test.rb"
13
+ ]
14
+ end
15
+
16
+ task default: [:test]
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "use_context"
4
+
5
+ module Kernel
6
+ include UseContext::ContextMethods
7
+ extend UseContext::ContextMethods
8
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "use_context"
4
+
5
+ module UseContext
6
+ module KernelRefinement
7
+ refine Kernel do
8
+ def provide_context(...)
9
+ UseContext.provide_context(...)
10
+ end
11
+
12
+ def use_context(...)
13
+ UseContext.use_context(...)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UseContext
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module UseContext
6
+ Fiber.attr_accessor :uc_context
7
+
8
+ class << self
9
+ def current
10
+ Fiber.current
11
+ end
12
+
13
+ def context
14
+ current.uc_context ||= {}
15
+ end
16
+ end
17
+
18
+ class EmptyContext
19
+ include Singleton
20
+
21
+ def [](_key)
22
+ nil
23
+ end
24
+ end
25
+
26
+ class Context
27
+ def initialize(stack)
28
+ @stack = stack
29
+ end
30
+
31
+ def [](key)
32
+ @stack.reverse_each do |context_hash|
33
+ if context_hash.include?(key)
34
+ return context_hash[key]
35
+ end
36
+ end
37
+
38
+ nil
39
+ end
40
+ end
41
+
42
+ class ContextStack
43
+ attr_reader :context
44
+
45
+ def initialize
46
+ @stack = []
47
+ @context = Context.new(@stack)
48
+ end
49
+
50
+ def push(context_hash)
51
+ @stack << context_hash
52
+ end
53
+
54
+ def pop
55
+ @stack.pop
56
+ end
57
+ end
58
+
59
+ module ContextMethods
60
+ def provide_context(name, context_hash)
61
+ context = UseContext.context[name] ||= ContextStack.new
62
+ context.push(context_hash)
63
+ yield
64
+ ensure
65
+ context.pop if context
66
+ end
67
+
68
+ def use_context(name)
69
+ yield UseContext.context[name]&.context || EmptyContext.instance
70
+ end
71
+ end
72
+
73
+ extend ContextMethods
74
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ # Use the refinement here rather than the monkeypatch, since we want to test both altering Kernel _and_ not altering it.
6
+ # Depending on test order, applying the monkeypatch here could affect other tests and lead to false positives.
7
+ using UseContext::KernelRefinement
8
+
9
+ def __uc_provide_context_for_tests(&block)
10
+ provide_context(:test, { key: :top_level_value }, &block)
11
+ end
12
+
13
+ def __uc_use_context_for_tests
14
+ use_context(:test) do |context|
15
+ context[:key]
16
+ end
17
+ end
18
+
19
+ class KernelTest < Minitest::Test
20
+ class Provider
21
+ def self.provide(&block)
22
+ provide_context(:test, { key: :class_value }, &block)
23
+ end
24
+
25
+ def provide(&block)
26
+ provide_context(:test, { key: :instance_value }, &block)
27
+ end
28
+ end
29
+
30
+ class User
31
+ def self.use
32
+ use_context(:test) do |context|
33
+ context[:key]
34
+ end
35
+ end
36
+
37
+ def use
38
+ use_context(:test) do |context|
39
+ context[:key]
40
+ end
41
+ end
42
+ end
43
+
44
+ define_method(:provide_context_from_dynamically_defined_method) do |&block|
45
+ provide_context(:test, { key: :value_from_dynamically_defined_method }, &block)
46
+ end
47
+
48
+ define_method(:use_context_from_dynamically_defined_method) do
49
+ use_context(:test) do |context|
50
+ context[:key]
51
+ end
52
+ end
53
+
54
+ def test_provide_context
55
+ value = provide_context(:test, { key: :inline_value }) do
56
+ use_context(:test) do |context|
57
+ context[:key]
58
+ end
59
+ end
60
+
61
+ assert_equal :inline_value, value
62
+ end
63
+
64
+ def test_provide_context_from_top_level
65
+ value = __uc_provide_context_for_tests do
66
+ __uc_use_context_for_tests
67
+ end
68
+
69
+ assert_equal :top_level_value, value
70
+ end
71
+
72
+ def test_provide_context_from_dynamically_defined_method
73
+ value = provide_context_from_dynamically_defined_method do
74
+ use_context_from_dynamically_defined_method
75
+ end
76
+
77
+ assert_equal :value_from_dynamically_defined_method, value
78
+ end
79
+
80
+ def test_provide_context_from_instance
81
+ assert_equal :instance_value, Provider.new.provide { User.new.use }
82
+ end
83
+
84
+ def test_provide_context_from_class
85
+ assert_equal :class_value, Provider.provide { User.use }
86
+ end
87
+
88
+ def test_context_can_be_overridden
89
+ provide_context(:test, { setting1: :a, setting2: :a }) do
90
+ use_context(:test) do |context|
91
+ assert_equal :a, context[:setting1]
92
+ assert_equal :a, context[:setting2]
93
+
94
+ provide_context(:test, { setting1: :b }) do
95
+ use_context(:test) do |context|
96
+ assert_equal :b, context[:setting1]
97
+ assert_equal :a, context[:setting2]
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "use_context/ext/kernel_refinement"
4
+ require "minitest/autorun"
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class UseContextTest < Minitest::Test
6
+ def test_provide_context_from_uc_constant
7
+ value = UseContext.provide_context(:test, { key: :value_from_uc_constant }) do
8
+ UseContext.use_context(:test) do |context|
9
+ context[:key]
10
+ end
11
+ end
12
+
13
+ assert_equal :value_from_uc_constant, value
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
2
+ require 'use_context/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'use_context'
6
+ s.version = ::UseContext::VERSION
7
+ s.authors = ['Cameron Dutro']
8
+ s.email = ['camertron@gmail.com']
9
+ s.homepage = 'http://github.com/camertron/use_context'
10
+ s.description = s.summary = 'Easily provide block-scoped context values in your Ruby code.'
11
+ s.platform = Gem::Platform::RUBY
12
+
13
+ s.require_path = 'lib'
14
+
15
+ s.files = Dir['{lib,test}/**/*', 'Gemfile', 'LICENSE', 'CHANGELOG.md', 'README.md', 'Rakefile', 'use_context.gemspec']
16
+ end
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: use_context
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Cameron Dutro
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Easily provide block-scoped context values in your Ruby code.
13
+ email:
14
+ - camertron@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - CHANGELOG.md
20
+ - Gemfile
21
+ - LICENSE
22
+ - README.md
23
+ - Rakefile
24
+ - lib/use_context.rb
25
+ - lib/use_context/ext/kernel.rb
26
+ - lib/use_context/ext/kernel_refinement.rb
27
+ - lib/use_context/version.rb
28
+ - test/kernel_test.rb
29
+ - test/test_helper.rb
30
+ - test/use_context_test.rb
31
+ - use_context.gemspec
32
+ homepage: http://github.com/camertron/use_context
33
+ licenses: []
34
+ metadata: {}
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubygems_version: 3.6.7
50
+ specification_version: 4
51
+ summary: Easily provide block-scoped context values in your Ruby code.
52
+ test_files: []