Skip to content

Commit 3ea58d6

Browse files
author
Lee Richmond
committed
Treat context like a global
There are a number of use cases - such as getting the current user - where Resources and other objects need access to the context (in Rails, the context is the controller). Resource#with_context made sure the context was available for filter guards, for example. This is now the same dev-facing API, but the context is set on Thread.current rather than the Resource instance. This is because the same context needs to be available to all nested resources, serializers, etc. 'context' is more of a global concept, and this code treats it as such. The final product is you can do something like this in a Resource: ```ruby def create(attributes) raise 'not authorized!' unless context.current_user.role == 'admin' super end ``` ...regardless of whether this resource is being created from its corresponding endpoint, or side-posted 3 levels deep.
1 parent c08b3e5 commit 3ea58d6

File tree

7 files changed

+46
-34
lines changed

7 files changed

+46
-34
lines changed

lib/jsonapi_compliable.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,25 @@ def self.included(klass)
4242
include Base
4343
end
4444
end
45+
46+
# @api private
47+
def self.context
48+
Thread.current[:context] ||= {}
49+
end
50+
51+
# @api private
52+
def self.context=(val)
53+
Thread.current[:context] = val
54+
end
55+
56+
# @api private
57+
def self.with_context(obj, namespace)
58+
begin
59+
prior = self.context
60+
self.context = { object: obj, namespace: namespace }
61+
yield
62+
ensure
63+
self.context = prior
64+
end
65+
end
4566
end

lib/jsonapi_compliable/base.rb

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,8 @@ def query_hash
8888
# @api private
8989
# @yieldreturn Code to run within the current context
9090
def wrap_context
91-
if self.class._jsonapi_compliable
92-
jsonapi_resource.with_context(self, action_name.to_sym) do
93-
yield
94-
end
91+
jsonapi_resource.with_context(self, action_name.to_sym) do
92+
yield
9593
end
9694
end
9795

lib/jsonapi_compliable/resource.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -446,12 +446,8 @@ def self.config
446446
# @param object The context (Rails controller or equivalent)
447447
# @param namespace One of index/show/etc
448448
def with_context(object, namespace = nil)
449-
begin
450-
prior = context
451-
@context = { object: object, namespace: namespace }
449+
JsonapiCompliable.with_context(object, namespace) do
452450
yield
453-
ensure
454-
@context = prior
455451
end
456452
end
457453

@@ -462,7 +458,11 @@ def with_context(object, namespace = nil)
462458
# @see #with_context
463459
# @return [Hash] the context hash
464460
def context
465-
@context || {}
461+
JsonapiCompliable.context[:object]
462+
end
463+
464+
def context_namespace
465+
JsonapiCompliable.context[:namespace]
466466
end
467467

468468
# Build a scope using this Resource configuration
@@ -592,7 +592,7 @@ def association_names
592592
def allowed_sideloads(namespace = nil)
593593
return {} unless sideloading
594594

595-
namespace ||= context[:namespace]
595+
namespace ||= context_namespace
596596
sideloads = sideloading.to_hash[:base]
597597
if !sideload_whitelist.empty? && namespace
598598
sideloads = Util::IncludeParams.scrub(sideloads, sideload_whitelist[namespace])

lib/jsonapi_compliable/scoping/filterable.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def find_filter!(name)
1414
resource.filters.find { |_name, opts| opts[:aliases].include?(name.to_sym) }
1515
raise JsonapiCompliable::Errors::BadFilter unless filter_name
1616
if guard = filter_value[:if]
17-
raise JsonapiCompliable::Errors::BadFilter if resource.context[:object].send(guard) == false
17+
raise JsonapiCompliable::Errors::BadFilter if resource.context.send(guard) == false
1818
end
1919
{ filter_name => filter_value }
2020
end

spec/jsonapi_compliable_spec.rb

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -78,24 +78,8 @@ def config(obj)
7878

7979
it 'wraps in the resource context' do
8080
instance.wrap_context do
81-
expect(instance.jsonapi_resource.context).to eq({
82-
object: instance,
83-
namespace: :index
84-
})
85-
end
86-
end
87-
88-
context 'when the class does not have a resource' do
89-
let(:klass) do
90-
Class.new do
91-
include JsonapiCompliable
92-
end
93-
end
94-
95-
it 'does nothing' do
96-
instance.wrap_context do
97-
expect(instance.resource).to be_nil
98-
end
81+
expect(instance.jsonapi_resource.context).to eq(instance)
82+
expect(instance.jsonapi_resource.context_namespace).to eq(:index)
9983
end
10084
end
10185
end

spec/resource_spec.rb

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,28 @@
4646
it 'sets/resets correct context' do
4747
dbl = double
4848
instance.with_context(dbl, :index) do
49-
expect(instance.context).to eq(object: dbl, namespace: :index)
49+
expect(instance.context).to eq(dbl)
50+
expect(instance.context_namespace).to eq(:index)
5051
end
51-
expect(instance.context).to eq({})
52+
expect(instance.context).to be_nil
53+
expect(instance.context_namespace).to be_nil
5254
end
5355

5456
context 'when an error' do
57+
around do |e|
58+
JsonapiCompliable.with_context('orig', 'orig namespace') do
59+
e.run
60+
end
61+
end
62+
5563
it 'resets the context' do
5664
expect {
5765
instance.with_context({}, :index) do
5866
raise 'foo'
5967
end
6068
}.to raise_error('foo')
61-
expect(instance.context).to eq({})
69+
expect(instance.context).to eq('orig')
70+
expect(instance.context_namespace).to eq('orig namespace')
6271
end
6372
end
6473
end

spec/support/scope_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
let(:scope) { resource.build_scope(scope_object, query) }
1515

1616
def render(object, opts = {})
17-
opts[:expose] = { context: resource.context[:object] }
17+
opts[:expose] = { context: resource.context }
1818
opts = JsonapiCompliable::Util::RenderOptions.generate(object, query.to_hash[:authors], opts)
1919
resolved = opts.delete(:jsonapi)
2020
raw_json = JSONAPI::Serializable::Renderer.render(resolved, opts)

0 commit comments

Comments
 (0)