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 +7 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +12 -0
- data/LICENSE +21 -0
- data/README.md +102 -0
- data/Rakefile +16 -0
- data/lib/use_context/ext/kernel.rb +8 -0
- data/lib/use_context/ext/kernel_refinement.rb +17 -0
- data/lib/use_context/version.rb +5 -0
- data/lib/use_context.rb +74 -0
- data/test/kernel_test.rb +103 -0
- data/test/test_helper.rb +4 -0
- data/test/use_context_test.rb +15 -0
- data/use_context.gemspec +16 -0
- metadata +52 -0
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
data/Gemfile
ADDED
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
|
+

|
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,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
|
data/lib/use_context.rb
ADDED
@@ -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
|
data/test/kernel_test.rb
ADDED
@@ -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
|
data/test/test_helper.rb
ADDED
@@ -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
|
data/use_context.gemspec
ADDED
@@ -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: []
|