lspace 0.1.pre.1 → 0.1
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.
- data/README.md +152 -16
- data/lib/lspace.rb +126 -196
- data/lib/lspace/class_methods.rb +129 -0
- data/lib/lspace/core_ext.rb +53 -12
- data/lib/lspace/eventmachine.rb +56 -16
- data/lspace.gemspec +5 -1
- data/spec/class_method_spec.rb +116 -0
- data/spec/core_ext_spec.rb +75 -0
- data/spec/eventmachine_spec.rb +131 -0
- data/spec/lspace_spec.rb +121 -60
- data/spec/spec_helper.rb +4 -1
- metadata +58 -6
@@ -0,0 +1,129 @@
|
|
1
|
+
class LSpace
|
2
|
+
|
3
|
+
# Create a new clean LSpace.
|
4
|
+
#
|
5
|
+
# This LSpace does not inherit any LSpace variables in the currently active LSpace.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# RSpec.configure do |c|
|
9
|
+
# c.around(:each) do |example|
|
10
|
+
# LSpace.clean do
|
11
|
+
# example.run
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# @param [Proc] block The logical block that will be run in the clean LSpace
|
17
|
+
# @see LSpace.enter
|
18
|
+
def self.clean(&block)
|
19
|
+
enter new({}, nil), &block
|
20
|
+
end
|
21
|
+
|
22
|
+
# Create a new LSpace with the given keys set to the given values.
|
23
|
+
#
|
24
|
+
# The LSpace will inherit any unspecified keys from the currently active LSpace.
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# LSpace.update :user_id => 6 do
|
28
|
+
# LSpace.update :job_id => 7 do
|
29
|
+
# LSpace[:user_id] == 6
|
30
|
+
# LSpace[:job_id] == 7
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# @param [Hash] hash The keys to update
|
35
|
+
# @param [Proc] block The logical block to run with the updated LSpace
|
36
|
+
# @see LSpace.enter
|
37
|
+
def self.update(hash={}, &block)
|
38
|
+
enter new(hash, current), &block
|
39
|
+
end
|
40
|
+
|
41
|
+
# Enter an LSpace for the logical duration of the block.
|
42
|
+
#
|
43
|
+
# The LSpace will be active at least for the duration of the block's callstack,
|
44
|
+
# but if the block creates any closures (using LSpace.preserve directly, or in
|
45
|
+
# library form) then the logical duration will also encompass code run in those
|
46
|
+
# closures.
|
47
|
+
#
|
48
|
+
# Entering an LSpace will also cause any around_filters defined on it and its parents to
|
49
|
+
# be run.
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# class Job
|
53
|
+
# def initialize
|
54
|
+
# @lspace = LSpace.new(:job_id => self.id)
|
55
|
+
# LSpace.enter(@lspace){ setup_lspace }
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# def run!
|
59
|
+
# LSpace.enter(@lspace) { run }
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# @param [LSpace] lspace The LSpace to enter
|
64
|
+
# @param [Proc] block The logical block to run with the given LSpace
|
65
|
+
def self.enter(lspace, &block)
|
66
|
+
previous = current
|
67
|
+
self.current = lspace
|
68
|
+
|
69
|
+
filters = lspace.hierarchy.take_while{ |lspace| lspace != previous }.map(&:around_filters).flatten
|
70
|
+
|
71
|
+
filters.inject(block) do |blk, filter|
|
72
|
+
lambda{ filter.call(&blk) }
|
73
|
+
end.call
|
74
|
+
ensure
|
75
|
+
self.current = previous
|
76
|
+
end
|
77
|
+
|
78
|
+
# Create a closure that will re-enter the current LSpace when the block is called.
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# class TaskQueue
|
82
|
+
# def queue(&block)
|
83
|
+
# @queue << LSpace.preserve(&block)
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# @see Proc#in_lspace
|
88
|
+
# @param [Proc] block The logical block to wrap
|
89
|
+
def self.preserve(&block)
|
90
|
+
current.wrap(&block)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Get the value for the key in the current LSpace or its parents
|
94
|
+
#
|
95
|
+
# @see LSpace#[]
|
96
|
+
def self.[](key)
|
97
|
+
current[key]
|
98
|
+
end
|
99
|
+
|
100
|
+
# Set the value for the key in the current LSpace
|
101
|
+
#
|
102
|
+
# @see LSpace#[]=
|
103
|
+
def self.[]=(key, value)
|
104
|
+
current[key] = value
|
105
|
+
end
|
106
|
+
|
107
|
+
# Add an around filter to the current LSpace
|
108
|
+
#
|
109
|
+
# @see LSpace#around_filter
|
110
|
+
def self.around_filter(&filter)
|
111
|
+
current.around_filter(&filter)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Get the current LSpace
|
115
|
+
#
|
116
|
+
# @return [LSpace]
|
117
|
+
def self.current
|
118
|
+
Thread.current[:lspace] ||= LSpace.new({}, nil)
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
# Update the current LSpace
|
124
|
+
#
|
125
|
+
# @see LSpace.enter
|
126
|
+
def self.current=(lspace)
|
127
|
+
Thread.current[:lspace] = lspace
|
128
|
+
end
|
129
|
+
end
|
data/lib/lspace/core_ext.rb
CHANGED
@@ -1,4 +1,48 @@
|
|
1
1
|
class Module
|
2
|
+
# Create getter methods for LSpace
|
3
|
+
#
|
4
|
+
# Assumes that your LSpace keys are Symbols.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# class Job
|
8
|
+
# lspace_reader :user_id
|
9
|
+
#
|
10
|
+
# def user
|
11
|
+
# User.find(user_id)
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# LSpace[:user_id] = 6
|
16
|
+
# Job.new.user == #<User:6>
|
17
|
+
#
|
18
|
+
# @param [Symbol] *attrs The accessors to create
|
19
|
+
def lspace_reader(*attrs)
|
20
|
+
attrs.each do |attr|
|
21
|
+
define_method(attr) do
|
22
|
+
LSpace[attr]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Preserve lspace of all blocks passed to this function.
|
28
|
+
#
|
29
|
+
# This wraps both the &block parameter, and also any Procs
|
30
|
+
# that are passed into the function directly.
|
31
|
+
#
|
32
|
+
# If you need more complicated logic (i.e. wrapping Procs
|
33
|
+
# that are passed to a function in a dictionary) you're
|
34
|
+
# on your own.
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# class << Thread
|
38
|
+
# in_lspace :new, :start, :fork
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# LSpace.new :user_id => 2 do
|
42
|
+
# Thread.new{ LSpace[:user_id] == 2 }
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# @param [Symbol] *methods The methods to wrap
|
2
46
|
def in_lspace(*methods)
|
3
47
|
methods.each do |method|
|
4
48
|
method_without_lspace = "#{method}_without_lspace"
|
@@ -16,21 +60,18 @@ class Module
|
|
16
60
|
protected method if protected_method_defined?(method_without_lspace)
|
17
61
|
end
|
18
62
|
end
|
19
|
-
|
20
|
-
def attr_lspace(*attrs)
|
21
|
-
attrs.each do |attr|
|
22
|
-
define_method(attr) do
|
23
|
-
LSpace[attr]
|
24
|
-
end
|
25
|
-
|
26
|
-
define_method("#{attr}=") do |value|
|
27
|
-
LSpace[attr] = value
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
63
|
end
|
32
64
|
|
33
65
|
class Proc
|
66
|
+
# Preserve LSpace when this Proc is run.
|
67
|
+
#
|
68
|
+
# @example
|
69
|
+
# todo = LSpace.new :user_id => 2 do
|
70
|
+
# proc{ LSpace[:user_id] }.in_lspace
|
71
|
+
# end
|
72
|
+
# todo.call == 2
|
73
|
+
# @see LSpace.preserve
|
74
|
+
# @return [Proc] A version of self that re-enters LSpace before running
|
34
75
|
def in_lspace
|
35
76
|
LSpace.preserve(&self)
|
36
77
|
end
|
data/lib/lspace/eventmachine.rb
CHANGED
@@ -1,39 +1,79 @@
|
|
1
1
|
require 'eventmachine'
|
2
|
+
require 'lspace'
|
2
3
|
module EventMachine
|
4
|
+
|
5
|
+
# Most of the low-level EventMachine stuff goes through singleton methods on the
|
6
|
+
# EventMachine class.
|
7
|
+
#
|
8
|
+
# We use in_lspace to preserve the LSpace of any blocks passed to these methods.
|
9
|
+
class << self
|
10
|
+
in_lspace :add_timer, :next_tick, :error_handler, :defer, :run, :run_block, :schedule, :fork_reactor
|
11
|
+
in_lspace :add_shutdown_hook if method_defined?(:add_shutdown_hook) # only some versions of EM
|
12
|
+
end
|
13
|
+
|
14
|
+
# Many EM APIs (e.g. em-http-request) are based on deferrables. Preserving lspace for
|
15
|
+
# both callbacks and errbacks makes these libraries "just work".
|
3
16
|
module Deferrable
|
4
17
|
in_lspace :callback, :errback
|
5
18
|
end
|
6
19
|
|
7
|
-
class
|
8
|
-
|
20
|
+
class Connection
|
21
|
+
|
22
|
+
class << self
|
23
|
+
# As EM uses a custom implementation of {new}, the only sane way to
|
24
|
+
# set up the LSpace in advance is to override allocate.
|
25
|
+
alias_method :allocate_without_lspace, :allocate
|
26
|
+
end
|
9
27
|
|
10
|
-
|
28
|
+
# Overridden allocate which sets up a new LSpace.
|
29
|
+
#
|
30
|
+
# Each connection object is run in its own LSpace, which can be
|
31
|
+
# configured by implementing the {setup_lspace} method.
|
32
|
+
def self.allocate
|
11
33
|
allocate_without_lspace.instance_eval do
|
12
34
|
extend EventMachine::LSpacePreserver
|
13
|
-
|
14
|
-
|
35
|
+
LSpace.update do
|
36
|
+
setup_lspace
|
37
|
+
@lspace = LSpace.current
|
38
|
+
end
|
15
39
|
self
|
16
40
|
end
|
17
41
|
end
|
18
42
|
|
43
|
+
# Override this method to setup the LSpace in a manner which you require.
|
44
|
+
#
|
45
|
+
# This method is called before initialize() and before post_init().
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# module EchoServer
|
49
|
+
# def setup_lspace
|
50
|
+
# LSpace.around_filter do |&block|
|
51
|
+
# begin
|
52
|
+
# block.call
|
53
|
+
# rescue => e
|
54
|
+
# self.rescue(e)
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# def rescue(e)
|
60
|
+
# puts "An exception occurred!: #{e}"
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
def setup_lspace; end
|
19
64
|
end
|
20
65
|
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
-
|
66
|
+
# A module that's included at the beginning of the method-resolution chain of
|
67
|
+
# connections which restores LSpace whenever a callback is fired by the eventloop.
|
25
68
|
module LSpacePreserver
|
26
69
|
[:initialize, :post_init, :connection_completed, :receive_data, :ssl_verify_peer, :ssl_handshake_completed].each do |method|
|
27
|
-
define_method(method) { |*a, &b| LSpace.enter(@
|
70
|
+
define_method(method) { |*a, &b| LSpace.enter(@lspace) { super(*a, &b) } }
|
28
71
|
end
|
29
72
|
|
73
|
+
# EM uses the arity of unbind to decide which arguments to pass it.
|
74
|
+
# AFAIK the no-argument version is considerably more popular, so we use that here.
|
30
75
|
[:unbind].each do |method|
|
31
|
-
define_method(method) { LSpace.enter(@
|
76
|
+
define_method(method) { LSpace.enter(@lspace) { super() } }
|
32
77
|
end
|
33
78
|
end
|
34
79
|
end
|
35
|
-
|
36
|
-
class << EventMachine
|
37
|
-
in_lspace :add_timer, :next_tick, :error_handler, :defer, :run, :run_block, :schedule, :fork_reactor
|
38
|
-
in_lspace :add_shutdown_hook if method_defined?(:add_shutdown_hook)
|
39
|
-
end
|
data/lspace.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "lspace"
|
3
|
-
s.version = "0.1
|
3
|
+
s.version = "0.1"
|
4
4
|
s.platform = Gem::Platform::RUBY
|
5
5
|
s.author = "Conrad Irwin"
|
6
6
|
s.email = "conrad.irwin@gmail.com"
|
@@ -9,4 +9,8 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.description = "Provides the convenience of global variables, without the safety concerns."
|
10
10
|
s.files = `git ls-files`.split("\n")
|
11
11
|
s.require_path = "lib"
|
12
|
+
|
13
|
+
s.add_development_dependency 'rspec'
|
14
|
+
s.add_development_dependency 'pry-rescue'
|
15
|
+
s.add_development_dependency 'eventmachine'
|
12
16
|
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe LSpace do
|
3
|
+
before do
|
4
|
+
@lspace = LSpace.new({}, nil)
|
5
|
+
end
|
6
|
+
|
7
|
+
describe ".current" do
|
8
|
+
it "should return the most recently entered LSpace" do
|
9
|
+
LSpace.enter @lspace do
|
10
|
+
LSpace.current.should == @lspace
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should ignore changes on other threads" do
|
15
|
+
LSpace.enter @lspace do
|
16
|
+
Thread.new{ LSpace.current.should_not == @lspace }.join
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe ".update" do
|
22
|
+
it "should enter a new LSpace with the new variables set" do
|
23
|
+
LSpace.update(:foo => 5) do
|
24
|
+
LSpace[:foo].should == 5
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should enter a new LSpace which delegates to the current parent" do
|
29
|
+
LSpace.update(:foo => 5) do
|
30
|
+
LSpace.update(:bar => 4) do
|
31
|
+
LSpace[:foo].should == 5
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe ".clean" do
|
38
|
+
it "should enter a new LSpace with no parent" do
|
39
|
+
LSpace.update(:foo => 5) do
|
40
|
+
LSpace.clean do
|
41
|
+
LSpace[:foo].should == nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe ".enter" do
|
48
|
+
it "should update LSpace.current" do
|
49
|
+
LSpace.enter(@lspace) do
|
50
|
+
LSpace.current.should == @lspace
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should revert that change at the end of the block" do
|
55
|
+
lspace = LSpace.current
|
56
|
+
LSpace.enter(@lspace) do
|
57
|
+
# yada yada
|
58
|
+
end
|
59
|
+
LSpace.current.should == lspace
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should revert that change even if the block raises an exception" do
|
63
|
+
lspace = LSpace.current
|
64
|
+
lambda do
|
65
|
+
LSpace.enter(@lspace) do
|
66
|
+
raise "OOPS"
|
67
|
+
end
|
68
|
+
end.should raise_error /OOPS/
|
69
|
+
LSpace.current.should == lspace
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should not effect other threads" do
|
73
|
+
LSpace.enter(@lspace) do
|
74
|
+
Thread.new{ LSpace.current.should_not == @lspace }.join
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should not effect other fibers" do
|
79
|
+
LSpace.enter(@lspace) do
|
80
|
+
Fiber.new{ LSpace.current.should_not == @lspace }.resume
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe ".preserve" do
|
86
|
+
it "should delegate to LSpace.current" do
|
87
|
+
LSpace.current.should_receive(:wrap).once
|
88
|
+
LSpace.preserve{ 5 }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe ".[]" do
|
93
|
+
it "should delegate to LSpace.current" do
|
94
|
+
LSpace.current.should_receive(:[]).once.with(:foo).and_return(:bar)
|
95
|
+
LSpace[:foo].should == :bar
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe ".[]=" do
|
100
|
+
it "should delegate to LSpace.current" do
|
101
|
+
LSpace.current.should_receive(:[]=).once.with(:foo, :bar)
|
102
|
+
LSpace[:foo] = :bar
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe ".around_filter" do
|
107
|
+
it "should delegate to LSpace.current" do
|
108
|
+
@lspace.should_receive(:around_filter).once
|
109
|
+
LSpace.enter @lspace do
|
110
|
+
LSpace.around_filter do |&block|
|
111
|
+
block.call
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Module do
|
4
|
+
describe "#lspace_reader" do
|
5
|
+
it "should define a reader for LSpace" do
|
6
|
+
klass = Class.new{ lspace_reader :user_id }
|
7
|
+
|
8
|
+
LSpace.update(:user_id => 8) do
|
9
|
+
klass.new.user_id.should == 8
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#in_lspace" do
|
15
|
+
before do
|
16
|
+
@klass = Class.new do
|
17
|
+
def initialize(task=nil, &block)
|
18
|
+
@block = block || task
|
19
|
+
end
|
20
|
+
in_lspace :initialize
|
21
|
+
|
22
|
+
def call
|
23
|
+
@block.call
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def private_test(&block)
|
29
|
+
block
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def protected_test(&block)
|
35
|
+
block
|
36
|
+
end
|
37
|
+
|
38
|
+
in_lspace :private_test, :protected_test
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should automatically preserve LSpace for blocks that are passed in" do
|
43
|
+
@task = LSpace.update :user_id => 6 do
|
44
|
+
@klass.new{ LSpace[:user_id] }
|
45
|
+
end
|
46
|
+
|
47
|
+
@task.call.should == 6
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should automatically preserve LSpace for procs that are passed in" do
|
51
|
+
@task = LSpace.update :user_id => 6 do
|
52
|
+
@klass.new proc{ LSpace[:user_id] }
|
53
|
+
end
|
54
|
+
|
55
|
+
@task.call.should == 6
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should preserve visibility of methods" do
|
59
|
+
@klass.private_method_defined?(:private_test).should == true
|
60
|
+
@klass.protected_method_defined?(:protected_test).should == true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe Proc do
|
66
|
+
describe "#in_lspace" do
|
67
|
+
it "should create a wrapper which preserves the LSpace" do
|
68
|
+
p = LSpace.update(:job_id => 19) do
|
69
|
+
lambda{ LSpace[:job_id] }.in_lspace
|
70
|
+
end
|
71
|
+
|
72
|
+
p.call.should == 19
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|