Class: LSpace
- Inherits:
-
Object
- Object
- LSpace
- Defined in:
- lib/lspace.rb,
lib/lspace/class_methods.rb
Overview
An LSpace is an implicit namespace for storing state that is secondary to your application’s purpose, but still necessary.
In many ways they are the successor to the Thread-local namespace, but they are designed to be active during a logical segment of code, no matter how you slice that code amongst different Threads or Fibers.
The API for LSpace encourages creating a new sub-LSpace whenever you want to mutate the value of an LSpace-variable. This ensures that local changes take effect only for code that is logically contained within a block, avoiding many of the problems of mutable global state.
Instance Attribute Summary collapse
-
#around_filters ⇒ Object
Returns the value of attribute around_filters.
-
#hash ⇒ Object
Returns the value of attribute hash.
-
#parent ⇒ Object
Returns the value of attribute parent.
Class Method Summary collapse
-
.[](key) ⇒ Object
Get the value for the key in the current LSpace or its parents.
-
.[]=(key, value) ⇒ Object
Set the value for the key in the current LSpace.
-
.around_filter(&filter) ⇒ Object
Add an around filter to the current LSpace.
-
.clean(&block) ⇒ Object
Create a new clean LSpace.
-
.current ⇒ LSpace
Get the current LSpace.
-
.enter(lspace, &block) ⇒ Object
Enter an LSpace for the logical duration of the block.
-
.fork ⇒ Object
Replace the current LSpace with a fork of it.
-
.keys ⇒ Object
Find all the keys currently in LSpace.
-
.preserve(&block) ⇒ Object
Create a closure that will re-enter the current LSpace when the block is called.
-
.rescue(*exceptions, &handler) ⇒ Object
Add an exception handler.
-
.with(hash = {}, &block) ⇒ Object
Create a new LSpace with the given keys set to the given values, and run the given block in that new LSpace.
Instance Method Summary collapse
-
#[](key) ⇒ Object
Get the most specific value for the key.
-
#[]=(key, value) ⇒ Object
Update the LSpace-variable with the given name.
-
#around_filter(&filter) ⇒ Object
Add an around_filter to this LSpace.
-
#enter(&block) ⇒ Object
Enter this LSpace for the duration of the block.
-
#hierarchy ⇒ Array<LSpace>
Get the list of Lspaces up to the root, most specific first.
-
#initialize(hash = {}, parent = LSpace.current, &block) ⇒ LSpace
constructor
Create a new LSpace.
-
#keys ⇒ Array
Return the list of keys in the current LSpace or its parents.
-
#rescue(*exceptions, &handler) ⇒ Object
Add an error handler to this LSpace.
-
#wrap(&original) ⇒ Object
Wraps a block/proc such that it runs in this LSpace when it is called.
Constructor Details
#initialize(hash = {}, parent = LSpace.current, &block) ⇒ LSpace
Create a new LSpace.
By default the new LSpace will exactly mirror the currently active LSpace, though any variables you pass in will take precedence over those defined in the parent.
37 38 39 40 41 42 |
# File 'lib/lspace.rb', line 37 def initialize(hash={}, parent=LSpace.current, &block) @hash = hash @parent = parent @around_filters = [] enter(&block) if block_given? end |
Instance Attribute Details
#around_filters ⇒ Object
Returns the value of attribute around_filters.
26 27 28 |
# File 'lib/lspace.rb', line 26 def around_filters @around_filters end |
#hash ⇒ Object
Returns the value of attribute hash.
26 27 28 |
# File 'lib/lspace.rb', line 26 def hash @hash end |
#parent ⇒ Object
Returns the value of attribute parent.
26 27 28 |
# File 'lib/lspace.rb', line 26 def parent @parent end |
Class Method Details
.[](key) ⇒ Object
Get the value for the key in the current LSpace or its parents
113 114 115 |
# File 'lib/lspace/class_methods.rb', line 113 def self.[](key) current[key] end |
.[]=(key, value) ⇒ Object
Set the value for the key in the current LSpace
120 121 122 |
# File 'lib/lspace/class_methods.rb', line 120 def self.[]=(key, value) current[key] = value end |
.around_filter(&filter) ⇒ Object
Add an around filter to the current LSpace
134 135 136 |
# File 'lib/lspace/class_methods.rb', line 134 def self.around_filter(&filter) current.around_filter(&filter) end |
.clean(&block) ⇒ Object
Create a new clean LSpace.
This LSpace does not inherit any LSpace variables in the currently active LSpace.
18 19 20 21 22 23 24 |
# File 'lib/lspace/class_methods.rb', line 18 def self.clean(&block) if block_given? enter new({}, nil), &block else new({}, nil) end end |
.current ⇒ LSpace
Get the current LSpace
148 149 150 |
# File 'lib/lspace/class_methods.rb', line 148 def self.current Thread.current[:lspace] ||= LSpace.new({}, nil) end |
.enter(lspace, &block) ⇒ Object
Enter an LSpace for the logical duration of the block.
The LSpace will be active at least for the duration of the block’s callstack, but if the block creates any closures (using LSpace.preserve directly, or in library form) then the logical duration will also encompass code run in those closures.
Entering an LSpace will also cause any around_filters defined on it and its parents to be run.
70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/lspace/class_methods.rb', line 70 def self.enter(lspace, &block) previous = current self.current = lspace filters = lspace.hierarchy.take_while{ |lspace| lspace != previous }.flatten.map(&:around_filters).flatten filters.inject(block) do |blk, filter| lambda{ filter.call(&blk) } end.call ensure self.current = previous end |
.fork ⇒ Object
Replace the current LSpace with a fork of it.
Forking the Lspace means that values changed with LSpace#[]= no longer affect parent LSpaces.
This should be used carefully - it may confuse around_filters, since they will see a different LSpace after the block is called than before.
91 92 93 |
# File 'lib/lspace/class_methods.rb', line 91 def self.fork self.current = LSpace.new({}, self.current) end |
.keys ⇒ Object
Find all the keys currently in LSpace
127 128 129 |
# File 'lib/lspace/class_methods.rb', line 127 def self.keys current.keys end |
.preserve(&block) ⇒ Object
Create a closure that will re-enter the current LSpace when the block is called.
106 107 108 |
# File 'lib/lspace/class_methods.rb', line 106 def self.preserve(&block) current.wrap(&block) end |
.rescue(*exceptions, &handler) ⇒ Object
Add an exception handler
141 142 143 |
# File 'lib/lspace/class_methods.rb', line 141 def self.rescue(*exceptions, &handler) current.rescue(*exceptions, &handler) end |
.with(hash = {}, &block) ⇒ Object
Create a new LSpace with the given keys set to the given values, and run the given block in that new LSpace.
The LSpace will inherit any unspecified keys from the currently active LSpace.
42 43 44 |
# File 'lib/lspace/class_methods.rb', line 42 def self.with(hash={}, &block) enter new(hash, current), &block end |
Instance Method Details
#[](key) ⇒ Object
Get the most specific value for the key.
If the key is not present in the hash of this LSpace, lookup proceeds up the chain of parent LSpaces. If the key is not found anywhere, nil is returned.
57 58 59 60 61 62 63 |
# File 'lib/lspace.rb', line 57 def [](key) hierarchy.each do |lspace| return lspace.hash[key] if lspace.hash.has_key?(key) end nil end |
#[]=(key, value) ⇒ Object
Update the LSpace-variable with the given name.
Bear in mind that any code using this LSpace will see this change, and consider using with or fork instead to localize your changes.
This method is mostly useful for setting up a new LSpace before any code is using it, and has no effect on parent LSpaces.
81 82 83 |
# File 'lib/lspace.rb', line 81 def []=(key, value) hash[key] = value end |
#around_filter(&filter) ⇒ Object
Add an around_filter to this LSpace.
Around filters are blocks that take a block-parameter. They are called whenever the LSpace is re-entered, so they are suitable for implementing integrations between LSpace and libraries that rely on Thread-local state (like Log4r) or for adding fallback exception handlers to your logical segment of code (to prevent exceptions from killing your Thread-pool or event loop).
119 120 121 122 |
# File 'lib/lspace.rb', line 119 def around_filter(&filter) around_filters.unshift filter self end |
#enter(&block) ⇒ Object
Enter this LSpace for the duration of the block
152 153 154 |
# File 'lib/lspace.rb', line 152 def enter(&block) LSpace.enter(self, &block) end |
#hierarchy ⇒ Array<LSpace>
Get the list of Lspaces up to the root, most specific first
174 175 176 177 178 179 180 |
# File 'lib/lspace.rb', line 174 def hierarchy @hierarchy ||= if parent [self] + parent.hierarchy else [self] end end |
#keys ⇒ Array
Return the list of keys in the current LSpace or its parents.
93 94 95 |
# File 'lib/lspace.rb', line 93 def keys hierarchy.flat_map{ |lspace| lspace.hash.keys }.uniq end |
#rescue(*exceptions, &handler) ⇒ Object
Add an error handler to this LSpace.
136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/lspace.rb', line 136 def rescue(*exceptions, &handler) exceptions << RuntimeError unless exceptions.any? around_filter do |&block| begin block.call rescue *exceptions => e handler.call e end end end |
#wrap(&original) ⇒ Object
Wraps a block/proc such that it runs in this LSpace when it is called.
160 161 162 163 164 165 166 167 168 169 |
# File 'lib/lspace.rb', line 160 def wrap(&original) # Store self so that it works if the block is instance_eval'd shelf = self proc do |*args, &block| shelf.enter do original.call(*args, &block) end end end |