tablesalt 0.26.0.2 → 0.27.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +134 -1
- data/lib/tablesalt/class_pass.rb +3 -3
- data/lib/tablesalt/rspec/config.rb +7 -0
- data/lib/tablesalt/rspec/custom_matchers/define_thread_accessor.rb +21 -0
- data/lib/tablesalt/rspec/custom_matchers/define_thread_reader.rb +19 -18
- data/lib/tablesalt/rspec/custom_matchers/define_thread_writer.rb +92 -0
- data/lib/tablesalt/rspec/custom_matchers.rb +2 -0
- data/lib/tablesalt/spec_helper.rb +1 -0
- data/lib/tablesalt/thread_accessor/management.rb +34 -0
- data/lib/tablesalt/thread_accessor/rack_middleware.rb +24 -0
- data/lib/tablesalt/thread_accessor/scoped_accessor.rb +27 -0
- data/lib/tablesalt/thread_accessor/store_instance.rb +13 -0
- data/lib/tablesalt/thread_accessor/thread_store.rb +16 -0
- data/lib/tablesalt/thread_accessor.rb +52 -4
- data/lib/tablesalt/version.rb +1 -1
- data/lib/tablesalt.rb +8 -8
- metadata +12 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6afc837476271cac528b32cc0eef83747d14bef637291e667322ade86735ad98
|
4
|
+
data.tar.gz: bf1c5ca11e457c8a6f16ef30d84deb17ad66206cc55d8e42cf21ccc5978cb7e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73f94eb9cab2f065eae115c5c8887d1565582d624afc86ae2d8cc50f81c36e2b725663e5dc3afb17cc26e10755009b8475c593f54366fac01ae2f83717dd9389
|
7
|
+
data.tar.gz: b33354269fa7bd1aa40f63a559aae58477a147e7aacd405b4d5da128b5d1555d41c4f884587c980771e5263fd69e7ed76f7737d650959186fc079a58613f74e5
|
data/README.md
CHANGED
@@ -31,7 +31,140 @@ Or install it yourself as:
|
|
31
31
|
|
32
32
|
## Usage
|
33
33
|
|
34
|
-
|
34
|
+
### ClassPass
|
35
|
+
|
36
|
+
TODO: write usage instructions
|
37
|
+
|
38
|
+
### DSLAccessor
|
39
|
+
|
40
|
+
TODO: write usage instructions
|
41
|
+
|
42
|
+
### Isolation
|
43
|
+
|
44
|
+
TODO: write usage instructions
|
45
|
+
|
46
|
+
### StringableObject
|
47
|
+
|
48
|
+
TODO: write usage instructions
|
49
|
+
|
50
|
+
### ThreadAccessor
|
51
|
+
|
52
|
+
In the simplest use case, a ThreadAccessor can be used to set a value on the current working thread to be used later on. The `thread_accessor` method creates a singleton and instance method for reading and writing a given variable stored on the thread.
|
53
|
+
|
54
|
+
#### Defined methods
|
55
|
+
* `thread_reader(method_name, thread_key = method_name, private: true)`
|
56
|
+
Defines singleton method and instance method to read from the given thread key.
|
57
|
+
* `thread_writer(method_name, thread_key = method_name, private: true)`
|
58
|
+
Defines singleton method and instance method to write to the given thread key.
|
59
|
+
* `thread_accessor(method_name, thread_key = method_name, private: true)`
|
60
|
+
Calls `thread_reader` and `thread_writer` with the given arguments.
|
61
|
+
|
62
|
+
#### Example
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
class CurrentUser
|
66
|
+
include TableSalt::ThreadAccessor
|
67
|
+
|
68
|
+
thread_accessor :current_user
|
69
|
+
|
70
|
+
def self.set(user)
|
71
|
+
self.current_user = user
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
CurrentUser.set(User.first)
|
76
|
+
|
77
|
+
# then, sometime later within the same request:
|
78
|
+
CurrentUser.current_user
|
79
|
+
# => #<User id: 1>
|
80
|
+
```
|
81
|
+
|
82
|
+
#### Thread Safety
|
83
|
+
Yep, when you mess with thread variables, you need to think about thread safety. For Rack applications, `ThreadAccessor` ships with a Rack middleware component:
|
84
|
+
```ruby
|
85
|
+
# in config/initializers/rack.rb, or config/initializers/tablesalt.rb, or anywhere else in your boot path:
|
86
|
+
config.middleware.use TableSalt::ThreadAccessor::RackMiddleware
|
87
|
+
```
|
88
|
+
|
89
|
+
If your application isn't on Rack, you'll need to add a little more code manually to take care of this:
|
90
|
+
```ruby
|
91
|
+
# Somewhere in your request path:
|
92
|
+
class YourMiddleware
|
93
|
+
def call
|
94
|
+
ThreadAccessor.clean_thread_context { yield }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
#### Namespaces
|
100
|
+
|
101
|
+
Maybe you wrote a gem that, say, sets a current user value on the thread to enable some other cool behavior. Since you don't want your gem's behavior to interfere with other behavior of the application, you can use a namespaced thread store instead:
|
102
|
+
```ruby
|
103
|
+
module MyGem
|
104
|
+
class CurrentUser
|
105
|
+
# This isolates the module's thread store for your gem.
|
106
|
+
include Tablesalt::ThreadAccessor[MyGem]
|
107
|
+
|
108
|
+
thread_accessor :current_user
|
109
|
+
|
110
|
+
def self.set(user)
|
111
|
+
self.current_user = user
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class DoAThing
|
116
|
+
include Tablesalt::ThreadAccessor[MyGem]
|
117
|
+
|
118
|
+
thread_accessor :current_user
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
MyGem::CurrentUser.set(User.first)
|
123
|
+
|
124
|
+
# then, sometime later within the same request:
|
125
|
+
MyGem::DoAThing.current_user
|
126
|
+
# => #<User id: 1>
|
127
|
+
|
128
|
+
# From class defined above:
|
129
|
+
CurrentUser.current_user
|
130
|
+
# => nil
|
131
|
+
```
|
132
|
+
|
133
|
+
Note that this will require you to clear your gem's thread store manually. It is recommended you provide your own middleware with your gem to keep the application's thread stores clean:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
module MyGem
|
137
|
+
class MyMiddleware
|
138
|
+
def initialize(app)
|
139
|
+
@app = app
|
140
|
+
end
|
141
|
+
|
142
|
+
def call(req)
|
143
|
+
ThreadAccessor.clean_thread_context(namespace: MyGem) { @app.call(req) }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
#### Test Helpers
|
150
|
+
|
151
|
+
Tablesalt's spec helper provides custom RSpec matchers for thread accessor methods:
|
152
|
+
* `defines_thread_accessor(name, thread_key, private: true, namespace: nil)`
|
153
|
+
* `defines_thread_reader(name, thread_key, private: true, namespace: nil)`
|
154
|
+
* `defines_thread_writer(name, thread_key, private: true, namespace: nil)`
|
155
|
+
|
156
|
+
If your application uses ThreadAccessor, you'll need to clear the thread stores between test runs.
|
157
|
+
* **RSpec**
|
158
|
+
If you're using RSpec, you're in luck! Just `require tablesalt/spec_helper` in `spec_helper.rb` or `rails_helper.rb`.
|
159
|
+
* **Minitest & others**
|
160
|
+
If you're using Minitest or some other testing framework, you'll need to clear things out manually. This is pretty simple, just run the following after each test run:
|
161
|
+
```ruby
|
162
|
+
Thread.current[Tablesalt::ThreadAccessor::THREAD_ACCESSOR_STORE_THREAD_KEY] = nil
|
163
|
+
```
|
164
|
+
|
165
|
+
### UsesHashForEquality
|
166
|
+
|
167
|
+
TODO: write usage instructions
|
35
168
|
|
36
169
|
## Development
|
37
170
|
|
data/lib/tablesalt/class_pass.rb
CHANGED
@@ -18,7 +18,7 @@ module Tablesalt
|
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
|
-
def class_pass_method(*methods)
|
21
|
+
def class_pass_method(*methods, to: nil)
|
22
22
|
methods.each do |method|
|
23
23
|
next if _class_pass_methods.include?(method)
|
24
24
|
|
@@ -26,9 +26,9 @@ module Tablesalt
|
|
26
26
|
|
27
27
|
define_singleton_method method do |*args, **attrs|
|
28
28
|
if RUBY_VERSION < "2.7.0" && attrs.empty?
|
29
|
-
new(*args).public_send(method)
|
29
|
+
new(*args).public_send(to || method)
|
30
30
|
else
|
31
|
-
new(*args, **attrs).public_send(method)
|
31
|
+
new(*args, **attrs).public_send(to || method)
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# RSpec matcher to assert the definition of a thread accessor
|
4
|
+
#
|
5
|
+
# class MyClass
|
6
|
+
# include Tablesalt::ThreadAccessor
|
7
|
+
#
|
8
|
+
# thread_accessor :foo, :a_foo_key
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# RSpec.describe MyClass do
|
12
|
+
# subject { described_class }
|
13
|
+
#
|
14
|
+
# it { is_expected.to define_thread_accessor :foo, :a_foo_key, private: false }
|
15
|
+
# end
|
16
|
+
RSpec::Matchers.define :define_thread_accessor do |accessor_name, thread_key, **options|
|
17
|
+
match do |subject|
|
18
|
+
expect(subject).to define_thread_reader accessor_name, thread_key, **options
|
19
|
+
expect(subject).to define_thread_writer accessor_name, thread_key, **options
|
20
|
+
end
|
21
|
+
end
|
@@ -5,7 +5,7 @@ require "active_support/core_ext/hash/keys"
|
|
5
5
|
# RSpec matcher to assert the definition of a thread accessor
|
6
6
|
#
|
7
7
|
# class MyClass
|
8
|
-
# include Tablesalt::
|
8
|
+
# include Tablesalt::ThreadAccessor
|
9
9
|
#
|
10
10
|
# thread_reader :a_thread_key
|
11
11
|
# end
|
@@ -16,20 +16,21 @@ require "active_support/core_ext/hash/keys"
|
|
16
16
|
# it { is_expected.to define_thread_reader :a_thread_key, private: false }
|
17
17
|
# end
|
18
18
|
RSpec::Matchers.define :define_thread_reader do |method_name, thread_key, **options|
|
19
|
-
attr_reader :subject, :method_name, :thread_key, :private_opt
|
19
|
+
attr_reader :subject, :method_name, :thread_key, :private_opt, :namespace
|
20
20
|
|
21
|
-
description { "
|
21
|
+
description { "define#{a_private} thread reader #{method_name.inspect} with thread key #{thread_key.inspect}#{namespace_failure_message}" }
|
22
22
|
|
23
|
-
failure_message { "expected #{subject_module} to define#{a_private} thread reader #{method_name.inspect} with thread key #{thread_key.inspect}" }
|
24
|
-
failure_message_when_negated { "expected #{subject_module} not to define#{a_private} thread reader #{method_name.inspect} with thread key #{thread_key.inspect}" }
|
23
|
+
failure_message { "expected #{subject_module} to define#{a_private} thread reader #{method_name.inspect} with thread key #{thread_key.inspect}#{namespace_failure_message}" }
|
24
|
+
failure_message_when_negated { "expected #{subject_module} not to define#{a_private} thread reader #{method_name.inspect} with thread key #{thread_key.inspect}#{namespace_failure_message}" }
|
25
25
|
|
26
|
-
|
27
|
-
options.assert_valid_keys(:private)
|
26
|
+
match do |subject|
|
27
|
+
options.assert_valid_keys(:private, :namespace)
|
28
28
|
|
29
29
|
@subject = subject
|
30
30
|
@method_name = method_name
|
31
31
|
@thread_key = thread_key.to_sym
|
32
32
|
@private_opt = options.fetch(:private, true)
|
33
|
+
@namespace = options.fetch(:namespace, nil)
|
33
34
|
|
34
35
|
with_value_on_thread do
|
35
36
|
expect(instance).to be_respond_to(method_name, true)
|
@@ -50,11 +51,12 @@ RSpec::Matchers.define :define_thread_reader do |method_name, thread_key, **opti
|
|
50
51
|
private
|
51
52
|
|
52
53
|
def with_value_on_thread
|
53
|
-
|
54
|
+
value_before = Tablesalt::ThreadAccessor.store(namespace)[thread_key]
|
55
|
+
Tablesalt::ThreadAccessor.store(namespace)[thread_key] = stubbed_value
|
54
56
|
|
55
57
|
yield
|
56
58
|
|
57
|
-
|
59
|
+
Tablesalt::ThreadAccessor.store(namespace)[thread_key] = value_before
|
58
60
|
end
|
59
61
|
|
60
62
|
def subject_module
|
@@ -62,19 +64,12 @@ RSpec::Matchers.define :define_thread_reader do |method_name, thread_key, **opti
|
|
62
64
|
end
|
63
65
|
|
64
66
|
def klass
|
65
|
-
@klass ||=
|
66
|
-
case subject
|
67
|
-
when Class
|
68
|
-
subject
|
69
|
-
when Module
|
70
|
-
Class.new(subject)
|
71
|
-
else
|
72
|
-
subject.class
|
73
|
-
end
|
67
|
+
@klass ||= subject.is_a?(Module) ? subject : subject.class
|
74
68
|
end
|
75
69
|
|
76
70
|
def instance
|
77
71
|
return subject unless subject.is_a?(Module)
|
72
|
+
return subject unless subject.respond_to?(:new)
|
78
73
|
|
79
74
|
@instance ||= begin
|
80
75
|
allow(klass).to receive(:initialize).with(any_args)
|
@@ -91,4 +86,10 @@ RSpec::Matchers.define :define_thread_reader do |method_name, thread_key, **opti
|
|
91
86
|
|
92
87
|
" a private"
|
93
88
|
end
|
89
|
+
|
90
|
+
def namespace_failure_message
|
91
|
+
return if namespace.blank?
|
92
|
+
|
93
|
+
" in #{namespace.inspect} namespace"
|
94
|
+
end
|
94
95
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/keys"
|
4
|
+
|
5
|
+
# RSpec matcher to assert the definition of a thread accessor
|
6
|
+
#
|
7
|
+
# class MyClass
|
8
|
+
# include Tablesalt::ThreadAccessor
|
9
|
+
#
|
10
|
+
# thread_writer :foo, :a_foo_key
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# RSpec.describe MyClass do
|
14
|
+
# subject { described_class }
|
15
|
+
#
|
16
|
+
# it { is_expected.to define_thread_writer :foo, :a_foo_key, private: false }
|
17
|
+
# end
|
18
|
+
RSpec::Matchers.define :define_thread_writer do |writer_name, thread_key, **options|
|
19
|
+
attr_reader :subject, :writer_name, :method_name, :thread_key, :private_opt, :namespace
|
20
|
+
|
21
|
+
description { "define#{a_private} thread writer #{writer_name.inspect} with thread key #{thread_key.inspect}#{namespace_failure_message}" }
|
22
|
+
|
23
|
+
failure_message { "expected #{subject_module} to define#{a_private} thread writer #{writer_name.inspect} with thread key #{thread_key.inspect}#{namespace_failure_message}" }
|
24
|
+
failure_message_when_negated { "expected #{subject_module} not to define#{a_private} thread writer #{writer_name.inspect} with thread key #{thread_key.inspect}#{namespace_failure_message}" }
|
25
|
+
|
26
|
+
match do |subject|
|
27
|
+
options.assert_valid_keys(:private, :namespace)
|
28
|
+
|
29
|
+
@subject = subject
|
30
|
+
@writer_name = writer_name
|
31
|
+
@method_name = "#{writer_name}="
|
32
|
+
@thread_key = thread_key.to_sym
|
33
|
+
@private_opt = options.fetch(:private, true)
|
34
|
+
@namespace = options.fetch(:namespace, nil)
|
35
|
+
|
36
|
+
expect(instance).to be_respond_to(method_name, true)
|
37
|
+
expect(klass).to be_respond_to(method_name, true)
|
38
|
+
|
39
|
+
klass.__send__(method_name, class_stubbed_value)
|
40
|
+
expect(Tablesalt::ThreadAccessor.store(namespace)[thread_key]).to eq class_stubbed_value
|
41
|
+
|
42
|
+
instance.__send__(method_name, instance_stubbed_value)
|
43
|
+
expect(Tablesalt::ThreadAccessor.store(namespace)[thread_key]).to eq instance_stubbed_value
|
44
|
+
|
45
|
+
if private_opt
|
46
|
+
expect(klass).to be_private_method_defined method_name
|
47
|
+
expect(klass.singleton_class).to be_private_method_defined method_name
|
48
|
+
end
|
49
|
+
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def subject_module
|
56
|
+
subject.is_a?(Module) ? subject : subject.class
|
57
|
+
end
|
58
|
+
|
59
|
+
def klass
|
60
|
+
@klass ||= subject.is_a?(Module) ? subject : subject.class
|
61
|
+
end
|
62
|
+
|
63
|
+
def instance
|
64
|
+
return subject unless subject.is_a?(Module)
|
65
|
+
return subject unless subject.respond_to?(:new)
|
66
|
+
|
67
|
+
@instance ||= begin
|
68
|
+
allow(klass).to receive(:initialize).with(any_args)
|
69
|
+
subject.new
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def class_stubbed_value
|
74
|
+
@class_stubbed_value ||= double("class thread value")
|
75
|
+
end
|
76
|
+
|
77
|
+
def instance_stubbed_value
|
78
|
+
@instance_stubbed_value ||= double("instance thread value")
|
79
|
+
end
|
80
|
+
|
81
|
+
def a_private
|
82
|
+
return unless private_opt
|
83
|
+
|
84
|
+
" a private"
|
85
|
+
end
|
86
|
+
|
87
|
+
def namespace_failure_message
|
88
|
+
return if namespace.blank?
|
89
|
+
|
90
|
+
" in #{namespace.inspect} namespace"
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tablesalt
|
4
|
+
module ThreadAccessor
|
5
|
+
module Management
|
6
|
+
def store(namespace = nil)
|
7
|
+
raise ArgumentError, "cannot request a namespaced store from a namespaced accessor" if namespace.present? && self::THREAD_ACCESSOR_STORE_NAMESPACE.present?
|
8
|
+
|
9
|
+
namespace = self::THREAD_ACCESSOR_STORE_NAMESPACE if namespace.nil?
|
10
|
+
|
11
|
+
stores = Thread.current[Tablesalt::ThreadAccessor::THREAD_ACCESSOR_STORE_THREAD_KEY] ||= {}
|
12
|
+
stores[namespace] ||= ThreadStore.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# Cleans up ThreadAccessor state after given block executes
|
16
|
+
#
|
17
|
+
# @param :logger [Logger] Optional; A logger instance that implements the method :warn to send warning messages to
|
18
|
+
# @yield Required; Yields no variables to the given block
|
19
|
+
def clean_thread_context(logger: nil, namespace: nil)
|
20
|
+
if store(namespace).present?
|
21
|
+
if logger.nil?
|
22
|
+
puts "WARNING: ThreadAccessor variables set outside ThreadAccessor context: #{store(namespace).keys.join(", ")}"
|
23
|
+
else
|
24
|
+
logger.warn("ThreadAccessor variables set outside ThreadAccessor context: #{store(namespace).keys.join(", ")}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
yield
|
29
|
+
ensure
|
30
|
+
store(namespace).clear
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tablesalt
|
4
|
+
module ThreadAccessor
|
5
|
+
class RackMiddleware
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
# Clears thread variables after request is finished processing.
|
11
|
+
# Make sure this middleware appears +before+ anything that may set
|
12
|
+
# thread variables using ThreadAccessor
|
13
|
+
def call(req)
|
14
|
+
ThreadAccessor.clean_thread_context(logger: logger) { @app.call(req) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def logger
|
18
|
+
return unless @app.respond_to? :logger
|
19
|
+
|
20
|
+
@app.logger
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tablesalt
|
4
|
+
module ThreadAccessor
|
5
|
+
class ScopedAccessor < Module
|
6
|
+
attr_reader :scope
|
7
|
+
|
8
|
+
def initialize(scope)
|
9
|
+
@scope = scope
|
10
|
+
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
extend Management
|
13
|
+
include ThreadAccessor
|
14
|
+
|
15
|
+
const_set :THREAD_ACCESSOR_STORE_NAMESPACE, scope
|
16
|
+
end
|
17
|
+
|
18
|
+
def name
|
19
|
+
"#{self.class}:#{scope}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"#<#{name}>"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tablesalt
|
4
|
+
module ThreadAccessor
|
5
|
+
module StoreInstance
|
6
|
+
private
|
7
|
+
|
8
|
+
def __thread_accessor_store_instance__
|
9
|
+
ThreadAccessor.store(respond_to?(:const_get) ? self::THREAD_ACCESSOR_STORE_NAMESPACE : self.class::THREAD_ACCESSOR_STORE_NAMESPACE)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/hash_with_indifferent_access"
|
4
|
+
|
5
|
+
module Tablesalt
|
6
|
+
module ThreadAccessor
|
7
|
+
class ThreadStore
|
8
|
+
delegate :==, to: :hash
|
9
|
+
delegate_missing_to :hash
|
10
|
+
|
11
|
+
def hash
|
12
|
+
@hash ||= HashWithIndifferentAccess.new
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,12 +1,49 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/concern"
|
4
|
+
require "active_support/core_ext/module/delegation"
|
4
5
|
|
6
|
+
require_relative "thread_accessor/management"
|
7
|
+
require_relative "thread_accessor/rack_middleware"
|
8
|
+
require_relative "thread_accessor/scoped_accessor"
|
9
|
+
require_relative "thread_accessor/store_instance"
|
10
|
+
require_relative "thread_accessor/thread_store"
|
11
|
+
|
12
|
+
# WARNING: This module is still in beta mode and will likely change significantly soon. Tread carefully...
|
5
13
|
module Tablesalt
|
6
14
|
module ThreadAccessor
|
7
15
|
extend ActiveSupport::Concern
|
16
|
+
extend Management
|
17
|
+
|
18
|
+
THREAD_ACCESSOR_STORE_THREAD_KEY = :__tablesalt_thread_accessor_store__
|
19
|
+
|
20
|
+
# nil by default, gets overridden by ScopedAccessor
|
21
|
+
THREAD_ACCESSOR_STORE_NAMESPACE = nil
|
22
|
+
|
23
|
+
include StoreInstance
|
24
|
+
|
25
|
+
class << self
|
26
|
+
# @example
|
27
|
+
# module MyGem
|
28
|
+
# class MyClass
|
29
|
+
# include Tablesalt::ThreadAccessor[:my_gem]
|
30
|
+
#
|
31
|
+
# # Stored in a separate thread store for :my_gem, safe from mischievous app developers
|
32
|
+
# thread_accessor :foo, :my_foo
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# @param scope [String, Symbol] A namespace for the thread variables
|
37
|
+
# @return [Module] A ThreadAccessor module to be included into your class
|
38
|
+
def [](scope)
|
39
|
+
@scoped_accessors ||= {}
|
40
|
+
@scoped_accessors[scope] ||= ScopedAccessor.new(scope)
|
41
|
+
end
|
42
|
+
end
|
8
43
|
|
9
44
|
module ClassMethods
|
45
|
+
include StoreInstance
|
46
|
+
|
10
47
|
private
|
11
48
|
|
12
49
|
# Defines an instance method and a singleton method to read from a given key in Thread.current
|
@@ -29,8 +66,8 @@ module Tablesalt
|
|
29
66
|
# @param thread_key [String, Symbol] The key to read from Thread.current
|
30
67
|
# @option :private [Boolean] If true, both defined methods will be private. Default: true
|
31
68
|
def thread_reader(method, thread_key, **options)
|
32
|
-
define_method(method) {
|
33
|
-
define_singleton_method(method) {
|
69
|
+
define_method(method) { __thread_accessor_store_instance__[thread_key] }
|
70
|
+
define_singleton_method(method) { __thread_accessor_store_instance__[thread_key] }
|
34
71
|
|
35
72
|
return unless options.fetch(:private, true)
|
36
73
|
|
@@ -52,14 +89,25 @@ module Tablesalt
|
|
52
89
|
# Thread.current[:foo]
|
53
90
|
# => "bar"
|
54
91
|
#
|
92
|
+
# Note::
|
93
|
+
# All written thread variables are tracked on-thread, but will not be automatically cleared when
|
94
|
+
# the thread is done processing a request/unit of work. Make sure to either use the provided
|
95
|
+
# {ThreadAccessor::RackMiddleware} or run {ThreadAccessor.clean_thread_context} manually once
|
96
|
+
# the thread is finished to avoid pollluting other requests.
|
97
|
+
#
|
98
|
+
# Gem Authors::
|
99
|
+
# Thread variables should ideally be kept in a namespaced store instead of the global one. This means
|
100
|
+
# you are responsible for clearing your own store - either add your own middleware or advise users how
|
101
|
+
# to clear the thread store themselves.
|
102
|
+
#
|
55
103
|
# @param method [String, Symbol] The name of the writer method
|
56
104
|
# @param thread_key [String, Symbol] The key to write to on Thread.current
|
57
105
|
# @option :private [Boolean] If true, both defined methods will be private. Default: true
|
58
106
|
def thread_writer(method, thread_key, **options)
|
59
107
|
method_name = "#{method}="
|
60
108
|
|
61
|
-
define_method(method_name) { |value|
|
62
|
-
define_singleton_method(method_name) { |value|
|
109
|
+
define_method(method_name) { |value| __thread_accessor_store_instance__[thread_key] = value }
|
110
|
+
define_singleton_method(method_name) { |value| __thread_accessor_store_instance__[thread_key] = value }
|
63
111
|
|
64
112
|
return unless options.fetch(:private, true)
|
65
113
|
|
data/lib/tablesalt/version.rb
CHANGED
data/lib/tablesalt.rb
CHANGED
@@ -3,13 +3,13 @@
|
|
3
3
|
require "active_support"
|
4
4
|
require "short_circu_it"
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
require_relative "tablesalt/version"
|
7
|
+
|
8
|
+
require_relative "tablesalt/class_pass"
|
9
|
+
require_relative "tablesalt/dsl_accessor"
|
10
|
+
require_relative "tablesalt/isolation"
|
11
|
+
require_relative "tablesalt/stringable_object"
|
12
|
+
require_relative "tablesalt/thread_accessor"
|
13
|
+
require_relative "tablesalt/uses_hash_for_equality"
|
14
14
|
|
15
15
|
module Tablesalt; end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tablesalt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.27.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jordan Minneti
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-04
|
11
|
+
date: 2021-09-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -53,12 +53,20 @@ files:
|
|
53
53
|
- lib/tablesalt/class_pass.rb
|
54
54
|
- lib/tablesalt/dsl_accessor.rb
|
55
55
|
- lib/tablesalt/isolation.rb
|
56
|
+
- lib/tablesalt/rspec/config.rb
|
56
57
|
- lib/tablesalt/rspec/custom_matchers.rb
|
58
|
+
- lib/tablesalt/rspec/custom_matchers/define_thread_accessor.rb
|
57
59
|
- lib/tablesalt/rspec/custom_matchers/define_thread_reader.rb
|
60
|
+
- lib/tablesalt/rspec/custom_matchers/define_thread_writer.rb
|
58
61
|
- lib/tablesalt/rspec/custom_matchers/isolate.rb
|
59
62
|
- lib/tablesalt/spec_helper.rb
|
60
63
|
- lib/tablesalt/stringable_object.rb
|
61
64
|
- lib/tablesalt/thread_accessor.rb
|
65
|
+
- lib/tablesalt/thread_accessor/management.rb
|
66
|
+
- lib/tablesalt/thread_accessor/rack_middleware.rb
|
67
|
+
- lib/tablesalt/thread_accessor/scoped_accessor.rb
|
68
|
+
- lib/tablesalt/thread_accessor/store_instance.rb
|
69
|
+
- lib/tablesalt/thread_accessor/thread_store.rb
|
62
70
|
- lib/tablesalt/uses_hash_for_equality.rb
|
63
71
|
- lib/tablesalt/version.rb
|
64
72
|
homepage: https://github.com/Freshly/spicerack/tree/main/tablesalt
|
@@ -68,7 +76,7 @@ metadata:
|
|
68
76
|
homepage_uri: https://github.com/Freshly/spicerack/tree/main/tablesalt
|
69
77
|
source_code_uri: https://github.com/Freshly/spicerack/tree/main/tablesalt
|
70
78
|
changelog_uri: https://github.com/Freshly/spicerack/blob/main/tablesalt/CHANGELOG.md
|
71
|
-
documentation_uri: https://www.rubydoc.info/gems/tablesalt/0.
|
79
|
+
documentation_uri: https://www.rubydoc.info/gems/tablesalt/0.27.1
|
72
80
|
post_install_message:
|
73
81
|
rdoc_options: []
|
74
82
|
require_paths:
|
@@ -84,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
92
|
- !ruby/object:Gem::Version
|
85
93
|
version: '0'
|
86
94
|
requirements: []
|
87
|
-
rubygems_version: 3.
|
95
|
+
rubygems_version: 3.2.26
|
88
96
|
signing_key:
|
89
97
|
specification_version: 4
|
90
98
|
summary: Miscellaneous helper modules, POROs, and more, that standardize common behavior
|