hitnmiss 2.1.0
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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +9 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +69 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/CONTRIBUTING.md +56 -0
- data/DEVELOPMENT.md +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +397 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/hitnmiss.gemspec +28 -0
- data/lib/hitnmiss.rb +22 -0
- data/lib/hitnmiss/background_refresh_repository.rb +78 -0
- data/lib/hitnmiss/driver.rb +39 -0
- data/lib/hitnmiss/driver_registry.rb +17 -0
- data/lib/hitnmiss/entity.rb +12 -0
- data/lib/hitnmiss/errors.rb +9 -0
- data/lib/hitnmiss/in_memory_driver.rb +94 -0
- data/lib/hitnmiss/repository.rb +41 -0
- data/lib/hitnmiss/repository/cache_management.rb +70 -0
- data/lib/hitnmiss/repository/driver_management.rb +17 -0
- data/lib/hitnmiss/repository/fetcher.rb +15 -0
- data/lib/hitnmiss/repository/key_generation.rb +31 -0
- data/lib/hitnmiss/version.rb +3 -0
- metadata +157 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 16113a952ba4f6e13da3988202b5293f7b29883e
|
4
|
+
data.tar.gz: e47681d3d3de77dc028ace05d8ca89c6c44d4cc0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 063cac92539361cd38cbf1cf6defd69d83ab9406c5e0f65e3639b7ad708f3ecee452cd6b2313383c6e0658baaaec4cf71ffc9eeddd79d61ec2fa65c0d4cb2828
|
7
|
+
data.tar.gz: f2495525eb2431776aaf631766e61d17f0ff712a7e98b9e9ad71c216eedeb8df4f542a76c14ccb24b203af5b8d3bbd702782fc1ee0e843e0a65630570e011303
|
data/.codeclimate.yml
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# ChangeLog
|
2
|
+
|
3
|
+
The following are lists of the notable changes included with each release.
|
4
|
+
This is intended to help keep people informed about notable changes between
|
5
|
+
versions, as well as provide a rough history.
|
6
|
+
|
7
|
+
#### Next Release
|
8
|
+
|
9
|
+
#### v2.1.0
|
10
|
+
|
11
|
+
* Changed: Documentation for open sourcing
|
12
|
+
|
13
|
+
#### v2.0.0
|
14
|
+
|
15
|
+
* Added: `BackgroundRefreshRepository` module
|
16
|
+
* Changed: Extract cache management into separate module
|
17
|
+
* Added: fingerprint, last\_modified, and updated\_at
|
18
|
+
* Changed: driver interface to receive a `Hitnmiss::Entity`
|
19
|
+
* Fixed: Correct typo in UnregisteredDriver
|
20
|
+
* Changed: Extract key generation concerns into separate module
|
21
|
+
* Changed: Extract `Fetcher` interface into separate module
|
22
|
+
|
23
|
+
#### v1.0.0
|
24
|
+
|
25
|
+
* Changed version to 1.0 since the api is stable
|
26
|
+
|
27
|
+
#### v0.4.0
|
28
|
+
|
29
|
+
* Changed `InMemoryDriver` to return `Hitnmiss::Driver::Hit` and
|
30
|
+
`Hitnmiss::Driver::Miss` instances.
|
31
|
+
* Changed driver interface to require `<#driver instance>.get` method to return a
|
32
|
+
`Hitnmiss::Driver::Hit` instance or a `Hitnmiss::Driver::Miss` instance
|
33
|
+
* Added `Hitnmiss::Driver::Miss` class
|
34
|
+
* Added `Hitnmiss::Driver::Hit` class
|
35
|
+
* Changed private methods `get`, `get_all` to `fetch` & `fetch_all`
|
36
|
+
* Changed public API method `fetch(*args)` to `get(*args)`
|
37
|
+
|
38
|
+
#### v0.3.0
|
39
|
+
|
40
|
+
* Changed class style interface to object style interface
|
41
|
+
* Fixed `InMemoryDriver#all` to return values not hash of value & expiration
|
42
|
+
* Added return values to Public API Documentation examples
|
43
|
+
|
44
|
+
#### v0.2.0
|
45
|
+
|
46
|
+
* Changed `Repository.prime_cache` to `Repository.prime`
|
47
|
+
* Added `Repository.prime_all` to prime entire repository
|
48
|
+
* Changed `Repository.perform` to `Repository.get`
|
49
|
+
* Added `Repository.get_all` for `Repository.prime_all`
|
50
|
+
* Added `Repository.all` to get all cached values
|
51
|
+
* Added `Repository.delete` to delete a cached value
|
52
|
+
* Added `Repository.clear` to clear all cached values
|
53
|
+
* Added `Driver#all` interface to get all cached values
|
54
|
+
* Added `Driver#delete` interface to delete a cached value
|
55
|
+
* Added `Driver#clear` interface to clear all cached values
|
56
|
+
* Changed `InMemoryDriver` to implement `#all`, `#delete`, and `#clear`
|
57
|
+
|
58
|
+
#### v0.1.2
|
59
|
+
|
60
|
+
* Fixed fetching cached boolean `false` value
|
61
|
+
|
62
|
+
#### v0.1.1
|
63
|
+
|
64
|
+
* Changed the InMemoryDriver to be threadsafe
|
65
|
+
|
66
|
+
#### v0.1.0
|
67
|
+
|
68
|
+
* Added driver registry to centrally manage instantiated drivers
|
69
|
+
* Added initial Minimum Viable Product version of the library
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at oss@acorns.com. All
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
42
|
+
incident.
|
43
|
+
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
45
|
+
version 1.3.0, available at
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
47
|
+
|
48
|
+
[homepage]: http://contributor-covenant.org
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# Where should I start?
|
2
|
+
|
3
|
+
We recommend first starting to learn about Hitnmiss by reading the
|
4
|
+
[README](https://github.com/Acornsgrow/hitnmiss/blob/master/README.md). Beyond
|
5
|
+
that you can explore the sections below to identify the path that best fits you.
|
6
|
+
**Note:** This project follows a contribution [code of
|
7
|
+
conduct](https://github.com/Acornsgrow/hitnmiss/blob/master/CODE_OF_CONDUCT.md).
|
8
|
+
|
9
|
+
## How can I help?
|
10
|
+
|
11
|
+
There are a number of ways you can help.
|
12
|
+
|
13
|
+
- Report Bugs
|
14
|
+
- Add Feature Requests
|
15
|
+
- Add/Update Documentation
|
16
|
+
- Triage Bugs
|
17
|
+
- Code Contributions
|
18
|
+
|
19
|
+
### Report Bugs
|
20
|
+
|
21
|
+
As a user a relatively easy way to contribute is to report bugs that you run
|
22
|
+
into. Generally, this should be done via our
|
23
|
+
[ISSUES](https://github.com/Acornsgrow/hitnmiss/issues) page. However, if it is
|
24
|
+
a security issue please send an e-mail directly to one of the core authors found
|
25
|
+
in the
|
26
|
+
[gemspec](https://github.com/Acornsgrow/hitnmiss/blob/master/hitnmiss.gemspec#L10)
|
27
|
+
rather than creating a public issue for it. This will give us some time to
|
28
|
+
review it and resolve it before people can abuse the security flaw.
|
29
|
+
|
30
|
+
### Add Feature Requests
|
31
|
+
|
32
|
+
One simple way to get involved after starting to use the project is to
|
33
|
+
contribute by adding feature requests for things that you see as gaps for the
|
34
|
+
project. This can be done by creating an issue or our
|
35
|
+
[ISSUES](https://github.com/Acornsgrow/hitnmiss/issues) page.
|
36
|
+
|
37
|
+
### Add/Update Documentation
|
38
|
+
|
39
|
+
You can also contribute by correcting, updating, or adding additional
|
40
|
+
documentation. This can be done by making a pull request for documentation in
|
41
|
+
any of our top level markdown files.
|
42
|
+
|
43
|
+
### Triage Bugs
|
44
|
+
|
45
|
+
Triaging bugs is also very important. This is the practice of the reviewing
|
46
|
+
[ISSUES](https://github.com/Acornsgrow/hitnmiss/issues), making sure that the
|
47
|
+
submitter provided necessary information to review the bugs, and classify them
|
48
|
+
appropriately.
|
49
|
+
|
50
|
+
### Code Contributions
|
51
|
+
|
52
|
+
The most involved type of contribution you can make is to actually submit code
|
53
|
+
to implement features, fix bugs, etc. This is done by submitting a pull request.
|
54
|
+
See
|
55
|
+
[DEVELOPING](https://github.com/Acornsgrow/hitnmiss/blob/master/DEVELOPMENT.md)
|
56
|
+
for further details on getting started with a code contribution.
|
data/DEVELOPMENT.md
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# Development
|
2
|
+
|
3
|
+
After checking out the repo, run `bin/setup` to install dependencies.
|
4
|
+
Then, run `rake spec` to run the tests. You can also run `bin/console`
|
5
|
+
for an interactive prompt that will allow you to experiment.
|
6
|
+
|
7
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Acorns Grow Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,397 @@
|
|
1
|
+
[](https://travis-ci.com/Acornsgrow/hitnmiss)
|
2
|
+
[](https://codeclimate.com/repos/567a3c30bd3f3b63510017dd/feed)
|
3
|
+
[](https://codeclimate.com/repos/567a3c30bd3f3b63510017dd/coverage)
|
4
|
+
[](https://codeclimate.com/repos/567a3c30bd3f3b63510017dd/feed)
|
5
|
+
|
6
|
+
# Hitnmiss
|
7
|
+
|
8
|
+
Hitnmiss is a Ruby gem that provides support for using the Repository
|
9
|
+
pattern for read-through, write-behind caching in a thread-safe way. It
|
10
|
+
is built heavily around using POROs (Plain Old Ruby Objects). This means
|
11
|
+
it is intended to be used with all kinds of Ruby applications from plain
|
12
|
+
Ruby command line tools, to framework (Rails, Sinatra, Rack, Hanami,
|
13
|
+
etc.) based web apps, etc.
|
14
|
+
|
15
|
+
Having defined repositories for accessing your caches aids in a number of ways.
|
16
|
+
First, it centralizes cache key generation. Secondly, it centralizes &
|
17
|
+
standardizes access to the cache rather than having code spread across your app
|
18
|
+
duplicating key generation and access. Third, it provides clean separation
|
19
|
+
between the cache persistence layer and the business representation.
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
|
23
|
+
Add this line to your application's Gemfile:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
gem 'hitnmiss'
|
27
|
+
```
|
28
|
+
|
29
|
+
And then execute:
|
30
|
+
|
31
|
+
$ bundle
|
32
|
+
|
33
|
+
Or install it yourself as:
|
34
|
+
|
35
|
+
$ gem install hitnmiss
|
36
|
+
|
37
|
+
## Define/Mixin a Repository
|
38
|
+
|
39
|
+
Before you can use Hitnmiss to manage cache you first need to define a
|
40
|
+
cache repository. This is done by defining a class for your repository
|
41
|
+
and mixing the appropriate repository module in using `include`.
|
42
|
+
|
43
|
+
Below, are explanations of the **Standard Repository** and the **Background
|
44
|
+
Refresh Repository** so that you can decide which one fits your needs as well as
|
45
|
+
learn how to use them.
|
46
|
+
|
47
|
+
### Standard Repository
|
48
|
+
|
49
|
+
The standard repository module, `Hitnmiss::Repository`, fits the most common
|
50
|
+
caching use case, the "Expiration Model". The "Expiration Model" is a caching
|
51
|
+
model where a value gets cached with an associated expiration and when that
|
52
|
+
expiration is reached that value is no longer cached. This affects the app
|
53
|
+
behavior by having it pay the caching cost when it tries to get a value and it
|
54
|
+
has expired. The following is an example of creating a `MostRecentPrice` cache
|
55
|
+
repository for the "Expiration Model".
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
class MostRecentPrice
|
59
|
+
include Hitnmiss::Repository
|
60
|
+
|
61
|
+
default_expiration 134 # expiration in seconds from when value cached
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
More often than not your caching use case will have a static/known
|
66
|
+
expiration that you want to use for all values that get cached. This is handled
|
67
|
+
for you by setting the `default_expiration` as seen in the example above.
|
68
|
+
**Note:** Hitnmiss also supports being able to have different expirations for
|
69
|
+
each cached value. You can learn more about this in the "Set Cache Source based
|
70
|
+
Expiration" section.
|
71
|
+
|
72
|
+
### Background Refresh Repository
|
73
|
+
|
74
|
+
Sometimes you don't have an expiration value and don't want cached values to
|
75
|
+
disappear. In these scenarios you want something to update the cache for you
|
76
|
+
based on some defined interval. When you use the
|
77
|
+
`Hitnmiss::BackgroundRefreshRepository` module and set the `refresh_interval`
|
78
|
+
as seen below, it prepares your repository to handle this scenario. This changes
|
79
|
+
the behavior where the app never experiences the caching cost as it is
|
80
|
+
continually managed for the app in the background based on the
|
81
|
+
`refresh_interval`.
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
class MostRecentPrice
|
85
|
+
include Hitnmiss::BackgroundRefreshRepository
|
86
|
+
|
87
|
+
refresh_interval 60*5 # refresh interval in seconds
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
Once you have defined your Background Refresh Repository in order to get the
|
92
|
+
background process to update your cache you have to kick it off using the
|
93
|
+
`background_refresh(*args, swallow_exceptions: [])` method as seen in the
|
94
|
+
example below. The optional keyword argument `swallow_exceptions` defaults to
|
95
|
+
`[]`. If enabled it will prevent the specified exceptions, raised in the
|
96
|
+
`fetch(*args)` or `stale?(*args)` methods you defined, from killing the
|
97
|
+
background thread, and prevent the exceptions from making their way up to the
|
98
|
+
application. This is useful in scenarios where you want it to absorb say timeout
|
99
|
+
exceptions, etc. and continue trucking along. **Note:** Any other exceptions not
|
100
|
+
covered by the exceptions listed in the `swallow_exceptions` array will still be
|
101
|
+
raised up into the application.
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
repository = MostRecentPrice.new
|
105
|
+
repository.background_refresh(store_id)
|
106
|
+
```
|
107
|
+
|
108
|
+
This model also has the added benefit that the priming of the cache in the
|
109
|
+
background refresh process is non-blocking. This means if you use this model the
|
110
|
+
consumer will not experience the priming of the cache like they would with the
|
111
|
+
Standard Repository's Expiration Model.
|
112
|
+
|
113
|
+
#### Staleness Checking
|
114
|
+
|
115
|
+
The Background Refresh Repository model introduces a new concept, Staleness
|
116
|
+
Checking. Staleness is checked during the background refresh process. The way it
|
117
|
+
works is if the cache is identified to be stale, then it primes the cache in the
|
118
|
+
background, if the cache is identified to NOT be stale, then it sleeps for
|
119
|
+
another `refresh_interval`.
|
120
|
+
|
121
|
+
The stale checker, `stale?(*args)`, defaults to an always stale value of `true`.
|
122
|
+
This causes the background refresh process to prime the cache every
|
123
|
+
`refresh_interval`.
|
124
|
+
|
125
|
+
If you want your cache implementation to be smarter and say validate a
|
126
|
+
fingerprint or last modified value against the source, you can do it simply by
|
127
|
+
overwriting the `stale?(*args)` method with your own staleness checking logic.
|
128
|
+
The following is an example of this.
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
class MostRecentPrice
|
132
|
+
include Hitnmiss::BackgroundRefreshRepository
|
133
|
+
|
134
|
+
refresh_interval 60*5 # refresh interval in seconds
|
135
|
+
|
136
|
+
def initialize
|
137
|
+
@client = HTTPClient.new
|
138
|
+
end
|
139
|
+
|
140
|
+
def stale?(*args)
|
141
|
+
hit_or_miss = get_from_cache(*args)
|
142
|
+
if hit_or_miss.is_a?(Hitnmiss::Driver::Miss)
|
143
|
+
return true
|
144
|
+
elsif hit_or_miss.is_a?(Hitnmiss::Driver::Hit)
|
145
|
+
url = "https://someresource.example.com/#{args[0]}/#{args[1]}/digest.json"
|
146
|
+
res = @client.head(url)
|
147
|
+
fingerprint = res.header['ETag'].first
|
148
|
+
return false if fingerprint == hit_or_miss.fingerprint
|
149
|
+
return true
|
150
|
+
else
|
151
|
+
raise Hitnmiss::Repository::UnsupportedDriverResponse.new("Driver '#{self.class.driver.inspect}' did not return an object of the support types (Hitnmiss::Driver::Hit, Hitnmiss::Driver::Miss)")
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
### Set a Repositories Cache Driver
|
158
|
+
|
159
|
+
Hitnmiss defaults to the provided `Hitnmiss::InMemoryDriver`, but if an alternate
|
160
|
+
driver is needed a new driver can be registered as seen below.
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
# config/hitnmiss.rb
|
164
|
+
Hitnmiss.register_driver(:my_driver, SomeAlternativeDriver.new)
|
165
|
+
```
|
166
|
+
|
167
|
+
Once you have registered the new driver you can tell `Hitnmiss` what
|
168
|
+
driver to use for this particular repository. The following is an example
|
169
|
+
of how one would accomplish setting the repository driver to the driver
|
170
|
+
that was just registered.
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
class MostRecentPrice
|
174
|
+
include Hitnmiss::Repository
|
175
|
+
|
176
|
+
driver :my_driver
|
177
|
+
end
|
178
|
+
```
|
179
|
+
|
180
|
+
This works exactly the same with the `Hitnmiss::BackgroundRefreshRepository`.
|
181
|
+
|
182
|
+
### Define Fetcher Methods
|
183
|
+
|
184
|
+
You may be asking yourself, "How does the cache value get set?" Well,
|
185
|
+
the answer is one of two ways.
|
186
|
+
|
187
|
+
* Fetching an individual cacheable entity
|
188
|
+
* Fetching all of the repository's cacheable entities
|
189
|
+
|
190
|
+
Both of these scenarios are supported by defining the `fetch(*args)`
|
191
|
+
method or the `fetch_all(keyspace)` method respectively in your cache
|
192
|
+
repository class. See the following example.
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
class MostRecentPrice
|
196
|
+
include Hitnmiss::Repository
|
197
|
+
|
198
|
+
default_expiration 134
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
def fetch(*args)
|
203
|
+
# - do whatever to get the value you want to cache
|
204
|
+
# - construct a Hitnmiss::Entity with the value
|
205
|
+
Hitnmiss::Entity.new('some value')
|
206
|
+
end
|
207
|
+
|
208
|
+
def fetch_all(keyspace)
|
209
|
+
# - do whatever to get the values you want to cache
|
210
|
+
# - construct a collection of arguments and Hitnmiss entities
|
211
|
+
[
|
212
|
+
{ args: ['a', 'b'], entity: Hitnmiss::Entity.new('some value') },
|
213
|
+
{ args: ['x', 'y'], entity: Hitnmiss::Entity.new('other value') }
|
214
|
+
]
|
215
|
+
end
|
216
|
+
end
|
217
|
+
```
|
218
|
+
|
219
|
+
The `fetch(*args)` method is responsible for obtaining the value that you want
|
220
|
+
to cache by whatever means necessary and returning a `Hitnmiss::Entity`
|
221
|
+
containing the value, and optionally an `expiration`, `fingerprint`, and
|
222
|
+
`last_modified`. **Note:** The `*args` passed into the `fetch(*args)` method
|
223
|
+
originate as the arguments that are passed into `prime` and `get` methods when
|
224
|
+
they are called. For more context on `prime` and `get` please so [Priming an
|
225
|
+
Entity](#priming-an-entity) and [Getting a Value](#getting-a-value)
|
226
|
+
respectively. Defining the `fetch(*args)` method is **required** if you want to
|
227
|
+
be able to cache values or get cached values using the `prime` or `get` methods.
|
228
|
+
|
229
|
+
If you wish to support [priming the cache for an entire
|
230
|
+
repository](#priming-the-entire-repository) using the `prime_all` method, you
|
231
|
+
**must** define the `fetch_all(keyspace)` method on the repository class. This
|
232
|
+
method **must** return a collection of hashes describing the `args` that would
|
233
|
+
be used to get an entity and the corresponding `Hitnmiss::Entity`. See example
|
234
|
+
above.
|
235
|
+
|
236
|
+
#### Set Cache Source based Expiration
|
237
|
+
|
238
|
+
In some cases you might need to set the expiration to be a different
|
239
|
+
value for each cached value. This is generally needed when you get
|
240
|
+
information back from a remote entity in the `fetch(*args)` method and you
|
241
|
+
use it to compute the expiration. This for example could be via the `Expiration`
|
242
|
+
header in an HTTP response. Once you have the expiration for that
|
243
|
+
value you can set it by passing the expiration into the
|
244
|
+
`Hitnmiss::Entity` constructor as seen below. **Note:** The expiration
|
245
|
+
in the `Hitnmiss::Entity` will take precedence over the
|
246
|
+
`default_expiration`.
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
class MostRecentPrice
|
250
|
+
include Hitnmiss::Repository
|
251
|
+
|
252
|
+
default_expiration 134
|
253
|
+
|
254
|
+
private
|
255
|
+
|
256
|
+
def fetch(*args)
|
257
|
+
# - do whatever to get the value you want to cache
|
258
|
+
# - construct a Hitnmiss::Entity with the value and optionally a
|
259
|
+
# result based expiration. If no result based expiration is
|
260
|
+
# provided it will use the default_expiration.
|
261
|
+
Hitnmiss::Entity.new('some value', expiration: 235)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
```
|
265
|
+
|
266
|
+
#### Set Cache Source based Fingerprint
|
267
|
+
|
268
|
+
In some cases you might want to set the fingerprint for the cached value. Doing
|
269
|
+
so provides more flexibility to `Hitnmiss` in terms of determining staleness of
|
270
|
+
cache. A very common example of this would be if you are fetching something over
|
271
|
+
HTTP from the remote and the remote includes the `ETag` header. If you take the
|
272
|
+
value of the `ETag` header (a fingerprint) and you set it in the
|
273
|
+
`Hitnmiss::Entity` it can be used later on by `Hitnmiss` to aid in identifying
|
274
|
+
staleness. Once you have obtained the fingerprint by whatever means you can set
|
275
|
+
it by passing the fingerprint into the `Hitnmiss::Entity` constructor as seen
|
276
|
+
below.
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
class MostRecentPrice
|
280
|
+
include Hitnmiss::Repository
|
281
|
+
|
282
|
+
default_expiration 134
|
283
|
+
|
284
|
+
private
|
285
|
+
|
286
|
+
def fetch(*args)
|
287
|
+
# - do whatever to get the value you want to cache
|
288
|
+
# - do whatever to get the fingerprint of the value you want to cache
|
289
|
+
# - construct a Hitnmiss::Entity with the value and optionally a
|
290
|
+
# fingerprint.
|
291
|
+
Hitnmiss::Entity.new('some value',
|
292
|
+
fingerprint: "63478adce0dd0fbc82ef4bb1f1d64193")
|
293
|
+
end
|
294
|
+
end
|
295
|
+
```
|
296
|
+
|
297
|
+
#### Set Cache Source based Last Modified
|
298
|
+
|
299
|
+
In some cases you might want to set the `last_modified` value for a cached
|
300
|
+
value. Doing this provides more felxibility to `Hitnmiss` when trying to
|
301
|
+
determine staleness of a cached item. A common example of this would be if you
|
302
|
+
are fetching somthing over HTTP from a remote server and it includes the
|
303
|
+
`Last-Modified` entity header in the response. If you took the value of the
|
304
|
+
`Last-Modified` header and you set it in the `Hitnmiss::Entity` it can be used
|
305
|
+
later on by `Hitnmiss` to aid in identifying staleness. Once you have obtained
|
306
|
+
the `Last-Modified` value by whatever means you can set it by passing the
|
307
|
+
`last_modified` option into the `Hitnmiss::Entity` constructor as seen below.
|
308
|
+
|
309
|
+
```ruby
|
310
|
+
class MostRecentPrice
|
311
|
+
include Hitnmiss::Repository
|
312
|
+
|
313
|
+
default_expiration 134
|
314
|
+
|
315
|
+
private
|
316
|
+
|
317
|
+
def fetch(*args)
|
318
|
+
# - do whatever to get the value you want to cache
|
319
|
+
# - do whatever to get the last modified of the value you want to cache
|
320
|
+
# - construct a Hitnmiss::Entity with the value and optionally a
|
321
|
+
# last_modified value.
|
322
|
+
Hitnmiss::Entity.new('some value',
|
323
|
+
last_modified: "2016-04-15T13:00:00Z")
|
324
|
+
end
|
325
|
+
end
|
326
|
+
```
|
327
|
+
|
328
|
+
## Usage
|
329
|
+
|
330
|
+
The following is a light breakdown of the various pieces of Hitnmiss and
|
331
|
+
how to get going with them after defining your cache repository.
|
332
|
+
|
333
|
+
### Priming an entity
|
334
|
+
|
335
|
+
Once you have defined the `fetch(*args)` method you can construct an
|
336
|
+
instance of your cache repository and use it. One way you might want to
|
337
|
+
use it is to force the repository to cache a value. This can be done
|
338
|
+
using the `prime` method as seen below.
|
339
|
+
|
340
|
+
```ruby
|
341
|
+
repository = MostRecentPrice.new
|
342
|
+
repository.prime(current_user.id) # => cached_value
|
343
|
+
```
|
344
|
+
|
345
|
+
### Priming the entire repository
|
346
|
+
|
347
|
+
You can use the `prime_all` method to prime the entire repository. **Note:**
|
348
|
+
The repository class must define the `fetch_all(keyspace)` method for this
|
349
|
+
to work. See the "Define Fetcher Methods" section above for details.
|
350
|
+
|
351
|
+
```ruby
|
352
|
+
repository = MostRecentPrice.new
|
353
|
+
repository.prime_all # => [cached values, ...]
|
354
|
+
```
|
355
|
+
|
356
|
+
### Getting a value
|
357
|
+
|
358
|
+
You can also use your cache repository to get a particular cached
|
359
|
+
value by doing the following.
|
360
|
+
|
361
|
+
```ruby
|
362
|
+
repository = MostRecentPrice.new
|
363
|
+
repository.get(current_user.id) # => cached_value
|
364
|
+
```
|
365
|
+
|
366
|
+
### Deleting a cached value
|
367
|
+
|
368
|
+
You can delete a cached value by calling the `delete` method with the
|
369
|
+
same arguments used to get it.
|
370
|
+
|
371
|
+
```ruby
|
372
|
+
repository = MostRecentPrice.new
|
373
|
+
# cache some values ex: repository.prime(current_user.id)
|
374
|
+
repository.delete(current_user.id)
|
375
|
+
```
|
376
|
+
|
377
|
+
### Clearing a repository
|
378
|
+
|
379
|
+
You can clear the entire repository of cached values by calling the
|
380
|
+
`clear` method.
|
381
|
+
|
382
|
+
```ruby
|
383
|
+
repository = MostRecentPrice.new
|
384
|
+
# cache some values ex: repository.prime(current_user.id)
|
385
|
+
repository.clear
|
386
|
+
```
|
387
|
+
|
388
|
+
## Contributing
|
389
|
+
|
390
|
+
If you are interested in contributing to Hitnmiss. There is a guide (both code
|
391
|
+
and general help) over in
|
392
|
+
[CONTRIBUTING](https://github.com/Acornsgrow/hitnmiss/blob/master/CONTRIBUTING.md).
|
393
|
+
|
394
|
+
## License
|
395
|
+
|
396
|
+
The gem is available as open source under the terms of the [MIT
|
397
|
+
License](http://opensource.org/licenses/MIT).
|