thermos 1.0.2 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83926b2fe8fb25c3e0f04afca0109f2d5da8fe5e36aee84fd642ff4fd9b134f0
4
- data.tar.gz: eb6120db89d227f077dace4ca1b6e33095e26c44df470252f36aebab2693d6da
3
+ metadata.gz: 96c092c9b446b9147dca86d7f6b655dcb8f6f00191a8260b0dbb6bd15bea56c1
4
+ data.tar.gz: e7b19005dffe9c4fbec290576e0d9094fb1d04021fd037e3148dc7bb57d99179
5
5
  SHA512:
6
- metadata.gz: 267d7aef1db1d00ed4d92bc82a5351cfca86391f4d01d5a223ab475650ef93a35dde0615063e05e8e7c80cb6ed56841a90c4c5580e4cb96824daa08301da6754
7
- data.tar.gz: 6c611264f064b6cde6bc963a1157a4545e9bcd15cfe3f4787451afcf8fb8efe53bf93240f0df473cf3e0a121f81ad3d581764ac5f72c10125ed2f60e7fab8ff5
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 = "1.0.2"
4
+ VERSION = "2.0.1"
5
5
  end
@@ -1,6 +1,6 @@
1
1
  require "test_helper"
2
2
 
3
- class DependenciesTest < ActiveSupport::TestCase
3
+ class DependenciesTest < ActiveJob::TestCase
4
4
  self.use_transactional_tests = true
5
5
  teardown :clear_cache
6
6
 
@@ -18,7 +18,7 @@ class DependenciesTest < ActiveSupport::TestCase
18
18
  mock.verify
19
19
 
20
20
  mock.expect(:call, 2, [category.id])
21
- category_item.update!(name: "foo")
21
+ perform_enqueued_jobs { category_item.update!(name: "foo") }
22
22
  mock.verify
23
23
 
24
24
  mock.expect(:call, 3, [category.id])
@@ -40,7 +40,7 @@ class DependenciesTest < ActiveSupport::TestCase
40
40
  mock.verify
41
41
 
42
42
  mock.expect(:call, 2, [category.id])
43
- category_item.update!(name: "foo")
43
+ perform_enqueued_jobs { category_item.update!(name: "foo") }
44
44
  assert_raises(MockExpectationError) { mock.verify }
45
45
 
46
46
  mock.expect(:call, 3, [category.id])
@@ -57,7 +57,7 @@ class DependenciesTest < ActiveSupport::TestCase
57
57
  end
58
58
 
59
59
  mock.expect(:call, 1, [category.id])
60
- CategoryItem.create!(category: category)
60
+ perform_enqueued_jobs { CategoryItem.create!(category: category) }
61
61
  mock.verify
62
62
 
63
63
  mock.expect(:call, 2, [category.id])
@@ -78,13 +78,13 @@ class DependenciesTest < ActiveSupport::TestCase
78
78
  ) { |id| mock.call(id) }
79
79
 
80
80
  mock.expect(:call, 1, [category.id])
81
- CategoryItem.create!(category: category)
81
+ perform_enqueued_jobs { CategoryItem.create!(category: category) }
82
82
  mock.verify
83
83
 
84
- category.update!(name: "hockey")
84
+ perform_enqueued_jobs { category.update!(name: "hockey") }
85
85
 
86
86
  mock.expect(:call, 1, [category.id])
87
- CategoryItem.create!(category: category)
87
+ perform_enqueued_jobs { CategoryItem.create!(category: category) }
88
88
  assert_raises(MockExpectationError) { mock.verify }
89
89
  end
90
90
 
@@ -102,7 +102,7 @@ class DependenciesTest < ActiveSupport::TestCase
102
102
  mock.verify
103
103
 
104
104
  mock.expect(:call, 2, [category.id])
105
- store.update!(name: "foo")
105
+ perform_enqueued_jobs { store.update!(name: "foo") }
106
106
  mock.verify
107
107
 
108
108
  mock.expect(:call, 3, [category.id])
@@ -142,7 +142,7 @@ class DependenciesTest < ActiveSupport::TestCase
142
142
 
143
143
  mock.expect(:call, 1, [category.id])
144
144
  mock.expect(:call, 1, [category.id])
145
- Store.create!(name: "foo", categories: [category])
145
+ perform_enqueued_jobs { Store.create!(name: "foo", categories: [category]) }
146
146
  mock.verify
147
147
 
148
148
  mock.expect(:call, 2, [category.id])
@@ -164,13 +164,13 @@ class DependenciesTest < ActiveSupport::TestCase
164
164
 
165
165
  mock.expect(:call, 1, [category.id])
166
166
  mock.expect(:call, 1, [category.id])
167
- Store.create!(name: "foo", categories: [category])
167
+ perform_enqueued_jobs { Store.create!(name: "foo", categories: [category]) }
168
168
  mock.verify
169
169
 
170
170
  category.update!(name: "hockey")
171
171
 
172
172
  mock.expect(:call, 2, [category.id])
173
- Store.create!(name: "bar", categories: [category])
173
+ perform_enqueued_jobs { Store.create!(name: "bar", categories: [category]) }
174
174
  assert_raises(MockExpectationError) { mock.verify }
175
175
  end
176
176
 
@@ -188,7 +188,7 @@ class DependenciesTest < ActiveSupport::TestCase
188
188
  mock.verify
189
189
 
190
190
  mock.expect(:call, 2, [category.id])
191
- product.update!(name: "foo")
191
+ perform_enqueued_jobs { product.update!(name: "foo") }
192
192
  mock.verify
193
193
 
194
194
  mock.expect(:call, 3, [category.id])
@@ -210,7 +210,7 @@ class DependenciesTest < ActiveSupport::TestCase
210
210
  mock.verify
211
211
 
212
212
  mock.expect(:call, 2, [category.id])
213
- product.update!(name: "foo")
213
+ perform_enqueued_jobs { product.update!(name: "foo") }
214
214
  assert_raises(MockExpectationError) { mock.verify }
215
215
 
216
216
  mock.expect(:call, 3, [category.id])
@@ -227,7 +227,7 @@ class DependenciesTest < ActiveSupport::TestCase
227
227
  end
228
228
 
229
229
  mock.expect(:call, 1, [category.id])
230
- Product.create!(categories: [category])
230
+ perform_enqueued_jobs { Product.create!(categories: [category]) }
231
231
  mock.verify
232
232
 
233
233
  mock.expect(:call, 2, [category.id])
@@ -248,13 +248,13 @@ class DependenciesTest < ActiveSupport::TestCase
248
248
  ) { |id| mock.call(id) }
249
249
 
250
250
  mock.expect(:call, 1, [category.id])
251
- Product.create!(categories: [category])
251
+ perform_enqueued_jobs { Product.create!(categories: [category]) }
252
252
  mock.verify
253
253
 
254
254
  category.update!(name: "hockey")
255
255
 
256
256
  mock.expect(:call, 2, [category.id])
257
- Product.create!(categories: [category])
257
+ perform_enqueued_jobs { Product.create!(categories: [category]) }
258
258
  assert_raises(MockExpectationError) { mock.verify }
259
259
  end
260
260
 
@@ -270,13 +270,13 @@ class DependenciesTest < ActiveSupport::TestCase
270
270
  ) { |id| mock.call(id) }
271
271
 
272
272
  mock.expect(:call, 1, [store.id])
273
- category.update!(name: "foo")
273
+ perform_enqueued_jobs { category.update!(name: "foo") }
274
274
  mock.verify
275
275
 
276
276
  mock.expect(:call, 2, [store.id])
277
277
  assert_equal 1, Thermos.drink(key: "key", id: store.id)
278
278
  assert_raises(MockExpectationError) { mock.verify }
279
- Product.create!(categories: [category])
279
+ perform_enqueued_jobs { Product.create!(categories: [category]) }
280
280
  mock.verify
281
281
 
282
282
  mock.expect(:call, 3, [store.id])
@@ -300,7 +300,7 @@ class DependenciesTest < ActiveSupport::TestCase
300
300
 
301
301
  category_mock.expect(:call, 2, [category.id])
302
302
  product_mock.expect(:call, 2, [product.id])
303
- product.update!(name: "foo")
303
+ perform_enqueued_jobs { product.update!(name: "foo") }
304
304
  assert_raises(MockExpectationError) { category_mock.verify }
305
305
  product_mock.verify
306
306
  end
@@ -1,7 +1,7 @@
1
1
  class Category < ActiveRecord::Base
2
2
  has_many :category_items
3
3
  has_many :products, through: :category_items
4
- belongs_to :store
4
+ belongs_to :store, optional: true
5
5
 
6
6
  def ball?
7
7
  name.match("ball")
@@ -1,4 +1,4 @@
1
1
  class CategoryItem < ActiveRecord::Base
2
- belongs_to :category
3
- belongs_to :product
2
+ belongs_to :category, optional: true
3
+ belongs_to :product, optional: true
4
4
  end
@@ -1,20 +1,33 @@
1
1
  require File.expand_path("../boot", __FILE__)
2
2
 
3
- require "rails/all"
3
+ require "rails"
4
+ require "active_model/railtie"
5
+ require "active_job/railtie"
6
+ require "active_record/railtie"
7
+ require "action_controller/railtie"
8
+ require "action_mailer/railtie"
9
+ require "action_view/railtie"
10
+ # require "active_storage/engine"
11
+ # require "action_mailbox/engine"
12
+ # require "action_text/engine"
13
+ require "rails/test_unit/railtie"
4
14
 
5
15
  Bundler.require(*Rails.groups)
6
16
  require "thermos"
7
17
 
8
18
  module Dummy
9
19
  class Application < Rails::Application
20
+ # Initialize configuration defaults for originally generated Rails version.
21
+ config.load_defaults Rails::VERSION::STRING.to_f
22
+
10
23
  # Settings in config/environments/* take precedence over those specified here.
11
24
  # Application configuration should go into files in config/initializers
12
25
  # -- all .rb files in that directory are automatically loaded.
13
- # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
14
- # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
15
- # config.time_zone = 'Central Time (US & Canada)'
16
- # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
17
- # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
18
- # config.i18n.default_locale = :de
26
+
27
+ # Don't generate system test files.
28
+ config.generators.system_tests = nil
29
+
30
+ # Use a real queuing backend for Active Job (and separate queues per environment).
31
+ config.active_job.queue_adapter = :test
19
32
  end
20
33
  end
@@ -1,17 +1,23 @@
1
1
  Rails.application.configure do
2
2
  # Settings specified here will take precedence over those in config/application.rb.
3
3
 
4
- # In the development environment your application's code is reloaded on
5
- # every request. This slows down response time but is perfect for development
4
+ # In the development environment your application's code is reloaded any time
5
+ # it changes. This slows down response time but is perfect for development
6
6
  # since you don't have to restart the web server when you make code changes.
7
- config.cache_classes = false
7
+ config.enable_reloading = true
8
8
 
9
9
  # Do not eager load code on boot.
10
10
  config.eager_load = false
11
11
 
12
- # Show full error reports and disable caching.
12
+ # Show full error reports.
13
13
  config.consider_all_requests_local = true
14
+
15
+ # Enable server timing.
16
+ config.server_timing = true
17
+
18
+ # Enable/disable caching. By default caching is disabled.
14
19
  config.action_controller.perform_caching = false
20
+ config.cache_store = :memory_store
15
21
 
16
22
  # Don't care if the mailer can't send.
17
23
  config.action_mailer.raise_delivery_errors = false
@@ -22,20 +28,6 @@ Rails.application.configure do
22
28
  # Raise an error on page load if there are pending migrations.
23
29
  config.active_record.migration_error = :page_load
24
30
 
25
- # Debug mode disables concatenation and preprocessing of assets.
26
- # This option may cause significant delays in view rendering with a large
27
- # number of complex assets.
28
- # config.assets.debug = true
29
-
30
- # Asset digests allow you to set far-future HTTP expiration dates on all assets,
31
- # yet still be able to expire them through the digest params.
32
- # config.assets.digest = true
33
-
34
- # Adds additional error checking when serving assets at runtime.
35
- # Checks for improperly declared sprockets dependencies.
36
- # Raises helpful error messages.
37
- # config.assets.raise_runtime_errors = true
38
-
39
31
  # Raises error for missing translations
40
- # config.action_view.raise_on_missing_translations = true
32
+ # config.i18n.raise_on_missing_translations = true
41
33
  end
@@ -2,78 +2,26 @@ Rails.application.configure do
2
2
  # Settings specified here will take precedence over those in config/application.rb.
3
3
 
4
4
  # Code is not reloaded between requests.
5
- config.cache_classes = true
5
+ config.enable_reloading = false
6
6
 
7
- # Eager load code on boot. This eager loads most of Rails and
8
- # your application in memory, allowing both threaded web servers
9
- # and those relying on copy on write to perform better.
10
- # Rake tasks automatically ignore this option for performance.
7
+ # Eager load code on boot for better performance and memory savings.
11
8
  config.eager_load = true
12
9
 
13
- # Full error reports are disabled and caching is turned on.
10
+ # Full error reports are disabled.
14
11
  config.consider_all_requests_local = false
15
- config.action_controller.perform_caching = true
16
-
17
- # Enable Rack::Cache to put a simple HTTP cache in front of your application
18
- # Add `rack-cache` to your Gemfile before enabling this.
19
- # For large-scale production use, consider using a caching reverse proxy like
20
- # NGINX, varnish or squid.
21
- # config.action_dispatch.rack_cache = true
22
-
23
- # Disable serving static files from the `/public` folder by default since
24
- # Apache or NGINX already handles this.
25
- config.serve_static_files = ENV["RAILS_SERVE_STATIC_FILES"].present?
26
-
27
- # Compress JavaScripts and CSS.
28
- # config.assets.js_compressor = :uglifier
29
-
30
- # config.assets.css_compressor = :sass
31
-
32
- # Do not fallback to assets pipeline if a precompiled asset is missed.
33
- # config.assets.compile = false
34
-
35
- # Asset digests allow you to set far-future HTTP expiration dates on all assets,
36
- # yet still be able to expire them through the digest params.
37
- # config.assets.digest = true
38
-
39
- # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
40
12
 
41
- # Specifies the header that your server uses for sending files.
42
- # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
43
- # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
44
-
45
- # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
46
- # config.force_ssl = true
47
-
48
- # Use the lowest log level to ensure availability of diagnostic information
49
- # when problems arise.
50
- config.log_level = :debug
51
-
52
- # Prepend all log lines with the following tags.
53
- # config.log_tags = [ :subdomain, :uuid ]
54
-
55
- # Use a different logger for distributed setups.
56
- # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
57
-
58
- # Use a different cache store in production.
59
- # config.cache_store = :mem_cache_store
60
-
61
- # Enable serving of images, stylesheets, and JavaScripts from an asset server.
62
- # config.action_controller.asset_host = 'http://assets.example.com'
13
+ # Turn on fragment caching in view templates.
14
+ config.action_controller.perform_caching = true
63
15
 
64
- # Ignore bad email addresses and do not raise email delivery errors.
65
- # Set this to true and configure the email server for immediate delivery to raise delivery errors.
66
- # config.action_mailer.raise_delivery_errors = false
16
+ # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead.
17
+ config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
67
18
 
68
19
  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
69
20
  # the I18n.default_locale when a translation cannot be found).
70
21
  config.i18n.fallbacks = true
71
22
 
72
- # Send deprecation notices to registered listeners.
73
- config.active_support.deprecation = :notify
74
-
75
- # Use default logging formatter so that PID and timestamp are not suppressed.
76
- config.log_formatter = ::Logger::Formatter.new
23
+ # Don't log any deprecations.
24
+ config.active_support.report_deprecations = false
77
25
 
78
26
  # Do not dump schema after migrations.
79
27
  config.active_record.dump_schema_after_migration = false
@@ -1,27 +1,27 @@
1
1
  Rails.application.configure do
2
2
  # Settings specified here will take precedence over those in config/application.rb.
3
3
 
4
- # The test environment is used exclusively to run your application's
5
- # test suite. You never need to work with it otherwise. Remember that
6
- # your test database is "scratch space" for the test suite and is wiped
7
- # and recreated between test runs. Don't rely on the data there!
8
- config.cache_classes = true
9
-
10
- # Do not eager load code on boot. This avoids loading your whole application
11
- # just for the purpose of running a single test. If you are using a tool that
12
- # preloads Rails for running tests, you may have to set it to true.
13
- config.eager_load = false
14
-
15
- # Configure static file server for tests with Cache-Control for performance.
16
- config.serve_static_files = true
17
- config.static_cache_control = "public, max-age=3600"
18
-
19
- # Show full error reports and disable caching.
4
+ # While tests run files are not watched, reloading is not necessary.
5
+ config.enable_reloading = false
6
+
7
+ # Eager loading loads your entire application. When running a single test locally,
8
+ # this is usually not necessary, and can slow down your test suite. However, it's
9
+ # recommended that you enable it in continuous integration systems to ensure eager
10
+ # loading is working properly before deploying your code.
11
+ config.eager_load = ENV["CI"].present?
12
+
13
+ # Configure public file server for tests with cache-control for performance.
14
+ config.public_file_server.enabled = true
15
+ config.public_file_server.headers = {
16
+ "Cache-Control" => "public, max-age=3600",
17
+ }
18
+
19
+ # Show full error reports.
20
20
  config.consider_all_requests_local = true
21
- config.action_controller.perform_caching = false
21
+ config.cache_store = :memory_store
22
22
 
23
- # Raise exceptions instead of rendering exception templates.
24
- config.action_dispatch.show_exceptions = false
23
+ # Render exception templates for rescuable exceptions and raise for other exceptions.
24
+ config.action_dispatch.show_exceptions = :rescuable
25
25
 
26
26
  # Disable request forgery protection in test environment.
27
27
  config.action_controller.allow_forgery_protection = false
@@ -31,12 +31,9 @@ Rails.application.configure do
31
31
  # ActionMailer::Base.deliveries array.
32
32
  config.action_mailer.delivery_method = :test
33
33
 
34
- # Randomize the order test cases are executed.
35
- config.active_support.test_order = :random
36
-
37
34
  # Print deprecation notices to the stderr.
38
35
  config.active_support.deprecation = :stderr
39
36
 
40
37
  # Raises error for missing translations
41
- # config.action_view.raise_on_missing_translations = true
38
+ # config.i18n.raise_on_missing_translations = true
42
39
  end
@@ -10,31 +10,31 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema[7.1].define(version: 2016_03_26_174530) do
13
+ ActiveRecord::Schema[8.1].define(version: 2016_03_26_174530) do
14
14
  create_table "categories", force: :cascade do |t|
15
+ t.datetime "created_at", precision: nil, null: false
15
16
  t.string "name"
16
17
  t.integer "store_id"
17
- t.datetime "created_at", precision: nil, null: false
18
18
  t.datetime "updated_at", precision: nil, null: false
19
19
  end
20
20
 
21
21
  create_table "category_items", force: :cascade do |t|
22
- t.string "name"
23
22
  t.integer "category_id"
24
- t.integer "product_id"
25
23
  t.datetime "created_at", precision: nil, null: false
24
+ t.string "name"
25
+ t.integer "product_id"
26
26
  t.datetime "updated_at", precision: nil, null: false
27
27
  end
28
28
 
29
29
  create_table "products", force: :cascade do |t|
30
- t.string "name"
31
30
  t.datetime "created_at", precision: nil, null: false
31
+ t.string "name"
32
32
  t.datetime "updated_at", precision: nil, null: false
33
33
  end
34
34
 
35
35
  create_table "stores", force: :cascade do |t|
36
- t.string "name"
37
36
  t.datetime "created_at", precision: nil, null: false
37
+ t.string "name"
38
38
  t.datetime "updated_at", precision: nil, null: false
39
39
  end
40
40
  end
data/test/filter_test.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require "test_helper"
2
2
 
3
- class FilterTest < ActiveSupport::TestCase
3
+ class FilterTest < ActiveJob::TestCase
4
4
  self.use_transactional_tests = true
5
5
  teardown :clear_cache
6
6
 
@@ -16,11 +16,11 @@ class FilterTest < ActiveSupport::TestCase
16
16
  ) { |name| mock.call(name) }
17
17
 
18
18
  mock.expect(:call, 1, ["basketball"])
19
- category.update!(name: "basketball")
19
+ perform_enqueued_jobs { category.update!(name: "basketball") }
20
20
  mock.verify
21
21
 
22
22
  mock.expect(:call, 1, ["hockey"])
23
- category.update!(name: "hockey")
23
+ perform_enqueued_jobs { category.update!(name: "hockey") }
24
24
  assert_raises(MockExpectationError) { mock.verify }
25
25
  end
26
26
 
data/test/test_helper.rb CHANGED
@@ -6,11 +6,11 @@ ActiveRecord::Migrator.migrations_paths = [
6
6
  File.expand_path("../../test/dummy/db/migrate", __FILE__),
7
7
  ]
8
8
  require "rails/test_help"
9
+ require "minitest/mock"
9
10
 
10
11
  # Filter out Minitest backtrace while allowing backtrace from other libraries
11
12
  # to be shown.
12
13
  Minitest.backtrace_filter = Minitest::BacktraceFilter.new
13
- require "minitest/mock"
14
14
 
15
15
  # Load support files
16
16
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
@@ -29,7 +29,7 @@ elsif ActiveSupport::TestCase.respond_to?(:fixture_paths=)
29
29
  end
30
30
  ActiveSupport::TestCase.fixtures :all
31
31
 
32
- ActiveJob::Base.queue_adapter = :inline
32
+ ActiveJob::Base.queue_adapter = :test
33
33
  ActiveSupport.test_order = :random
34
34
 
35
35
  def clear_cache
data/test/thermos_test.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require "test_helper"
2
2
 
3
- class ThermosTest < ActiveSupport::TestCase
3
+ class ThermosTest < ActiveJob::TestCase
4
4
  self.use_transactional_tests = true
5
5
  teardown :clear_cache
6
6
 
@@ -50,7 +50,7 @@ class ThermosTest < ActiveSupport::TestCase
50
50
  mock.verify
51
51
 
52
52
  mock.expect(:call, 2, [category.id])
53
- category.update!(name: "foo")
53
+ perform_enqueued_jobs { category.update!(name: "foo") }
54
54
  mock.verify
55
55
 
56
56
  mock.expect(:call, 3, [category.id])
@@ -92,7 +92,7 @@ class ThermosTest < ActiveSupport::TestCase
92
92
  mock.verify
93
93
 
94
94
  mock.expect(:call, 1, [category.id])
95
- category.update!(name: "foo")
95
+ perform_enqueued_jobs { category.update!(name: "foo") }
96
96
  mock.verify
97
97
 
98
98
  mock.expect(:call, 3, [other_category.id])
@@ -123,7 +123,7 @@ class ThermosTest < ActiveSupport::TestCase
123
123
  end
124
124
 
125
125
  mock.expect(:call, 1, ["foo"])
126
- Category.create!(name: "foo")
126
+ perform_enqueued_jobs { Category.create!(name: "foo") }
127
127
  mock.verify
128
128
 
129
129
  mock.expect(:call, 2, ["foo"])
@@ -144,7 +144,7 @@ class ThermosTest < ActiveSupport::TestCase
144
144
  mock.verify
145
145
 
146
146
  mock.expect(:call, 2, ["foo"])
147
- category.update!(name: "foo")
147
+ perform_enqueued_jobs { category.update!(name: "foo") }
148
148
  mock.verify
149
149
 
150
150
  mock.expect(:call, 3, [category.name])
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thermos
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Thal
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-19 00:00:00.000000000 Z
11
+ date: 2025-12-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,30 +16,36 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '7.1'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '9'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
27
  - - ">="
25
28
  - !ruby/object:Gem::Version
26
- version: '0'
29
+ version: '7.1'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '9'
27
33
  - !ruby/object:Gem::Dependency
28
- name: rake
34
+ name: minitest
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
- - - ">="
37
+ - - "~>"
32
38
  - !ruby/object:Gem::Version
33
- version: '0'
39
+ version: '5.0'
34
40
  type: :development
35
41
  prerelease: false
36
42
  version_requirements: !ruby/object:Gem::Requirement
37
43
  requirements:
38
- - - ">="
44
+ - - "~>"
39
45
  - !ruby/object:Gem::Version
40
- version: '0'
46
+ version: '5.0'
41
47
  - !ruby/object:Gem::Dependency
42
- name: sqlite3
48
+ name: rake
43
49
  requirement: !ruby/object:Gem::Requirement
44
50
  requirements:
45
51
  - - ">="
@@ -53,7 +59,7 @@ dependencies:
53
59
  - !ruby/object:Gem::Version
54
60
  version: '0'
55
61
  - !ruby/object:Gem::Dependency
56
- name: prettier
62
+ name: sqlite3
57
63
  requirement: !ruby/object:Gem::Requirement
58
64
  requirements:
59
65
  - - ">="
@@ -67,22 +73,26 @@ dependencies:
67
73
  - !ruby/object:Gem::Version
68
74
  version: '0'
69
75
  - !ruby/object:Gem::Dependency
70
- name: psych
76
+ name: prettier
71
77
  requirement: !ruby/object:Gem::Requirement
72
78
  requirements:
73
- - - "<"
79
+ - - ">="
74
80
  - !ruby/object:Gem::Version
75
- version: 4.0.0
81
+ version: '0'
76
82
  type: :development
77
83
  prerelease: false
78
84
  version_requirements: !ruby/object:Gem::Requirement
79
85
  requirements:
80
- - - "<"
86
+ - - ">="
81
87
  - !ruby/object:Gem::Version
82
- version: 4.0.0
88
+ version: '0'
83
89
  description: |
84
- Thermos is a library for caching in rails that re-warms caches
85
- 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.
86
96
  email:
87
97
  - athal7@me.com
88
98
  executables: []
@@ -90,6 +100,7 @@ extensions: []
90
100
  extra_rdoc_files: []
91
101
  files:
92
102
  - MIT-LICENSE
103
+ - README.md
93
104
  - Rakefile
94
105
  - lib/thermos.rb
95
106
  - lib/thermos/beverage.rb
@@ -130,7 +141,6 @@ files:
130
141
  - test/dummy/config/initializers/filter_parameter_logging.rb
131
142
  - test/dummy/config/initializers/inflections.rb
132
143
  - test/dummy/config/initializers/mime_types.rb
133
- - test/dummy/config/initializers/session_store.rb
134
144
  - test/dummy/config/initializers/wrap_parameters.rb
135
145
  - test/dummy/config/locales/en.yml
136
146
  - test/dummy/config/routes.rb
@@ -155,26 +165,31 @@ files:
155
165
  homepage: https://github.com/athal7/thermos
156
166
  licenses:
157
167
  - MIT
158
- metadata: {}
159
- post_install_message:
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'
173
+ post_install_message:
160
174
  rdoc_options: []
161
175
  require_paths:
162
176
  - lib
163
177
  required_ruby_version: !ruby/object:Gem::Requirement
164
178
  requirements:
165
- - - "~>"
179
+ - - ">="
166
180
  - !ruby/object:Gem::Version
167
- version: '3.0'
181
+ version: '3.2'
168
182
  required_rubygems_version: !ruby/object:Gem::Requirement
169
183
  requirements:
170
184
  - - ">="
171
185
  - !ruby/object:Gem::Version
172
186
  version: '0'
173
187
  requirements: []
174
- rubygems_version: 3.5.3
175
- signing_key:
188
+ rubygems_version: 3.5.22
189
+ signing_key:
176
190
  specification_version: 4
177
- 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.
178
193
  test_files:
179
194
  - test/dependencies_test.rb
180
195
  - test/dummy/README.rdoc
@@ -206,7 +221,6 @@ test_files:
206
221
  - test/dummy/config/initializers/filter_parameter_logging.rb
207
222
  - test/dummy/config/initializers/inflections.rb
208
223
  - test/dummy/config/initializers/mime_types.rb
209
- - test/dummy/config/initializers/session_store.rb
210
224
  - test/dummy/config/initializers/wrap_parameters.rb
211
225
  - test/dummy/config/locales/en.yml
212
226
  - test/dummy/config/routes.rb
@@ -1,3 +0,0 @@
1
- # Be sure to restart your server when you modify this file.
2
-
3
- Rails.application.config.session_store :cookie_store, key: "_dummy_session"