thermos 0.2.1 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ad51a5746942e6b4ebba54cb95f906b7521f25a4c746b4a3d22e1855e935c7e
4
- data.tar.gz: 4c0a9dceea7a709c7b3ce541fbd9895f7585ab889b3b80f05f6d564fd1cb0ff2
3
+ metadata.gz: 87ceed3d0f1b54b2cd9e9b0afde15e90252fcbc9f575c5bf11a66e414045c5d5
4
+ data.tar.gz: dc3bc76cd808f474b80676c5f2d1f2e50681c6028c09412e12eb565b4494cfc4
5
5
  SHA512:
6
- metadata.gz: e4c2c9f520e0b27d0aff7147b8ff1db84ff99cea1e4cd66bf10bd11b33e5a5d13fb3e181ef616ada90bd2dc5ce7343f40ddaf414a92989697851e9d040673e6c
7
- data.tar.gz: 06c365876889c0d7729397613dd7c6c267667138824c43e47d9b670718e696df488424a90914fda58dda29891810005c0fbac503020e0987795a283941792fa2
6
+ metadata.gz: d86ada3f6a3b07ed8bbf35c481e73ac4563a0aced184f6e47a986847287f0bf4a3c02c42e5d7600db1f5d8c2358f5ba4975fd20c4ccceee8fc3ec0cfcaae7811
7
+ data.tar.gz: 51f90cb754d381d81293e433261e09cb4e184dc1dcddd05fbfcbf7baacb577330f6a4cc6a0621cc8cd9964d7677e499554a8dd533d65762adb421d56cef58458
data/lib/thermos.rb CHANGED
@@ -8,14 +8,14 @@ require 'thermos/refill_job'
8
8
  require 'thermos/rebuild_cache_job'
9
9
 
10
10
  module Thermos
11
- def self.keep_warm(key:, model:, id:, deps: [], lookup_key: nil, &block)
12
- fill(key: key, model: model, deps: deps, lookup_key: lookup_key, &block)
11
+ def self.keep_warm(key:, model:, id:, deps: [], lookup_key: nil, filter: nil, &block)
12
+ fill(key: key, model: model, deps: deps, lookup_key: lookup_key, filter: filter, &block)
13
13
  drink(key: key, id: id)
14
14
  end
15
15
 
16
- def self.fill(key:, model:, deps: [], lookup_key: nil, &block)
16
+ def self.fill(key:, model:, deps: [], lookup_key: nil, filter: nil, &block)
17
17
  BeverageStorage.instance.add_beverage(
18
- Beverage.new(key: key, model: model, deps: deps, action: block, lookup_key: lookup_key)
18
+ Beverage.new(key: key, model: model, deps: deps, action: block, lookup_key: lookup_key, filter: filter)
19
19
  )
20
20
  end
21
21
 
@@ -2,29 +2,33 @@
2
2
 
3
3
  module Thermos
4
4
  class Beverage
5
- attr_reader :key, :model, :deps, :action, :lookup_key
5
+ attr_reader :key, :model, :deps, :action, :lookup_key, :filter
6
6
 
7
- def initialize(key:, model:, deps:, action:, lookup_key: nil)
7
+ def initialize(key:, model:, deps:, action:, lookup_key: nil, filter: nil)
8
8
  @key = key
9
9
  @model = model
10
10
  @lookup_key = lookup_key || :id
11
- @deps = deps.map do |dep|
12
- Dependency.new(model: model, association: dep)
13
- end
11
+ @filter = filter || nil
12
+ @deps = generate_deps(model, deps)
14
13
  @action = action
15
14
 
16
15
  set_observers
17
16
  end
18
17
 
19
18
  def lookup_keys_for_dep_model(dep_model)
20
- @deps.flat_map do |dep|
21
- return [] unless dep.klass == dep_model.class
22
- @model.joins(dep.association)
19
+ @deps.select do |dep|
20
+ dep.klass == dep_model.class
21
+ end.flat_map do |dep|
22
+ @model.joins(dep.path)
23
23
  .where(dep.table => { id: dep_model.id })
24
24
  .pluck(@lookup_key)
25
25
  end.uniq
26
26
  end
27
27
 
28
+ def should_fill?(model)
29
+ @filter.class == Proc ? !!@filter.call(model) : true
30
+ end
31
+
28
32
  private
29
33
 
30
34
  def set_observers
@@ -35,5 +39,34 @@ module Thermos
35
39
  def observe(model)
36
40
  model.include(Notifier) unless model.included_modules.include?(Notifier)
37
41
  end
42
+
43
+ def generate_deps(model, deps, root = nil, path = nil)
44
+ deps.reduce([]) do |acc, dep|
45
+ if dep.is_a? Symbol
46
+ acc << Dependency.new(
47
+ model: root || model,
48
+ ref: model.reflect_on_association(dep),
49
+ path: path || dep)
50
+ elsif dep.is_a? Array
51
+ dep.each do |d|
52
+ acc << Dependency.new(
53
+ model: root || model,
54
+ ref: model.reflect_on_association(d),
55
+ path: path || d)
56
+ end
57
+ elsif dep.is_a? Hash
58
+ dep.each do |k,v|
59
+ ref = model.reflect_on_association(k)
60
+ acc << Dependency.new(
61
+ model: root || model,
62
+ ref: ref,
63
+ path: path || k
64
+ )
65
+ acc.concat(generate_deps(ref.class_name.constantize, v, model, dep))
66
+ end
67
+ end
68
+ acc
69
+ end
70
+ end
38
71
  end
39
72
  end
@@ -2,14 +2,13 @@
2
2
 
3
3
  module Thermos
4
4
  class Dependency
5
- attr_reader :model, :association, :klass, :table
5
+ attr_reader :model, :path, :klass, :table
6
6
 
7
- def initialize(model:, association:)
7
+ def initialize(model:, ref:, path: nil)
8
8
  @model = model
9
- @association = association
10
- reflection = @model.reflections[@association.to_s]
11
- @table = reflection.table_name
12
- @klass = reflection.class_name.constantize
9
+ @path = path
10
+ @table = ref.table_name
11
+ @klass = ref.class_name.constantize
13
12
  end
14
13
  end
15
14
  end
@@ -5,7 +5,7 @@ module Thermos
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- after_save :notify_thermos
8
+ after_commit :notify_thermos
9
9
  end
10
10
 
11
11
  private
@@ -9,7 +9,7 @@ module Thermos
9
9
 
10
10
  def refill_primary_caches(model)
11
11
  BeverageStorage.instance.beverages.each do |beverage|
12
- if beverage.model == model.class
12
+ if beverage.model == model.class && beverage.should_fill?(model)
13
13
  Thermos::RebuildCacheJob.perform_later(beverage.key, model.send(beverage.lookup_key))
14
14
  end
15
15
  end
@@ -17,8 +17,10 @@ module Thermos
17
17
 
18
18
  def refill_dependency_caches(model)
19
19
  BeverageStorage.instance.beverages.each do |beverage|
20
- beverage.lookup_keys_for_dep_model(model).each do |lookup_key|
21
- Thermos::RebuildCacheJob.perform_later(beverage.key, lookup_key)
20
+ if beverage.should_fill?(model)
21
+ beverage.lookup_keys_for_dep_model(model).each do |lookup_key|
22
+ Thermos::RebuildCacheJob.perform_later(beverage.key, lookup_key)
23
+ end
22
24
  end
23
25
  end
24
26
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Thermos
4
- VERSION = '0.2.1'
4
+ VERSION = '0.5.0'
5
5
  end
@@ -18,10 +18,6 @@ module Dummy
18
18
  # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
19
19
  # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
20
20
  # config.i18n.default_locale = :de
21
-
22
- if Rails.version >= "5.2.0"
23
- Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
24
- end
25
21
  end
26
22
  end
27
23
 
@@ -2,11 +2,11 @@
2
2
  # of editing this file, please use the migrations feature of Active Record to
3
3
  # incrementally modify your database, and then regenerate this schema definition.
4
4
  #
5
- # Note that this schema.rb definition is the authoritative source for your
6
- # database schema. If you need to create the application database on another
7
- # system, you should be using db:schema:load, not running all the migrations
8
- # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9
- # you'll amass, the slower it'll run and the greater likelihood for issues).
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
data/test/thermos_test.rb CHANGED
@@ -65,6 +65,30 @@ class ThermosTest < ActiveSupport::TestCase
65
65
  assert_raises(MockExpectationError) { mock.verify }
66
66
  end
67
67
 
68
+ test "does not rebuild the cache on rolled back primary model change" do
69
+ mock = Minitest::Mock.new
70
+ category = categories(:baseball)
71
+
72
+ Thermos.fill(key: "key", model: Category) do |id|
73
+ mock.call(id)
74
+ end
75
+
76
+ mock.expect(:call, 1, [category.id])
77
+ assert_equal 1, Thermos.drink(key: "key", id: category.id)
78
+ mock.verify
79
+
80
+ mock.expect(:call, 2, [category.id])
81
+ ActiveRecord::Base.transaction do
82
+ category.update!(name: "foo")
83
+ raise ActiveRecord::Rollback
84
+ end
85
+ assert_raises(MockExpectationError) { mock.verify }
86
+
87
+ mock.expect(:call, 3, [category.id])
88
+ assert_equal 1, Thermos.drink(key: "key", id: category.id)
89
+ assert_raises(MockExpectationError) { mock.verify }
90
+ end
91
+
68
92
  test "does not rebuild the cache for an unrelated primary model change" do
69
93
  mock = Minitest::Mock.new
70
94
  category = categories(:baseball)
@@ -102,6 +126,23 @@ class ThermosTest < ActiveSupport::TestCase
102
126
  assert_equal 1, Thermos.drink(key: "key", id: "foo")
103
127
  assert_raises(MockExpectationError) { mock.verify }
104
128
  end
129
+
130
+ test "allows filtering for which records should be rebuilt" do
131
+ mock = Minitest::Mock.new
132
+ category = categories(:baseball)
133
+ filter = ->(model) { model.name.match("ball") }
134
+ Thermos.fill(key: "key", model: Category, lookup_key: "name", filter: filter) do |name|
135
+ mock.call(name)
136
+ end
137
+
138
+ mock.expect(:call, 1, ["basketball"])
139
+ category.update!(name: "basketball")
140
+ mock.verify
141
+
142
+ mock.expect(:call, 1, ["hockey"])
143
+ category.update!(name: "hockey")
144
+ assert_raises(MockExpectationError) { mock.verify }
145
+ end
105
146
 
106
147
  # has_many model changes
107
148
  test "rebuilds the cache on has_many model change" do
@@ -290,6 +331,30 @@ class ThermosTest < ActiveSupport::TestCase
290
331
  assert_raises(MockExpectationError) { mock.verify }
291
332
  end
292
333
 
334
+ test "handles indirect associations" do
335
+ mock = Minitest::Mock.new
336
+ category = categories(:baseball)
337
+ store = category.store
338
+
339
+ Thermos.fill(key: "key", model: Store, deps: [categories: [:products]]) do |id|
340
+ mock.call(id)
341
+ end
342
+
343
+ mock.expect(:call, 1, [store.id])
344
+ category.update!(name: "foo")
345
+ mock.verify
346
+
347
+ mock.expect(:call, 2, [store.id])
348
+ assert_equal 1, Thermos.drink(key: "key", id: store.id)
349
+ assert_raises(MockExpectationError) { mock.verify }
350
+ Product.create!(categories: [category])
351
+ mock.verify
352
+
353
+ mock.expect(:call, 3, [store.id])
354
+ assert_equal 2, Thermos.drink(key: "key", id: store.id)
355
+ assert_raises(MockExpectationError) { mock.verify }
356
+ end
357
+
293
358
  test "only rebuilds cache for stated dependencies, even if another cache has an associated model of the primary" do
294
359
  category_mock = Minitest::Mock.new
295
360
  product_mock = Minitest::Mock.new
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: 0.2.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Thal
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-02 00:00:00.000000000 Z
11
+ date: 2021-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,14 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 5.0.0
19
+ version: 5.2.4
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: 6.2.0
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: 5.0.0
29
+ version: 5.2.4
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: 6.2.0
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: rake
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -134,64 +140,67 @@ required_ruby_version: !ruby/object:Gem::Requirement
134
140
  - - ">="
135
141
  - !ruby/object:Gem::Version
136
142
  version: 2.5.0
143
+ - - "<"
144
+ - !ruby/object:Gem::Version
145
+ version: 2.8.0
137
146
  required_rubygems_version: !ruby/object:Gem::Requirement
138
147
  requirements:
139
148
  - - ">="
140
149
  - !ruby/object:Gem::Version
141
150
  version: '0'
142
151
  requirements: []
143
- rubygems_version: 3.0.3
152
+ rubygems_version: 3.0.3.1
144
153
  signing_key:
145
154
  specification_version: 4
146
155
  summary: Always-warm, auto-rebuilding rails caching without timers or touching.
147
156
  test_files:
148
- - test/fixtures/category_items.yml
157
+ - test/thermos_test.rb
149
158
  - test/fixtures/categories.yml
150
- - test/fixtures/stores.yml
151
159
  - test/fixtures/products.yml
160
+ - test/fixtures/stores.yml
161
+ - test/fixtures/category_items.yml
162
+ - test/dummy/README.rdoc
163
+ - test/dummy/config.ru
164
+ - test/dummy/Rakefile
165
+ - test/dummy/db/schema.rb
166
+ - test/dummy/db/migrate/20160325214744_create_categories.rb
167
+ - test/dummy/db/migrate/20160326174530_create_stores.rb
168
+ - test/dummy/db/migrate/20160325214849_create_products.rb
169
+ - test/dummy/db/migrate/20160325220006_create_category_items.rb
170
+ - test/dummy/config/environment.rb
171
+ - test/dummy/config/secrets.yml
152
172
  - test/dummy/config/application.rb
173
+ - test/dummy/config/environments/test.rb
174
+ - test/dummy/config/environments/development.rb
175
+ - test/dummy/config/environments/production.rb
153
176
  - test/dummy/config/boot.rb
154
- - test/dummy/config/routes.rb
155
177
  - test/dummy/config/locales/en.yml
156
- - test/dummy/config/initializers/backtrace_silencers.rb
178
+ - test/dummy/config/database.yml
157
179
  - test/dummy/config/initializers/cookies_serializer.rb
158
- - test/dummy/config/initializers/wrap_parameters.rb
180
+ - test/dummy/config/initializers/assets.rb
181
+ - test/dummy/config/initializers/backtrace_silencers.rb
182
+ - test/dummy/config/initializers/mime_types.rb
159
183
  - test/dummy/config/initializers/session_store.rb
184
+ - test/dummy/config/initializers/wrap_parameters.rb
160
185
  - test/dummy/config/initializers/filter_parameter_logging.rb
161
- - test/dummy/config/initializers/assets.rb
162
186
  - test/dummy/config/initializers/inflections.rb
163
- - test/dummy/config/initializers/mime_types.rb
164
- - test/dummy/config/secrets.yml
165
- - test/dummy/config/environment.rb
166
- - test/dummy/config/database.yml
167
- - test/dummy/config/environments/development.rb
168
- - test/dummy/config/environments/production.rb
169
- - test/dummy/config/environments/test.rb
170
- - test/dummy/bin/setup
171
- - test/dummy/bin/rails
172
- - test/dummy/bin/bundle
173
- - test/dummy/bin/rake
174
- - test/dummy/app/views/layouts/application.html.erb
175
- - test/dummy/app/models/store.rb
176
- - test/dummy/app/models/category.rb
177
- - test/dummy/app/models/product.rb
187
+ - test/dummy/config/routes.rb
188
+ - test/dummy/public/500.html
189
+ - test/dummy/public/404.html
190
+ - test/dummy/public/422.html
191
+ - test/dummy/public/favicon.ico
178
192
  - test/dummy/app/models/category_item.rb
179
- - test/dummy/app/assets/config/manifest.js
180
- - test/dummy/app/assets/stylesheets/application.css
193
+ - test/dummy/app/models/product.rb
194
+ - test/dummy/app/models/category.rb
195
+ - test/dummy/app/models/store.rb
196
+ - test/dummy/app/helpers/application_helper.rb
197
+ - test/dummy/app/views/layouts/application.html.erb
181
198
  - test/dummy/app/assets/javascripts/application.js
199
+ - test/dummy/app/assets/stylesheets/application.css
200
+ - test/dummy/app/assets/config/manifest.js
182
201
  - test/dummy/app/controllers/application_controller.rb
183
- - test/dummy/app/helpers/application_helper.rb
184
- - test/dummy/db/schema.rb
185
- - test/dummy/db/migrate/20160325214849_create_products.rb
186
- - test/dummy/db/migrate/20160325220006_create_category_items.rb
187
- - test/dummy/db/migrate/20160325214744_create_categories.rb
188
- - test/dummy/db/migrate/20160326174530_create_stores.rb
189
- - test/dummy/public/404.html
190
- - test/dummy/public/favicon.ico
191
- - test/dummy/public/500.html
192
- - test/dummy/public/422.html
193
- - test/dummy/config.ru
194
- - test/dummy/Rakefile
195
- - test/dummy/README.rdoc
196
- - test/thermos_test.rb
202
+ - test/dummy/bin/setup
203
+ - test/dummy/bin/bundle
204
+ - test/dummy/bin/rake
205
+ - test/dummy/bin/rails
197
206
  - test/test_helper.rb