thermos 0.2.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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