support_table_cache 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +10 -0
- data/MIT-LICENSE +20 -0
- data/README.md +102 -0
- data/VERSION +1 -0
- data/lib/support_table_cache.rb +165 -0
- data/support_table_cache.gemspec +33 -0
- metadata +77 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6c7814572a411ad62d5077cf255ff24059d2ab9292be81ded041de7ecff8a6ac
|
4
|
+
data.tar.gz: 015ea25fb5083ce1a80579a143486bf44a091fa375bf0d122b0889763321852c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c4a1f00fa0873f0e28501119c5b588a6df166f5f9bcee84fcaf3dc4ffe0985251a6d6e6ee24f00d2ee740bd02b1dfe74db425a7a8170d8d5ddd98fbed0c8ecde
|
7
|
+
data.tar.gz: 81de8cda7452731a341e82752f3a25ab793c1b15e518c41c78118348841b41f5f8c56af13304e2565e9bfeffc00210764bf0351e4920d56af0e4e49704fdfde7
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## 1.0.0
|
8
|
+
|
9
|
+
### Added
|
10
|
+
- Add SupportTableCache concern to enable automatic caching on models when calling `find_by` with unique key parameters.
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2022 Brian Durand
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Support Table Cache
|
2
|
+
|
3
|
+
[![Continuous Integration](https://github.com/bdurand/support_table_cache/actions/workflows/continuous_integration.yml/badge.svg)](https://github.com/bdurand/support_table_cache/actions/workflows/continuous_integration.yml)
|
4
|
+
[![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
|
5
|
+
|
6
|
+
This gem adds caching for ActiveRecord support table models. These are models which have a unique key (i.e. a unique `name` attribute, etc.) and which have a limited number of entries (a few hundred at most). These are often models added do normalize the data structure.
|
7
|
+
|
8
|
+
Rows from these kinds of tables are rarely inserted, updated, or deleted, but are queried very frequently. To take advantage of this behavior, this gem adds automatic caching for records when using the `find_by` method. This is most useful in situations where you have a unique key but need to get the database row for that key.
|
9
|
+
|
10
|
+
For instance, suppose you have a model `Status` that has a unique name attribute and you need to process a bunch of records from a data source that includes the status name. In order to do anything, you'll need to lookup each status by name to get the database id:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
params.each do |data|
|
14
|
+
status = Status.find_by(name: data[:status]
|
15
|
+
Things.where(id: data[:id]).update!(status_id: status.id)
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
With this gem, you can avoid the database query for the `find_by` call. You don't need to alter your code in any way other than to include `SupportTableCache` in your model and tell it which attributes comprise a unique key that can be used for caching.
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
To use the gem, you need to include it in you models and then specify which attributes can be used for caching with the `cache_by` method. A caching attribute must be a unique key on the model. For a composite key, you can specify an array of attributes. If any of the attributes are case insensitive strings, you need to specify that as well.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class MyModel < ApplicationRecord
|
27
|
+
include SupportTableCache
|
28
|
+
|
29
|
+
cache_by :id
|
30
|
+
cache_by [:group, :name], case_sensitive: false
|
31
|
+
end
|
32
|
+
|
33
|
+
# Uses cache
|
34
|
+
MyModel.find_by(id: 1)
|
35
|
+
|
36
|
+
# Uses cache on a composite key
|
37
|
+
MyModel.find_by(group: "first", name: "One")
|
38
|
+
|
39
|
+
# Does not use cache since value is not defined as a cacheable key
|
40
|
+
MyModel.find_by(value: 1)
|
41
|
+
|
42
|
+
# Does not use caching since not using find_by
|
43
|
+
MyModel.where(id: 1).first
|
44
|
+
```
|
45
|
+
|
46
|
+
By default, records will be cleaned up from the cache only when they are modified. However, you can set a time to live on the model after which records will be removed from the cache.
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class MyModel < ApplicationRecord
|
50
|
+
include SupportTableCache
|
51
|
+
|
52
|
+
self.support_table_cache_ttl = 5.minutes
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
If you are in a Rails application, the `Rails.cache` will be used by default to cache records. Otherwise, you need to set the `ActiveSupport::Cache::CacheStore`` to use.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
SupportTableCache.cache = ActiveSupport::Cache::MemoryStore.new
|
60
|
+
```
|
61
|
+
|
62
|
+
You can also disable caching behavior entirely if you want or just within a block. You may want to disable it entirely in test mode if it interferes with your tests.
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
# Disable the cache globally
|
66
|
+
SupportTableCache.disable
|
67
|
+
|
68
|
+
SupportTableCache.enable do
|
69
|
+
# Re-enable the cache for the block
|
70
|
+
SupportTableCache.disable do
|
71
|
+
# Disable it again
|
72
|
+
end
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
## Installation
|
77
|
+
|
78
|
+
Add this line to your application's Gemfile:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
gem "support_table_cache"
|
82
|
+
```
|
83
|
+
|
84
|
+
And then execute:
|
85
|
+
```bash
|
86
|
+
$ bundle
|
87
|
+
```
|
88
|
+
|
89
|
+
Or install it yourself as:
|
90
|
+
```bash
|
91
|
+
$ gem install support_table_cache
|
92
|
+
```
|
93
|
+
|
94
|
+
## Contributing
|
95
|
+
|
96
|
+
Open a pull request on [GitHub](https://github.com/bdurand/support_table_cache).
|
97
|
+
|
98
|
+
Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting.
|
99
|
+
|
100
|
+
## License
|
101
|
+
|
102
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This concern can be added to a model for a support table to add the ability to lookup
|
4
|
+
# entries in these table using Rails.cache when calling find_by rather than hitting the
|
5
|
+
# database every time.
|
6
|
+
module SupportTableCache
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
class_attribute :support_table_cache_by_attributes, instance_accessor: false
|
11
|
+
class_attribute :support_table_cache_ttl, instance_accessor: false
|
12
|
+
|
13
|
+
class << self
|
14
|
+
prepend FindByOverride unless include?(FindByOverride)
|
15
|
+
private :support_table_cache_by_attributes=
|
16
|
+
end
|
17
|
+
|
18
|
+
after_commit :support_table_clear_cache_entries
|
19
|
+
end
|
20
|
+
|
21
|
+
class_methods do
|
22
|
+
protected
|
23
|
+
|
24
|
+
# Specify which attributes can be used for looking up records in the cache. Each value must
|
25
|
+
# define a unique key, Multiple unique keys can be specified.
|
26
|
+
# If multiple attributes are used to make up a unique key, then they should be passed in as an array.
|
27
|
+
# @param attributes [String, Symbol, Array<String, Symbol>] Attributes that make up a unique key.
|
28
|
+
# @param case_sensitive [Boolean] Indicate if strings should treated as case insensitive in the key.
|
29
|
+
def cache_by(attributes, case_sensitive: true)
|
30
|
+
attributes = Array(attributes).map(&:to_s).sort.freeze
|
31
|
+
self.support_table_cache_by_attributes = (support_table_cache_by_attributes || []) + [[attributes, case_sensitive]]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
# Disable the caching behavior. If a block is specified, then caching is only
|
37
|
+
# disabled for that block. If no block is specified, then caching is disabled
|
38
|
+
# globally.
|
39
|
+
# @param disabled [Boolean] Caching will be disabled if this is true, enabled if false.
|
40
|
+
def disable(disabled = true, &block)
|
41
|
+
if block
|
42
|
+
save_val = Thread.current[:support_table_cache_disabled]
|
43
|
+
begin
|
44
|
+
Thread.current[:support_table_cache_disabled] = !!disabled
|
45
|
+
ensure
|
46
|
+
Thread.current[:support_table_cache_disabled] = save_val
|
47
|
+
end
|
48
|
+
else
|
49
|
+
@disabled = !!disabled
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def enable(&block)
|
54
|
+
disable(false, &block)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return true if caching has been disabled.
|
58
|
+
# @return [Boolean]
|
59
|
+
def disabled?
|
60
|
+
block_value = Thread.current[:support_table_cache_disabled]
|
61
|
+
if block_value.nil?
|
62
|
+
!!(defined?(@disabled) && @disabled)
|
63
|
+
else
|
64
|
+
block_value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
attr_writer :cache
|
69
|
+
|
70
|
+
def cache
|
71
|
+
if defined?(@cache)
|
72
|
+
@cache
|
73
|
+
elsif defined?(Rails.cache)
|
74
|
+
Rails.cache
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Generate a consistent cache key for a set of attributes. Returns nil if the attributes
|
79
|
+
# are not cacheable.
|
80
|
+
# @param klass [Class] The class to
|
81
|
+
# @api private
|
82
|
+
def cache_key(klass, attributes, key_attribute_names, case_sensitive)
|
83
|
+
return nil if attributes.blank? || key_attribute_names.blank?
|
84
|
+
|
85
|
+
sorted_names = attributes.keys.map(&:to_s).sort
|
86
|
+
return nil unless sorted_names == key_attribute_names
|
87
|
+
|
88
|
+
sorted_attributes = {}
|
89
|
+
sorted_names.each do |attribute_name|
|
90
|
+
value = (attributes[attribute_name] || attributes[attribute_name.to_sym])
|
91
|
+
if !case_sensitive && (value.is_a?(String) || value.is_a?(Symbol))
|
92
|
+
value = value.to_s.downcase
|
93
|
+
end
|
94
|
+
sorted_attributes[attribute_name] = value
|
95
|
+
end
|
96
|
+
|
97
|
+
[klass.name, sorted_attributes]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
module FindByOverride
|
102
|
+
# Override for the find_by method that looks in the cache first.
|
103
|
+
def find_by(*args)
|
104
|
+
return super if SupportTableCache.cache.nil? || SupportTableCache.disabled?
|
105
|
+
|
106
|
+
cache_key = nil
|
107
|
+
attributes = args.first if args.size == 1 && args.first.is_a?(Hash)
|
108
|
+
if attributes
|
109
|
+
support_table_cache_by_attributes.each do |attribute_names, case_sensitive|
|
110
|
+
cache_key = SupportTableCache.cache_key(self, attributes, attribute_names, case_sensitive)
|
111
|
+
break if cache_key
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
if cache_key
|
116
|
+
SupportTableCache.cache.fetch(cache_key, expires_in: support_table_cache_ttl) { super }
|
117
|
+
else
|
118
|
+
super
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Remove the cache entry for this record.
|
124
|
+
# @return [void]
|
125
|
+
def uncache
|
126
|
+
cache_by_attributes = self.class.support_table_cache_by_attributes
|
127
|
+
return if cache_by_attributes.blank? || SupportTableCache.cache.nil?
|
128
|
+
|
129
|
+
cache_by_attributes.each do |attribute_names, case_sensitive|
|
130
|
+
attributes = {}
|
131
|
+
attribute_names.each do |name|
|
132
|
+
attributes[name] = self[name]
|
133
|
+
end
|
134
|
+
cache_key = SupportTableCache.cache_key(self.class, attributes, attribute_names, case_sensitive)
|
135
|
+
SupportTableCache.cache.delete(cache_key)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
# Clear all combinations of the cacheable attributes whenever any attribute changes.
|
142
|
+
# We have to make sure to clear the keys with the attribute values both before
|
143
|
+
# and after the change.
|
144
|
+
def support_table_clear_cache_entries
|
145
|
+
cache_by_attributes = self.class.support_table_cache_by_attributes
|
146
|
+
return if cache_by_attributes.blank? || SupportTableCache.cache.nil?
|
147
|
+
|
148
|
+
cache_by_attributes.each do |attribute_names, case_sensitive|
|
149
|
+
attributes_before = {} if saved_change_to_id.blank? || saved_change_to_id.first.present?
|
150
|
+
attributes_after = {} if saved_change_to_id.blank? || saved_change_to_id.last.present?
|
151
|
+
attribute_names.each do |name|
|
152
|
+
if attributes_before
|
153
|
+
attributes_before[name] = (saved_changes.include?(name) ? saved_changes[name].first : self[name])
|
154
|
+
end
|
155
|
+
if attributes_after
|
156
|
+
attributes_after[name] = self[name]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
[attributes_before, attributes_after].compact.uniq.each do |attributes|
|
160
|
+
cache_key = SupportTableCache.cache_key(self.class, attributes, attribute_names, case_sensitive)
|
161
|
+
SupportTableCache.cache.delete(cache_key)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = "support_table_cache"
|
3
|
+
spec.version = File.read(File.expand_path("../VERSION", __FILE__)).strip
|
4
|
+
spec.authors = ["Brian Durand"]
|
5
|
+
spec.email = ["bbdurand@gmail.com"]
|
6
|
+
|
7
|
+
spec.summary = "Automatic ActiveRecord caching for small support tables."
|
8
|
+
|
9
|
+
spec.homepage = "https://github.com/bdurand/support_table_cache"
|
10
|
+
spec.license = "MIT"
|
11
|
+
|
12
|
+
# Specify which files should be added to the gem when it is released.
|
13
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
14
|
+
ignore_files = %w[
|
15
|
+
.
|
16
|
+
Appraisals
|
17
|
+
Gemfile
|
18
|
+
Gemfile.lock
|
19
|
+
Rakefile
|
20
|
+
bin/
|
21
|
+
gemfiles/
|
22
|
+
spec/
|
23
|
+
]
|
24
|
+
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject { |f| ignore_files.any? { |path| f.start_with?(path) } }
|
26
|
+
end
|
27
|
+
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_dependency "activerecord"
|
31
|
+
|
32
|
+
spec.add_development_dependency "bundler"
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: support_table_cache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brian Durand
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-05-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- bbdurand@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- CHANGELOG.md
|
49
|
+
- MIT-LICENSE
|
50
|
+
- README.md
|
51
|
+
- VERSION
|
52
|
+
- lib/support_table_cache.rb
|
53
|
+
- support_table_cache.gemspec
|
54
|
+
homepage: https://github.com/bdurand/support_table_cache
|
55
|
+
licenses:
|
56
|
+
- MIT
|
57
|
+
metadata: {}
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubygems_version: 3.0.3
|
74
|
+
signing_key:
|
75
|
+
specification_version: 4
|
76
|
+
summary: Automatic ActiveRecord caching for small support tables.
|
77
|
+
test_files: []
|