Skip to content

Commit 8032db6

Browse files
committed
Add SSHKit::Backend.current
The point of SSHKit::Backend.current is to give code access to the current Backend without having to pass around a reference to it. For example, consider a `git` helper that executes a git command: def git(*args) SSHKit::Backend.current.execute(:git, *args) end Thanks to `current`, we can use this git method wherever we are in an `on` block, without having to pass a reference to `self`: on release_roles(:all) do git "status" end Without the thread local, the same task would be much more awkward due to the necessary passing of `self`: def git(backend, *args) backend.execute(:git, *args) end on release_roles(:all) do git self, "status" end To someone not intimately familiar with SSHKit, what `self` in this context is confusing and not obvious at all. It is because SSHKit uses `instance_exec` within the `on` block that `self` magically transforms. Better to avoid using `self` altogether and prefer the thread local.
1 parent 67eb3a0 commit 8032db6

File tree

3 files changed

+42
-0
lines changed

3 files changed

+42
-0
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ appear at the top.
1717
[PR #308](https://github.com/capistrano/sshkit/pull/308) @mattbrictson
1818
* `SSHKit::Backend::Printer#test` now always returns true
1919
[PR #312](https://github.com/capistrano/sshkit/pull/312) @mikz
20+
* Add `SSHKit::Backend.current` so that Capistrano plugin authors can refactor
21+
helper methods and still have easy access to the currently-executing Backend
22+
without having to use global variables.
23+
[PR #319](https://github.com/capistrano/sshkit/pull/319) @mattbrictson
2024

2125
## 1.8.1
2226

lib/sshkit/backends/abstract.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ module Backend
44

55
MethodUnavailableError = Class.new(SSHKit::StandardError)
66

7+
# The Backend instance that is running in the current thread. If no Backend
8+
# is running, returns `nil` instead.
9+
#
10+
# Example:
11+
#
12+
# on(:local) do
13+
# self == SSHKit::Backend.current # => true
14+
# end
15+
#
16+
def self.current
17+
Thread.current["sshkit_backend"]
18+
end
19+
720
class Abstract
821

922
extend Forwardable
@@ -12,7 +25,10 @@ class Abstract
1225
attr_reader :host
1326

1427
def run
28+
Thread.current["sshkit_backend"] = self
1529
instance_exec(@host, &@block)
30+
ensure
31+
Thread.current["sshkit_backend"] = nil
1632
end
1733

1834
def initialize(host, &block)

test/unit/backends/test_abstract.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,28 @@ def test_invoke_raises_no_method_error
131131
end
132132
end
133133

134+
def test_current_refers_to_currently_executing_backend
135+
backend = nil
136+
current = nil
137+
138+
backend = ExampleBackend.new do
139+
backend = self
140+
current = SSHKit::Backend.current
141+
end
142+
backend.run
143+
144+
assert_equal(backend, current)
145+
end
146+
147+
def test_current_is_nil_outside_of_the_block
148+
backend = ExampleBackend.new do
149+
# nothing
150+
end
151+
backend.run
152+
153+
assert_nil(SSHKit::Backend.current)
154+
end
155+
134156
# Use a concrete ExampleBackend rather than a mock for improved assertion granularity
135157
class ExampleBackend < Abstract
136158
attr_writer :full_stdout

0 commit comments

Comments
 (0)