skull_island 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +39 -0
- data/.travis.yml +9 -2
- data/Gemfile +3 -1
- data/Gemfile.lock +127 -0
- data/README.md +348 -2
- data/Rakefile +13 -3
- data/bin/console +4 -3
- data/lib/core_extensions/string/transformations.rb +30 -0
- data/lib/skull_island/api_client.rb +36 -0
- data/lib/skull_island/api_client_base.rb +86 -0
- data/lib/skull_island/api_exception.rb +7 -0
- data/lib/skull_island/exceptions/api_client_not_configured.rb +9 -0
- data/lib/skull_island/exceptions/immutable_modification.rb +9 -0
- data/lib/skull_island/exceptions/invalid_arguments.rb +9 -0
- data/lib/skull_island/exceptions/invalid_cache_size.rb +9 -0
- data/lib/skull_island/exceptions/invalid_options.rb +9 -0
- data/lib/skull_island/exceptions/invalid_property.rb +9 -0
- data/lib/skull_island/exceptions/invalid_where_query.rb +9 -0
- data/lib/skull_island/exceptions/new_instance_with_id.rb +9 -0
- data/lib/skull_island/helpers/api_client.rb +64 -0
- data/lib/skull_island/helpers/resource.rb +178 -0
- data/lib/skull_island/helpers/resource_class.rb +74 -0
- data/lib/skull_island/lru_cache.rb +175 -0
- data/lib/skull_island/resource.rb +198 -0
- data/lib/skull_island/resource_collection.rb +193 -0
- data/lib/skull_island/resources/certificate.rb +36 -0
- data/lib/skull_island/resources/consumer.rb +20 -0
- data/lib/skull_island/resources/plugin.rb +144 -0
- data/lib/skull_island/resources/route.rb +83 -0
- data/lib/skull_island/resources/service.rb +94 -0
- data/lib/skull_island/resources/upstream.rb +129 -0
- data/lib/skull_island/resources/upstream_target.rb +86 -0
- data/lib/skull_island/rspec/fake_api_client.rb +63 -0
- data/lib/skull_island/rspec.rb +3 -0
- data/lib/skull_island/simple_api_client.rb +18 -0
- data/lib/skull_island/validations/api_client.rb +45 -0
- data/lib/skull_island/validations/resource.rb +24 -0
- data/lib/skull_island/version.rb +3 -1
- data/lib/skull_island.rb +47 -1
- data/skull_island.gemspec +16 -13
- metadata +66 -7
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SkullIsland
|
4
|
+
# The API Client Base class
|
5
|
+
class APIClientBase
|
6
|
+
attr_reader :server, :base_uri
|
7
|
+
attr_accessor :username, :password
|
8
|
+
|
9
|
+
include Validations::APIClient
|
10
|
+
include Helpers::APIClient
|
11
|
+
|
12
|
+
def api_uri
|
13
|
+
@api_uri ||= URI.parse(server)
|
14
|
+
@api_uri.path = base_uri if base_uri
|
15
|
+
@api_uri
|
16
|
+
end
|
17
|
+
|
18
|
+
def authenticated?
|
19
|
+
raise Exceptions::APIClientNotConfigured unless configured?
|
20
|
+
|
21
|
+
@username && @password ? true : false
|
22
|
+
end
|
23
|
+
|
24
|
+
def configured?
|
25
|
+
@configured ? true : false
|
26
|
+
end
|
27
|
+
|
28
|
+
def json_headers
|
29
|
+
{ content_type: :json, accept: :json }
|
30
|
+
end
|
31
|
+
|
32
|
+
def get(uri, data = nil)
|
33
|
+
client_action do |client|
|
34
|
+
if data
|
35
|
+
JSON.parse client[uri].get(json_headers.merge(params: data))
|
36
|
+
else
|
37
|
+
JSON.parse client[uri].get(json_headers)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def post(uri, data = nil)
|
43
|
+
client_action do |client|
|
44
|
+
if data
|
45
|
+
JSON.parse client[uri].post(json_escape(data.to_json), json_headers)
|
46
|
+
else
|
47
|
+
JSON.parse client[uri].post(nil, json_headers)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def patch(uri, data)
|
53
|
+
client_action do |client|
|
54
|
+
response = client[uri].patch(json_escape(data.to_json), json_headers)
|
55
|
+
if response && !response.empty?
|
56
|
+
JSON.parse(response)
|
57
|
+
else
|
58
|
+
true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def put(uri, data)
|
64
|
+
client_action do |client|
|
65
|
+
client[uri].put(json_escape(data.to_json), json_headers)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def client_action
|
72
|
+
raise Exceptions::APIClientNotConfigured unless configured?
|
73
|
+
|
74
|
+
yield connection
|
75
|
+
end
|
76
|
+
|
77
|
+
# Don't bother creating a connection until we need one
|
78
|
+
def connection
|
79
|
+
@connection ||= if authenticated?
|
80
|
+
RestClient::Resource.new(api_uri.to_s, @username, @password)
|
81
|
+
else
|
82
|
+
RestClient::Resource.new(api_uri.to_s)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SkullIsland
|
4
|
+
module Helpers
|
5
|
+
# Simple helper methods for the API Client
|
6
|
+
module APIClient
|
7
|
+
def about_service
|
8
|
+
get '/'
|
9
|
+
end
|
10
|
+
|
11
|
+
def server_status
|
12
|
+
get '/status'
|
13
|
+
end
|
14
|
+
|
15
|
+
def cache(key)
|
16
|
+
symbolized_key = key.to_sym
|
17
|
+
if !@cache.has?(symbolized_key) && block_given?
|
18
|
+
result = yield(self)
|
19
|
+
@cache.store(symbolized_key, result)
|
20
|
+
elsif !@cache.has?(symbolized_key)
|
21
|
+
return nil
|
22
|
+
end
|
23
|
+
@cache.retrieve(symbolized_key)
|
24
|
+
end
|
25
|
+
|
26
|
+
def invalidate_cache_for(key)
|
27
|
+
symbolized_key = key.to_sym
|
28
|
+
@cache.invalidate(symbolized_key)
|
29
|
+
end
|
30
|
+
|
31
|
+
def lru_cache
|
32
|
+
@cache
|
33
|
+
end
|
34
|
+
|
35
|
+
# Substitute characters with their JSON-supported versions
|
36
|
+
# @return [String]
|
37
|
+
def json_escape(string)
|
38
|
+
json_escape = {
|
39
|
+
'&' => '\u0026',
|
40
|
+
'>' => '\u003e',
|
41
|
+
'<' => '\u003c',
|
42
|
+
'%' => '\u0025',
|
43
|
+
"\u2028" => '\u2028',
|
44
|
+
"\u2029" => '\u2029'
|
45
|
+
}
|
46
|
+
json_escape_regex = /[\u2028\u2029&><%]/u
|
47
|
+
|
48
|
+
string.to_s.gsub(json_escape_regex, json_escape)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Provides access to the "raw" underlying rest-client
|
52
|
+
# @return [RestClient::Resource]
|
53
|
+
def raw
|
54
|
+
connection
|
55
|
+
end
|
56
|
+
|
57
|
+
# The API Client version (uses Semantic Versioning)
|
58
|
+
# @return [String]
|
59
|
+
def version
|
60
|
+
VERSION
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SkullIsland
|
4
|
+
module Helpers
|
5
|
+
# Simple helper methods for Resources
|
6
|
+
module Resource
|
7
|
+
def datetime_from_params(params, actual_key)
|
8
|
+
DateTime.new(
|
9
|
+
params["#{actual_key}(1i)"].to_i,
|
10
|
+
params["#{actual_key}(2i)"].to_i,
|
11
|
+
params["#{actual_key}(3i)"].to_i,
|
12
|
+
params["#{actual_key}(4i)"].to_i,
|
13
|
+
params["#{actual_key}(5i)"].to_i
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def fresh?
|
18
|
+
!tainted?
|
19
|
+
end
|
20
|
+
|
21
|
+
def host_regex
|
22
|
+
/^(([\w]|[\w][\w\-]*[\w])\.)*([\w]|[\w][\w\-]*[\w])$/
|
23
|
+
end
|
24
|
+
|
25
|
+
def id_property
|
26
|
+
self.class.properties.select { |_, opts| opts[:id_property] }.keys.first || 'id'
|
27
|
+
end
|
28
|
+
|
29
|
+
def id
|
30
|
+
@entity[id_property.to_s]
|
31
|
+
end
|
32
|
+
|
33
|
+
def immutable?
|
34
|
+
self.class.immutable?
|
35
|
+
end
|
36
|
+
|
37
|
+
# ActiveRecord ActiveModel::Name compatibility method
|
38
|
+
def model_name
|
39
|
+
self.class
|
40
|
+
end
|
41
|
+
|
42
|
+
def new?
|
43
|
+
!@entity.key?(id_property.to_s)
|
44
|
+
end
|
45
|
+
|
46
|
+
# ActiveRecord ActiveModel::Model compatibility method
|
47
|
+
def persisted?
|
48
|
+
!new?
|
49
|
+
end
|
50
|
+
|
51
|
+
def postprocess_created_at(value)
|
52
|
+
Time.at(value).utc.to_datetime
|
53
|
+
end
|
54
|
+
|
55
|
+
def postprocess_updated_at(value)
|
56
|
+
Time.at(value).utc.to_datetime
|
57
|
+
end
|
58
|
+
|
59
|
+
def properties
|
60
|
+
self.class.properties
|
61
|
+
end
|
62
|
+
|
63
|
+
def required_properties
|
64
|
+
properties.select { |_key, value| value[:required] }
|
65
|
+
end
|
66
|
+
|
67
|
+
def tainted?
|
68
|
+
@tainted ? true : false
|
69
|
+
end
|
70
|
+
|
71
|
+
# ActiveRecord ActiveModel::Conversion compatibility method
|
72
|
+
def to_key
|
73
|
+
new? ? [] : [id]
|
74
|
+
end
|
75
|
+
|
76
|
+
# ActiveRecord ActiveModel::Conversion compatibility method
|
77
|
+
def to_model
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
# ActiveRecord ActiveModel::Conversion compatibility method
|
82
|
+
def to_param
|
83
|
+
new? ? nil : id.to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
def destroy
|
87
|
+
raise Exceptions::ImmutableModification if immutable?
|
88
|
+
|
89
|
+
unless new?
|
90
|
+
@api_client.delete(relative_uri.to_s)
|
91
|
+
@api_client.invalidate_cache_for(relative_uri.to_s)
|
92
|
+
@lazy = false
|
93
|
+
@tainted = true
|
94
|
+
@entity.delete('id')
|
95
|
+
end
|
96
|
+
true
|
97
|
+
end
|
98
|
+
|
99
|
+
def reload
|
100
|
+
if new?
|
101
|
+
# Can't reload a new resource
|
102
|
+
false
|
103
|
+
else
|
104
|
+
@api_client.invalidate_cache_for(relative_uri.to_s)
|
105
|
+
entity_data = @api_client.cache(relative_uri.to_s) do |client|
|
106
|
+
client.get(relative_uri.to_s)
|
107
|
+
end
|
108
|
+
@entity = entity_data
|
109
|
+
@lazy = false
|
110
|
+
@tainted = false
|
111
|
+
true
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def save
|
116
|
+
saveable_data = prune_for_save(@entity)
|
117
|
+
validate_required_properties(saveable_data)
|
118
|
+
|
119
|
+
if new?
|
120
|
+
@entity = @api_client.post(save_uri.to_s, saveable_data)
|
121
|
+
@lazy = true
|
122
|
+
else
|
123
|
+
@api_client.invalidate_cache_for(relative_uri.to_s)
|
124
|
+
@entity = @api_client.put(relative_uri, saveable_data)
|
125
|
+
end
|
126
|
+
@tainted = false
|
127
|
+
true
|
128
|
+
end
|
129
|
+
|
130
|
+
def save_uri
|
131
|
+
self.class.relative_uri
|
132
|
+
end
|
133
|
+
|
134
|
+
# ActiveRecord ActiveModel compatibility method
|
135
|
+
def update(params)
|
136
|
+
new_params = {}
|
137
|
+
# need to convert multi-part datetime params
|
138
|
+
params.each do |key, value|
|
139
|
+
if /([^(]+)\(1i/.match?(key)
|
140
|
+
actual_key = key.match(/([^(]+)\(/)[1]
|
141
|
+
new_params[actual_key] = datetime_from_params(params, actual_key)
|
142
|
+
else
|
143
|
+
new_params[key] = value
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
new_params.each do |key, value|
|
148
|
+
setter_key = "#{key}=".to_sym
|
149
|
+
raise Exceptions::InvalidProperty unless respond_to?(setter_key)
|
150
|
+
|
151
|
+
send(setter_key, value)
|
152
|
+
end
|
153
|
+
save
|
154
|
+
end
|
155
|
+
|
156
|
+
def <=>(other)
|
157
|
+
if id < other.id
|
158
|
+
-1
|
159
|
+
elsif id > other.id
|
160
|
+
1
|
161
|
+
elsif id == other.id
|
162
|
+
0
|
163
|
+
else
|
164
|
+
raise Exceptions::InvalidArguments
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def prune_for_save(data)
|
169
|
+
data.reject do |k, v|
|
170
|
+
k.to_sym == id_property ||
|
171
|
+
!properties[k.to_sym] ||
|
172
|
+
properties[k.to_sym][:read_only] ||
|
173
|
+
v.nil?
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SkullIsland
|
4
|
+
module Helpers
|
5
|
+
# Simple helper class methods for Resource
|
6
|
+
module ResourceClass
|
7
|
+
# Determine a list of names to use to access a resource entity attribute
|
8
|
+
# @param original_name [String,Symbol] the name of the underlying attribute
|
9
|
+
# @param opts [Hash] property options as defined in a {Resource} subclass
|
10
|
+
# @return [Array<Symbol>] the list of names
|
11
|
+
def determine_getter_names(original_name, opts)
|
12
|
+
names = []
|
13
|
+
names << (opts[:type] == :boolean ? "#{original_name}?" : original_name)
|
14
|
+
if opts[:as]
|
15
|
+
Array(opts[:as]).each do |new_name|
|
16
|
+
names << (opts[:type] == :boolean ? "#{new_name}?" : new_name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
names.map(&:to_sym).uniq
|
20
|
+
end
|
21
|
+
|
22
|
+
# Determine a list of names to use to set a resource entity attribute
|
23
|
+
# @param original_name [String,Symbol] the name of the underlying attribute
|
24
|
+
# @param opts [Hash] property options as defined in a {Resource} subclass
|
25
|
+
# @return [Array<Symbol>] the list of names
|
26
|
+
def determine_setter_names(original_name, opts)
|
27
|
+
names = ["#{original_name}="]
|
28
|
+
names.concat(Array(opts[:as]).map { |new_name| "#{new_name}=" }) if opts[:as]
|
29
|
+
names.map(&:to_sym).uniq
|
30
|
+
end
|
31
|
+
|
32
|
+
# Produce a more human-readable representation of {#i18n_key}
|
33
|
+
# @note ActiveRecord ActiveModel::Name compatibility method
|
34
|
+
# @return [String]
|
35
|
+
def human
|
36
|
+
i18n_key.humanize
|
37
|
+
end
|
38
|
+
|
39
|
+
# Check if a resource class is immutable
|
40
|
+
def immutable?
|
41
|
+
@immutable ||= false
|
42
|
+
end
|
43
|
+
|
44
|
+
# A mock internationalization key based on the class name
|
45
|
+
# @note ActiveRecord ActiveModel::Name compatibility method
|
46
|
+
# @return [String]
|
47
|
+
def i18n_key
|
48
|
+
name.split('::').last.to_underscore
|
49
|
+
end
|
50
|
+
|
51
|
+
alias singular_route_key i18n_key
|
52
|
+
|
53
|
+
# A symbolized version of {#i18n_key}
|
54
|
+
# @note ActiveRecord ActiveModel::Name compatibility method
|
55
|
+
# @return [Symbol]
|
56
|
+
def param_key
|
57
|
+
i18n_key.to_sym
|
58
|
+
end
|
59
|
+
|
60
|
+
# All the properties defined for this Resource class
|
61
|
+
# @return [Hash{Symbol => Hash}]
|
62
|
+
def properties
|
63
|
+
@properties ||= {}
|
64
|
+
end
|
65
|
+
|
66
|
+
# A route key for building URLs
|
67
|
+
# @note ActiveRecord ActiveModel::Name compatibility method
|
68
|
+
# @return [String]
|
69
|
+
def route_key
|
70
|
+
i18n_key.en.plural
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SkullIsland
|
4
|
+
# A very simple Least Recently Used (LRU) cache implementation. Stores data
|
5
|
+
# in a Hash, uses a dedicated Array for storing and sorting keys (and
|
6
|
+
# implementing the LRU algorithm), and doesn't bother storing access
|
7
|
+
# information for cache data. It stores hit and miss counts for the
|
8
|
+
# entire cache (not for individual keys). It also uses three mutexes for
|
9
|
+
# thread-safety: a write lock, a read lock, and a metadata lock.
|
10
|
+
class LRUCache
|
11
|
+
attr_reader :max_size, :keys
|
12
|
+
|
13
|
+
# @raise [Exceptions::InvalidCacheSize] if the max_size isn't an Integer
|
14
|
+
def initialize(max_size = 100)
|
15
|
+
raise Exceptions::InvalidCacheSize unless max_size.is_a?(Integer)
|
16
|
+
|
17
|
+
@max_size = max_size
|
18
|
+
@hits = 0
|
19
|
+
@misses = 0
|
20
|
+
@keys = []
|
21
|
+
@data = {}
|
22
|
+
@read_mutex = Mutex.new
|
23
|
+
@write_mutex = Mutex.new
|
24
|
+
@meta_mutex = Mutex.new
|
25
|
+
end
|
26
|
+
|
27
|
+
# Does the cache contain the requested item?
|
28
|
+
# This doesn't count against cache misses
|
29
|
+
# @param key [Symbol] the index of the potentially cached object
|
30
|
+
def has?(key)
|
31
|
+
@meta_mutex.synchronize { @keys.include?(key) }
|
32
|
+
end
|
33
|
+
|
34
|
+
alias has_key? has?
|
35
|
+
alias include? has?
|
36
|
+
|
37
|
+
# The number of items in the cache
|
38
|
+
# @return [Fixnum] key count
|
39
|
+
def size
|
40
|
+
@meta_mutex.synchronize { @keys.size }
|
41
|
+
end
|
42
|
+
|
43
|
+
# Convert the contents of the cache to a Hash
|
44
|
+
# @return [Hash] the cached data
|
45
|
+
def to_hash
|
46
|
+
@read_mutex.synchronize { @data.dup }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return a raw Array of the cache data without its keys.
|
50
|
+
# Not particularly useful but it may be useful in the future.
|
51
|
+
# @return [Array] just the cached values
|
52
|
+
def values
|
53
|
+
@read_mutex.synchronize { @data.values }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Allow iterating over the cached items, represented as key+value pairs
|
57
|
+
def each(&block)
|
58
|
+
to_hash.each(&block)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Invalidate a cached item by its index / key. Returns `nil` if the object
|
62
|
+
# doesn't exist.
|
63
|
+
# @param key [Symbol] the cached object's index
|
64
|
+
def invalidate(key)
|
65
|
+
invalidate_key(key)
|
66
|
+
@write_mutex.synchronize { @data.delete(key) }
|
67
|
+
end
|
68
|
+
|
69
|
+
alias delete invalidate
|
70
|
+
|
71
|
+
# Remove all items from the cache without clearing statistics
|
72
|
+
# @return [Boolean] was the truncate operation successful?
|
73
|
+
def truncate
|
74
|
+
@read_mutex.synchronize do
|
75
|
+
@write_mutex.synchronize do
|
76
|
+
@meta_mutex.synchronize { @keys = [] }
|
77
|
+
@data = {}
|
78
|
+
end
|
79
|
+
@data.empty?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Similar to {#truncate} (in fact, it calls it) but it also clears the
|
84
|
+
# statistical metadata.
|
85
|
+
# @return [Boolean] was the flush operation successful?
|
86
|
+
def flush
|
87
|
+
if truncate
|
88
|
+
@meta_mutex.synchronize do
|
89
|
+
@hits = 0
|
90
|
+
@misses = 0
|
91
|
+
end
|
92
|
+
true
|
93
|
+
else
|
94
|
+
false
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Provides a hash of the current metadata for the cache. It provides the
|
99
|
+
# current cache size (`:size`),the number of cache hits (`:hits`), and
|
100
|
+
# the number of cache misses (`:misses`).
|
101
|
+
# @return [Hash] cache statistics
|
102
|
+
def statistics
|
103
|
+
{
|
104
|
+
size: size,
|
105
|
+
hits: @meta_mutex.synchronize { @hits },
|
106
|
+
misses: @meta_mutex.synchronize { @misses }
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
# Store some data (`value`) indexed by a `key`. If an object exists with
|
111
|
+
# the same key, and the value is different, it will be overwritten.
|
112
|
+
# Storing a value causes its key to be moved to the end of the keys array
|
113
|
+
# (meaning it is the __most recently used__ item), and this happens on
|
114
|
+
# #store regardless of whether or not the key previously existed.
|
115
|
+
# This behavior is relied upon by {#retrieve} to allow reorganization of
|
116
|
+
# the keys without necessarily modifying the data it indexes.
|
117
|
+
# Uses recursion for overwriting existing items.
|
118
|
+
#
|
119
|
+
# @param key [Symbol] the index to use for referencing this cached item
|
120
|
+
# @param value [Object] the data to cache
|
121
|
+
def store(key, value)
|
122
|
+
if has?(key)
|
123
|
+
if @read_mutex.synchronize { @data[key] == value }
|
124
|
+
invalidate_key(key)
|
125
|
+
@meta_mutex.synchronize { @keys << key }
|
126
|
+
value
|
127
|
+
else
|
128
|
+
invalidate(key)
|
129
|
+
store(key, value)
|
130
|
+
end
|
131
|
+
else
|
132
|
+
invalidate(@keys.first) until size < @max_size
|
133
|
+
|
134
|
+
@write_mutex.synchronize do
|
135
|
+
@meta_mutex.synchronize { @keys << key }
|
136
|
+
@data[key] = value
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
alias []= store
|
142
|
+
|
143
|
+
# Retrieve an item from the cache. Returns `nil` if the item does not
|
144
|
+
# exist. Relies on {#store} returning the stored value to ensure the LRU
|
145
|
+
# algorithm is maintained safely.
|
146
|
+
# @param key [Symbol] the index to retrieve
|
147
|
+
def retrieve(key)
|
148
|
+
if has?(key)
|
149
|
+
@meta_mutex.synchronize { @hits += 1 }
|
150
|
+
# Looks dumb, but it actually only reorganizes the keys Array
|
151
|
+
store(key, @read_mutex.synchronize { @data[key] })
|
152
|
+
else
|
153
|
+
@meta_mutex.synchronize { @misses += 1 }
|
154
|
+
nil
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
alias [] retrieve
|
159
|
+
|
160
|
+
def marshal_dump
|
161
|
+
[@max_size, @hits, @misses, @keys, @data]
|
162
|
+
end
|
163
|
+
|
164
|
+
def marshal_load(array)
|
165
|
+
@max_size, @hits, @misses, @keys, @data = array
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
# Invalidate just the key of a cached item. Dangerous if used incorrectly.
|
171
|
+
def invalidate_key(key)
|
172
|
+
@meta_mutex.synchronize { @keys.delete(key) }
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|