hitnmiss 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.com/Acornsgrow/hitnmiss.svg?token=GGEgqzL4zt7sa3zVgspU&branch=master)](https://travis-ci.com/Acornsgrow/hitnmiss)
|
2
|
+
[![Code Climate](https://codeclimate.com/repos/567a3c30bd3f3b63510017dd/badges/e979a32e79ec12d35896/gpa.svg)](https://codeclimate.com/repos/567a3c30bd3f3b63510017dd/feed)
|
3
|
+
[![Test Coverage](https://codeclimate.com/repos/567a3c30bd3f3b63510017dd/badges/e979a32e79ec12d35896/coverage.svg)](https://codeclimate.com/repos/567a3c30bd3f3b63510017dd/coverage)
|
4
|
+
[![Issue Count](https://codeclimate.com/repos/567a3c30bd3f3b63510017dd/badges/e979a32e79ec12d35896/issue_count.svg)](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).
|