Skip to content
This repository was archived by the owner on Oct 16, 2019. It is now read-only.

Commit c617777

Browse files
committed
Improve JSON serialization compatibility of User
Previously, the MembershipCache was a subclass of Hash. When serializing to and deserializing from JSON, the MembershipCache would become a Hash and blow up. This is a problem in warden-github-rails where I switched to a custom warden (de)serialization in order to properly support JSON serialization and also to support mocked users in test environments between requests[1]. [1]: fphilipe/warden-github-rails@db82470
1 parent 3e9aa5f commit c617777

File tree

3 files changed

+51
-25
lines changed

3 files changed

+51
-25
lines changed

lib/warden/github/membership_cache.rb

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ module GitHub
33
# A hash subclass that acts as a cache for organization and team
44
# membership states. Only membership states that are true are cached. These
55
# are invalidated after a certain time.
6-
class MembershipCache < ::Hash
6+
class MembershipCache
77
CACHE_TIMEOUT = 60 * 5
88

9+
def initialize(data)
10+
@data = data
11+
end
12+
913
# Fetches a membership status by type and id (e.g. 'org', 'my_company')
1014
# from cache. If no cached value is present or if the cached value
1115
# expired, the block will be invoked and the return value, if true,
@@ -27,10 +31,10 @@ def fetch_membership(type, id)
2731
private
2832

2933
def cached_membership_valid?(type, id)
30-
timestamp = fetch(type).fetch(id)
34+
timestamp = @data.fetch(type).fetch(id)
3135

3236
if Time.now.to_i > timestamp + CACHE_TIMEOUT
33-
fetch(type).delete(id)
37+
@data.fetch(type).delete(id)
3438
false
3539
else
3640
true
@@ -40,7 +44,7 @@ def cached_membership_valid?(type, id)
4044
end
4145

4246
def cache_membership(type, id)
43-
hash = self[type] ||= {}
47+
hash = @data[type] ||= {}
4448
hash[id] = Time.now.to_i
4549
end
4650
end

lib/warden/github/user.rb

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module Warden
22
module GitHub
3-
class User < Struct.new(:attribs, :token, :browser_session_id)
3+
class User < Struct.new(:attribs, :token, :browser_session_id, :memberships)
44
ATTRIBUTES = %w[id login name gravatar_id avatar_url email company site_admin].freeze
55

66
def self.load(access_token, browser_session_id = nil)
@@ -32,7 +32,7 @@ def marshal_load(hash)
3232
#
3333
# Returns: true if the user is publicized as an org member
3434
def organization_public_member?(org_name)
35-
memberships.fetch_membership(:org_pub, org_name) do
35+
membership_cache.fetch_membership(:org_pub, org_name) do
3636
api.organization_public_member?(org_name, login)
3737
end
3838
end
@@ -46,7 +46,7 @@ def organization_public_member?(org_name)
4646
#
4747
# Returns: true if the user has access, false otherwise
4848
def organization_member?(org_name)
49-
memberships.fetch_membership(:org, org_name) do
49+
membership_cache.fetch_membership(:org, org_name) do
5050
api.organization_member?(org_name, login)
5151
end
5252
end
@@ -57,7 +57,7 @@ def organization_member?(org_name)
5757
#
5858
# Returns: true if the user has access, false otherwise
5959
def team_member?(team_id)
60-
memberships.fetch_membership(:team, team_id) do
60+
membership_cache.fetch_membership(:team, team_id) do
6161
api.team_member?(team_id, login)
6262
end
6363
end
@@ -105,8 +105,9 @@ def api
105105

106106
private
107107

108-
def memberships
109-
attribs['member'] ||= MembershipCache.new
108+
def membership_cache
109+
self.memberships ||= {}
110+
@membership_cache ||= MembershipCache.new(memberships)
110111
end
111112
end
112113
end

spec/unit/membership_cache_spec.rb

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
require 'spec_helper'
22

33
describe Warden::GitHub::MembershipCache do
4-
subject(:cache) do
5-
described_class.new
6-
end
7-
84
describe '#fetch_membership' do
95
it 'returns false by default' do
6+
cache = described_class.new({})
107
cache.fetch_membership('foo', 'bar').should be_falsey
118
end
129

1310
context 'when cache valid' do
14-
before do
15-
cache['foo'] = {}
16-
cache['foo']['bar'] = Time.now.to_i - described_class::CACHE_TIMEOUT + 5
11+
let(:cache) do
12+
described_class.new('foo' => {
13+
'bar' => Time.now.to_i - described_class::CACHE_TIMEOUT + 5
14+
})
1715
end
1816

1917
it 'returns true' do
@@ -27,9 +25,10 @@
2725
end
2826

2927
context 'when cache expired' do
30-
before do
31-
cache['foo'] = {}
32-
cache['foo']['bar'] = Time.now.to_i - described_class::CACHE_TIMEOUT - 5
28+
let(:cache) do
29+
described_class.new('foo' => {
30+
'bar' => Time.now.to_i - described_class::CACHE_TIMEOUT - 5
31+
})
3332
end
3433

3534
context 'when no block given' do
@@ -38,25 +37,47 @@
3837
end
3938
end
4039

41-
it 'deletes the cached value' do
42-
cache.fetch_membership('foo', 'bar')
43-
cache['foo'].should_not have_key('bar')
44-
end
45-
4640
it 'invokes the block' do
4741
expect { |b| cache.fetch_membership('foo', 'bar', &b) }.
4842
to yield_control
4943
end
5044
end
5145

5246
it 'caches the value when block returns true' do
47+
cache = described_class.new({})
5348
cache.fetch_membership('foo', 'bar') { true }
5449
cache.fetch_membership('foo', 'bar').should be_truthy
5550
end
5651

5752
it 'does not cache the value when block returns false' do
53+
cache = described_class.new({})
5854
cache.fetch_membership('foo', 'bar') { false }
5955
cache.fetch_membership('foo', 'bar').should be_falsey
6056
end
6157
end
58+
59+
it 'uses the hash that is passed to the initializer as storage' do
60+
time = Time.now.to_i
61+
hash = {
62+
'foo' => {
63+
'valid' => time,
64+
'timedout' => time - described_class::CACHE_TIMEOUT - 5
65+
}
66+
}
67+
cache = described_class.new(hash)
68+
69+
# Verify that existing data in the hash is used:
70+
expect(cache.fetch_membership('foo', 'valid')).to be_true
71+
expect(cache.fetch_membership('foo', 'timedout')).to be_false
72+
73+
# Add new data to the hash:
74+
cache.fetch_membership('foo', 'new') { true }
75+
cache.fetch_membership('foo', 'false') { false }
76+
77+
# Verify the final hash state:
78+
expect(hash).to eq('foo' => {
79+
'valid' => time,
80+
'new' => time
81+
})
82+
end
6283
end

0 commit comments

Comments
 (0)