thermos 2.0.0 → 2.0.1

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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +213 -0
  3. data/lib/thermos/version.rb +1 -1
  4. metadata +15 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37303ec15144f7b0a1aa69e53a80ea8bc7586b7af91e4ae7e4133aca8fa3ce8d
4
- data.tar.gz: 5e5e22860067239168af1171502b880d534ba85e50e38c5a1c5c785c05987924
3
+ metadata.gz: 96c092c9b446b9147dca86d7f6b655dcb8f6f00191a8260b0dbb6bd15bea56c1
4
+ data.tar.gz: e7b19005dffe9c4fbec290576e0d9094fb1d04021fd037e3148dc7bb57d99179
5
5
  SHA512:
6
- metadata.gz: 848351bdfa6ef208f1b285d4fd5e0000ba0fe61d1ce842961cbfb8a721adcbb7b61070dd96af3ef452006aa0632381537c7d8b81d183c7ac1db4d9d7ddb96bad
7
- data.tar.gz: d29bea390a0cf22f754cc21d6b199fa1e507d79f5f71fcb455ecbd05ed92ed1fa3410fa306d94ddcc721407606e9fb8b8682a0802a54d88dbe2c9d2ead3f17f9
6
+ metadata.gz: 5484c23bf0c7ff96d75158c37bc399cf06a45a25447347c2a56ffc0bc8d026742042655019dbfb3518fbb3ea9765431963db334973692e3ad4f9cf272c78710e
7
+ data.tar.gz: 39f11866f8de933f2e6f68174f43d4c4b88fc48b25c472bee5799447cdcc6d91c568eb31173bf004a41cccf0d0b882cd0368d299287b6487d2c6f7d99bccac8c
data/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # Thermos
2
+
3
+ **Always-warm Rails caching that automatically rebuilds when your models change.**
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/thermos.svg)](https://badge.fury.io/rb/thermos)
6
+ [![Code Climate](https://codeclimate.com/github/athal7/thermos/badges/gpa.svg)](https://codeclimate.com/github/athal7/thermos)
7
+ ![Build Status](https://img.shields.io/github/actions/workflow/status/athal7/thermos/CI.yml?branch=main)
8
+
9
+ Thermos is a Rails caching library that keeps your cache always warm by rebuilding it in the background whenever ActiveRecord models change. No more stale data, no more cold cache penalties, no more `touch: true` on all your associations.
10
+
11
+ ## Features
12
+
13
+ - **Always-warm cache** — Cache is rebuilt in the background when models change
14
+ - **No stale data** — Unlike TTL-based caching, data is only as stale as your job queue latency
15
+ - **No cold cache penalties** — Cache is pre-warmed, so users never wait for expensive queries
16
+ - **No `touch` callbacks needed** — Thermos watches model dependencies automatically
17
+ - **Works with any backend** — Sidekiq, Solid Queue, Resque, or any ActiveJob adapter
18
+ - **Works with any cache store** — Redis, Memcached, Solid Cache, or any Rails cache store
19
+ - **ETag support** — Works seamlessly with HTTP caching for browser and CDN caching
20
+
21
+ ## Installation
22
+
23
+ Add to your Gemfile:
24
+
25
+ ```ruby
26
+ gem 'thermos'
27
+ ```
28
+
29
+ Then run:
30
+
31
+ ```bash
32
+ bundle install
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ```ruby
38
+ # In a controller - cache is automatically rebuilt when Category or its products change
39
+ json = Thermos.keep_warm(key: "category", model: Category, id: params[:id], deps: [:products]) do |id|
40
+ Category.includes(:products).find(id).to_json
41
+ end
42
+
43
+ render json: json
44
+ ```
45
+
46
+ That's it! When any `Category` or associated `Product` is created, updated, or destroyed, Thermos automatically rebuilds the cache in the background.
47
+
48
+ ## Why Thermos?
49
+
50
+ Most cache strategies have significant downsides:
51
+
52
+ | Strategy | Problem |
53
+ |----------|---------|
54
+ | **TTL-based** (expires_in) | Stale data until expiration |
55
+ | **Key-based** (cache_key) | Cold cache on first request after any change |
56
+ | **Touch callbacks** | Extra database writes on every association change |
57
+
58
+ Thermos solves all of these by rebuilding caches proactively in background jobs.
59
+
60
+ > "I just want to Thermos everything now!! Unbelievable improvement. It's like every dev's dream come true" — [@jono-booth](https://github.com/jono-booth)
61
+
62
+ ## Prerequisites
63
+
64
+ Configure a [Rails Cache Store](https://guides.rubyonrails.org/caching_with_rails.html#configuration) that supports shared access across processes (Redis, Memcached, Solid Cache — not MemoryStore).
65
+
66
+ Thermos works with any ActiveJob adapter, including Rails 8's [Solid Queue](https://github.com/rails/solid_queue) and [Solid Cache](https://github.com/rails/solid_cache).
67
+
68
+ ## Usage
69
+
70
+ ### keep_warm (Simple)
71
+
72
+ With `keep_warm`, the cached content is defined along with the cache block and dependencies definition. This is the simplest implementation, *but is only compatible with the [Active Job Inline Adapter](https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/InlineAdapter.html)*. See the next section about fill/drink for compatibility with other Active Job Adapters.
73
+
74
+ *API Controller*
75
+
76
+ ```ruby
77
+ json = Thermos.keep_warm(key: "api_categories_show", model: Category, id: params[:id], deps: [:category_items, :products]) do |id|
78
+ Category.find(id).to_json
79
+ end
80
+
81
+ render json: json
82
+ ```
83
+
84
+ *Frontend Controller*
85
+
86
+ ```ruby
87
+ rendered_template = Thermos.keep_warm(key: "frontend_categories_show", model: Category, id: params[:id], deps: [:category_items, :products]) do |id|
88
+ @category = Category.includes(category_items: :product).find(id)
89
+ render_to_string :show
90
+ end
91
+
92
+ render rendered_template
93
+ ```
94
+
95
+ ### fill / drink (Advanced)
96
+
97
+ For more control, define your cache once with `fill` and read it anywhere with `drink`. This is ideal for sharing cached data across multiple controllers or when using background job adapters other than inline.
98
+
99
+ *Rails Initializer*
100
+
101
+ ```ruby
102
+ Thermos.fill(key: "api_categories_show", model: Category, deps: [:category_items, :products]) do |id|
103
+ Category.find(id).to_json
104
+ end
105
+ ```
106
+
107
+ *API Controller*
108
+
109
+ ```ruby
110
+ json = Thermos.drink(key: "api_categories_show", id: params[:id])
111
+ render json: json
112
+ ```
113
+
114
+ ## Options
115
+
116
+ ### lookup_key
117
+
118
+ If you want to be able to lookup by a key other than `id` (e.g. you use a slug in the params), you can specify the `lookup_key` as an argument to `keep_warm` or `fill`:
119
+
120
+ ```ruby
121
+ Thermos.keep_warm(key: "api_categories_show", model: Category, id: params[:slug], lookup_key: :slug) do |slug|
122
+ Category.find_by(slug: slug).to_json
123
+ end
124
+ ```
125
+
126
+ or
127
+
128
+ ```ruby
129
+ Thermos.fill(key: "api_categories_show", model: Category, lookup_key: :slug) do |slug|
130
+ Category.find_by(slug: slug).to_json
131
+ end
132
+ ```
133
+
134
+ ### queue
135
+
136
+ If you want to specify a queue for the refill jobs to run other than the default queue, you can provide it to either way of using Thermos:
137
+
138
+ ```ruby
139
+ Thermos.keep_warm(key: "api_categories_show", model: Category, queue: "low_priority") do |id|
140
+ Category.find(id).to_json
141
+ end
142
+ ```
143
+
144
+ or
145
+
146
+ ```ruby
147
+ Thermos.fill(key: "api_categories_show", model: Category, queue: "low_priority") do |id|
148
+ Category.find(id).to_json
149
+ end
150
+
151
+ Thermos.drink(key: "api_categories_show", id: params[:slug])
152
+ ```
153
+
154
+ ### Indirect Relationships
155
+
156
+ You can specify indirect relationships as dependencies as well. For example, if `Store has_many categories`, and `Category has_many products`, but there is no relationship specified on the `Store` model to `Product`:
157
+
158
+ ```ruby
159
+ Thermos.keep_warm(key: "api_stores_show", model: Store, id: params[:id], deps: [categories: [:products]]) do |id|
160
+ Store.find(id).to_json
161
+ end
162
+ ```
163
+
164
+ *NOTE* in this example, a change to any model in the association chain will trigger a refill of the cache.
165
+
166
+ ### filter
167
+
168
+ You can provide a filter to restrict whether a record gets rebuilt on model changes:
169
+
170
+ ```ruby
171
+ filter = ->(model) { model.name.match("ball") }
172
+ Thermos.keep_warm(key: "api_categories_show", model: Category, id: params[:id], filter: filter) do |id|
173
+ Category.find(id).to_json
174
+ end
175
+ ```
176
+
177
+ ## Using with ETags
178
+
179
+ Thermos works seamlessly with Rails' HTTP caching via ETags, enabling browser and CDN caching of your responses. Since Thermos keeps your cache always warm and rebuilds it when models change, the cached value's digest will naturally change when the underlying data changes.
180
+
181
+ Use Rails' `stale?` helper with the cached value to enable conditional GET requests:
182
+
183
+ ```ruby
184
+ def show
185
+ json = Thermos.drink(key: "api_categories_show", id: params[:id])
186
+
187
+ if stale?(etag: json)
188
+ render json: json
189
+ end
190
+ end
191
+ ```
192
+
193
+ When the cached value changes (triggered by model updates), the ETag will change, and clients will receive the new content. When the value hasn't changed, clients with a matching ETag will receive a `304 Not Modified` response.
194
+
195
+ This enables caching at multiple layers:
196
+ - **Browser cache**: Browsers store responses and revalidate with the server using the ETag, avoiding re-downloads of unchanged content
197
+ - **CDN cache**: CDNs can cache responses and serve them directly to users, only revalidating with your server when needed
198
+
199
+ Combined with Thermos, you get:
200
+ - **Always-warm application cache** (no cold cache penalties)
201
+ - **Reduced server load** (304 responses skip rendering)
202
+ - **Reduced bandwidth** (browsers and CDNs serve cached content)
203
+ - **Faster responses** (CDN edge locations serve content closer to users)
204
+
205
+ ## Contributors
206
+
207
+ <a href="https://github.com/athal7/thermos/graphs/contributors">
208
+ <img src="https://contrib.rocks/image?repo=athal7/thermos" />
209
+ </a>
210
+
211
+ ## License
212
+
213
+ This project uses MIT-LICENSE.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Thermos
4
- VERSION = "2.0.0"
4
+ VERSION = "2.0.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thermos
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Thal
@@ -87,8 +87,12 @@ dependencies:
87
87
  - !ruby/object:Gem::Version
88
88
  version: '0'
89
89
  description: |
90
- Thermos is a library for caching in rails that re-warms caches
91
- in the background based on model changes.
90
+ Thermos is a Rails caching library that keeps your cache always warm by
91
+ automatically rebuilding it in the background when ActiveRecord models change.
92
+ No more stale data from TTL expiration, no more slow cold cache hits, and no
93
+ need to 'touch' associated models. Works with any ActiveJob backend (Sidekiq,
94
+ Solid Queue, etc.) and any cache store (Redis, Memcached, Solid Cache, etc.).
95
+ Perfect for API responses, JSON serialization, and view caching.
92
96
  email:
93
97
  - athal7@me.com
94
98
  executables: []
@@ -96,6 +100,7 @@ extensions: []
96
100
  extra_rdoc_files: []
97
101
  files:
98
102
  - MIT-LICENSE
103
+ - README.md
99
104
  - Rakefile
100
105
  - lib/thermos.rb
101
106
  - lib/thermos/beverage.rb
@@ -160,7 +165,11 @@ files:
160
165
  homepage: https://github.com/athal7/thermos
161
166
  licenses:
162
167
  - MIT
163
- metadata: {}
168
+ metadata:
169
+ homepage_uri: https://github.com/athal7/thermos
170
+ source_code_uri: https://github.com/athal7/thermos
171
+ changelog_uri: https://github.com/athal7/thermos/releases
172
+ rubygems_mfa_required: 'true'
164
173
  post_install_message:
165
174
  rdoc_options: []
166
175
  require_paths:
@@ -179,7 +188,8 @@ requirements: []
179
188
  rubygems_version: 3.5.22
180
189
  signing_key:
181
190
  specification_version: 4
182
- summary: Always-warm, auto-rebuilding rails caching without timers or touching.
191
+ summary: Always-warm, auto-rebuilding Rails cache that updates in the background when
192
+ models change.
183
193
  test_files:
184
194
  - test/dependencies_test.rb
185
195
  - test/dummy/README.rdoc