activesupport_cache_database 0.3.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +46 -2
- data/.gitignore +1 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile.lock +39 -30
- data/Gemfile.rails6.lock +44 -34
- data/README.md +44 -18
- data/activesupport_cache_database.gemspec +3 -3
- data/lib/active_support/cache/database_store/model.rb +8 -3
- data/lib/active_support/cache/database_store.rb +79 -6
- data/lib/generators/cache/database/install_generator.rb +24 -0
- data/lib/generators/cache/database/templates/add_cache_compression_column.rb +5 -0
- data/lib/generators/cache/database/templates/create_table_for_cache.rb +24 -0
- metadata +11 -8
- data/lib/active_support/cache/database_store/migration.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de874398941cbdc132a9e5053c5ac020907b06824714f440a9cd4f79ffbf63e6
|
4
|
+
data.tar.gz: 8328f4136ca8cc455dad96ea244955779960050f874d9ef55b13106dd3be571c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e68b16bec37e6cc86f3cb41a9767b14db70a8b90a7f83247bb61a70b1e0d31a6d63dcd06c9074610648e64d2b2f13dac5c43d4d852ee6490dc5616d145231ec
|
7
|
+
data.tar.gz: 56d6d46356b8ae59baa1b5e69688cc11e810c768e7b79bdcded38762e001e1aea38a3e74732a139a74560a5263bf1156e460679023c1df00ed3a5355d8c5306b
|
data/.github/workflows/test.yml
CHANGED
@@ -7,18 +7,62 @@ on:
|
|
7
7
|
branches: [main]
|
8
8
|
|
9
9
|
jobs:
|
10
|
+
rubocop:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
steps:
|
13
|
+
- uses: actions/checkout@v3
|
14
|
+
- uses: ruby/setup-ruby@v1
|
15
|
+
with:
|
16
|
+
ruby-version: "3.2"
|
17
|
+
bundler-cache: true
|
18
|
+
- run: bundle exec rake rubocop
|
10
19
|
test:
|
11
20
|
runs-on: ubuntu-latest
|
21
|
+
services:
|
22
|
+
mysql:
|
23
|
+
image: mysql
|
24
|
+
env:
|
25
|
+
MYSQL_DATABASE: test
|
26
|
+
MYSQL_ROOT_PASSWORD: pass
|
27
|
+
ports:
|
28
|
+
- 3306:3306
|
29
|
+
options: >-
|
30
|
+
--health-cmd "mysqladmin ping -ppass"
|
31
|
+
--health-interval 10s
|
32
|
+
--health-start-period 10s
|
33
|
+
--health-timeout 5s
|
34
|
+
--health-retries 10
|
35
|
+
postgres:
|
36
|
+
image: postgres
|
37
|
+
env:
|
38
|
+
POSTGRES_USER: root
|
39
|
+
POSTGRES_PASSWORD: pass
|
40
|
+
POSTGRES_DB: test
|
41
|
+
ports:
|
42
|
+
- 5432:5432
|
43
|
+
options: >-
|
44
|
+
--health-cmd pg_isready
|
45
|
+
--health-interval 10s
|
46
|
+
--health-timeout 5s
|
47
|
+
--health-retries 5
|
12
48
|
strategy:
|
49
|
+
fail-fast: false
|
13
50
|
matrix:
|
14
51
|
ruby-version: ["2.7", "3.0", "3.1", "3.2"]
|
15
|
-
|
52
|
+
gemfile: ["Gemfile", "Gemfile.rails6"]
|
53
|
+
database_url:
|
54
|
+
- "sqlite3:///tmp/test.sqlite3"
|
55
|
+
- "mysql2://root:pass@127.0.0.1/test"
|
56
|
+
- "postgres://root:pass@127.0.0.1/test"
|
16
57
|
env:
|
17
58
|
BUNDLE_GEMFILE: ${{ matrix.gemfile }}
|
59
|
+
DATABASE_URL: ${{ matrix.database_url }}
|
60
|
+
RACK_ENV: test
|
18
61
|
steps:
|
19
62
|
- uses: actions/checkout@v3
|
20
63
|
- uses: ruby/setup-ruby@v1
|
21
64
|
with:
|
22
65
|
ruby-version: ${{ matrix.ruby-version }}
|
23
66
|
bundler-cache: true
|
24
|
-
- run:
|
67
|
+
- run: |
|
68
|
+
bundle exec rake spec
|
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## v0.5.0
|
4
|
+
|
5
|
+
### Added
|
6
|
+
- Adding pluggable compression for cache storage (plain and gzip supported out of the box) [#42](https://github.com/bsm/activesupport-cache-database/pull/42)
|
7
|
+
- #write_multi to insert cache in a single INSERT statement [#41](https://github.com/bsm/activesupport-cache-database/pull/41)
|
8
|
+
- Use migration generator & add a note about unlogged tables for PG [#31](https://github.com/bsm/activesupport-cache-database/pull/31)
|
9
|
+
- Use partial index for expires_at column [#28](https://github.com/bsm/activesupport-cache-database/pull/28)
|
10
|
+
|
11
|
+
### Changed
|
12
|
+
- Test with multiple DB engines; remove Rails 6.0 support [#35](https://github.com/bsm/activesupport-cache-database/pull/35)
|
13
|
+
- Retain support for Rails 6.1 [#37](https://github.com/bsm/activesupport-cache-database/pull/37)
|
14
|
+
|
15
|
+
## v0.4.0 (2023-07-18)
|
16
|
+
|
17
|
+
- Allow to clean up cache entries without `expire_at` [#24](https://github.com/bsm/activesupport-cache-database/pull/24)
|
data/Gemfile.lock
CHANGED
@@ -1,82 +1,91 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
activesupport_cache_database (0.
|
5
|
-
activerecord (>= 6.
|
6
|
-
activesupport (>= 6.
|
4
|
+
activesupport_cache_database (0.5.0)
|
5
|
+
activerecord (>= 6.1)
|
6
|
+
activesupport (>= 6.1)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
activemodel (7.0.
|
12
|
-
activesupport (= 7.0.
|
13
|
-
activerecord (7.0.
|
14
|
-
activemodel (= 7.0.
|
15
|
-
activesupport (= 7.0.
|
16
|
-
activesupport (7.0.
|
11
|
+
activemodel (7.0.7.2)
|
12
|
+
activesupport (= 7.0.7.2)
|
13
|
+
activerecord (7.0.7.2)
|
14
|
+
activemodel (= 7.0.7.2)
|
15
|
+
activesupport (= 7.0.7.2)
|
16
|
+
activesupport (7.0.7.2)
|
17
17
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
18
|
i18n (>= 1.6, < 2)
|
19
19
|
minitest (>= 5.1)
|
20
20
|
tzinfo (~> 2.0)
|
21
21
|
ast (2.4.2)
|
22
|
+
base64 (0.1.1)
|
22
23
|
concurrent-ruby (1.2.2)
|
23
24
|
diff-lcs (1.5.0)
|
24
|
-
i18n (1.
|
25
|
+
i18n (1.14.1)
|
25
26
|
concurrent-ruby (~> 1.0)
|
26
27
|
json (2.6.3)
|
27
|
-
|
28
|
-
|
28
|
+
language_server-protocol (3.17.0.3)
|
29
|
+
mini_portile2 (2.8.4)
|
30
|
+
minitest (5.19.0)
|
29
31
|
mysql2 (0.5.5)
|
30
|
-
parallel (1.
|
31
|
-
parser (3.2.
|
32
|
+
parallel (1.23.0)
|
33
|
+
parser (3.2.2.3)
|
32
34
|
ast (~> 2.4.1)
|
33
|
-
|
35
|
+
racc
|
36
|
+
pg (1.5.3)
|
37
|
+
racc (1.7.1)
|
34
38
|
rainbow (3.1.1)
|
35
39
|
rake (13.0.6)
|
36
|
-
regexp_parser (2.
|
37
|
-
rexml (3.2.
|
40
|
+
regexp_parser (2.8.1)
|
41
|
+
rexml (3.2.6)
|
38
42
|
rspec (3.12.0)
|
39
43
|
rspec-core (~> 3.12.0)
|
40
44
|
rspec-expectations (~> 3.12.0)
|
41
45
|
rspec-mocks (~> 3.12.0)
|
42
|
-
rspec-core (3.12.
|
46
|
+
rspec-core (3.12.2)
|
43
47
|
rspec-support (~> 3.12.0)
|
44
|
-
rspec-expectations (3.12.
|
48
|
+
rspec-expectations (3.12.3)
|
45
49
|
diff-lcs (>= 1.2.0, < 2.0)
|
46
50
|
rspec-support (~> 3.12.0)
|
47
|
-
rspec-mocks (3.12.
|
51
|
+
rspec-mocks (3.12.6)
|
48
52
|
diff-lcs (>= 1.2.0, < 2.0)
|
49
53
|
rspec-support (~> 3.12.0)
|
50
|
-
rspec-support (3.12.
|
51
|
-
rubocop (1.
|
54
|
+
rspec-support (3.12.1)
|
55
|
+
rubocop (1.56.2)
|
56
|
+
base64 (~> 0.1.1)
|
52
57
|
json (~> 2.3)
|
58
|
+
language_server-protocol (>= 3.17.0)
|
53
59
|
parallel (~> 1.10)
|
54
|
-
parser (>= 3.2.
|
60
|
+
parser (>= 3.2.2.3)
|
55
61
|
rainbow (>= 2.2.2, < 4.0)
|
56
62
|
regexp_parser (>= 1.8, < 3.0)
|
57
63
|
rexml (>= 3.2.5, < 4.0)
|
58
|
-
rubocop-ast (>= 1.
|
64
|
+
rubocop-ast (>= 1.28.1, < 2.0)
|
59
65
|
ruby-progressbar (~> 1.7)
|
60
66
|
unicode-display_width (>= 2.4.0, < 3.0)
|
61
|
-
rubocop-ast (1.
|
67
|
+
rubocop-ast (1.29.0)
|
62
68
|
parser (>= 3.2.1.0)
|
63
69
|
rubocop-bsm (0.6.1)
|
64
70
|
rubocop (~> 1.0)
|
65
71
|
rubocop-performance
|
66
72
|
rubocop-rake
|
67
73
|
rubocop-rspec
|
68
|
-
rubocop-capybara (2.
|
74
|
+
rubocop-capybara (2.18.0)
|
69
75
|
rubocop (~> 1.41)
|
70
|
-
rubocop-
|
76
|
+
rubocop-factory_bot (2.23.1)
|
77
|
+
rubocop (~> 1.33)
|
78
|
+
rubocop-performance (1.19.0)
|
71
79
|
rubocop (>= 1.7.0, < 2.0)
|
72
80
|
rubocop-ast (>= 0.4.0)
|
73
81
|
rubocop-rake (0.6.0)
|
74
82
|
rubocop (~> 1.0)
|
75
|
-
rubocop-rspec (2.
|
83
|
+
rubocop-rspec (2.23.2)
|
76
84
|
rubocop (~> 1.33)
|
77
85
|
rubocop-capybara (~> 2.17)
|
78
|
-
|
79
|
-
|
86
|
+
rubocop-factory_bot (~> 2.22)
|
87
|
+
ruby-progressbar (1.13.0)
|
88
|
+
sqlite3 (1.6.4)
|
80
89
|
mini_portile2 (~> 2.8.0)
|
81
90
|
tzinfo (2.0.6)
|
82
91
|
concurrent-ruby (~> 1.0)
|
data/Gemfile.rails6.lock
CHANGED
@@ -1,95 +1,105 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
activesupport_cache_database (0.
|
5
|
-
activerecord (>= 6.
|
6
|
-
activesupport (>= 6.
|
4
|
+
activesupport_cache_database (0.5.0)
|
5
|
+
activerecord (>= 6.1)
|
6
|
+
activesupport (>= 6.1)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
activemodel (6.1.7.
|
12
|
-
activesupport (= 6.1.7.
|
13
|
-
activerecord (6.1.7.
|
14
|
-
activemodel (= 6.1.7.
|
15
|
-
activesupport (= 6.1.7.
|
16
|
-
activesupport (6.1.7.
|
11
|
+
activemodel (6.1.7.6)
|
12
|
+
activesupport (= 6.1.7.6)
|
13
|
+
activerecord (6.1.7.6)
|
14
|
+
activemodel (= 6.1.7.6)
|
15
|
+
activesupport (= 6.1.7.6)
|
16
|
+
activesupport (6.1.7.6)
|
17
17
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
18
18
|
i18n (>= 1.6, < 2)
|
19
19
|
minitest (>= 5.1)
|
20
20
|
tzinfo (~> 2.0)
|
21
21
|
zeitwerk (~> 2.3)
|
22
22
|
ast (2.4.2)
|
23
|
-
|
23
|
+
base64 (0.1.1)
|
24
|
+
concurrent-ruby (1.2.2)
|
24
25
|
diff-lcs (1.5.0)
|
25
|
-
i18n (1.
|
26
|
+
i18n (1.14.1)
|
26
27
|
concurrent-ruby (~> 1.0)
|
27
28
|
json (2.6.3)
|
28
|
-
|
29
|
+
language_server-protocol (3.17.0.3)
|
30
|
+
minitest (5.19.0)
|
29
31
|
mysql2 (0.5.5)
|
30
|
-
parallel (1.
|
31
|
-
parser (3.2.
|
32
|
+
parallel (1.23.0)
|
33
|
+
parser (3.2.2.3)
|
32
34
|
ast (~> 2.4.1)
|
33
|
-
|
35
|
+
racc
|
36
|
+
pg (1.5.3)
|
37
|
+
racc (1.7.1)
|
34
38
|
rainbow (3.1.1)
|
35
39
|
rake (13.0.6)
|
36
|
-
regexp_parser (2.
|
37
|
-
rexml (3.2.
|
40
|
+
regexp_parser (2.8.1)
|
41
|
+
rexml (3.2.6)
|
38
42
|
rspec (3.12.0)
|
39
43
|
rspec-core (~> 3.12.0)
|
40
44
|
rspec-expectations (~> 3.12.0)
|
41
45
|
rspec-mocks (~> 3.12.0)
|
42
|
-
rspec-core (3.12.
|
46
|
+
rspec-core (3.12.2)
|
43
47
|
rspec-support (~> 3.12.0)
|
44
|
-
rspec-expectations (3.12.
|
48
|
+
rspec-expectations (3.12.3)
|
45
49
|
diff-lcs (>= 1.2.0, < 2.0)
|
46
50
|
rspec-support (~> 3.12.0)
|
47
|
-
rspec-mocks (3.12.
|
51
|
+
rspec-mocks (3.12.6)
|
48
52
|
diff-lcs (>= 1.2.0, < 2.0)
|
49
53
|
rspec-support (~> 3.12.0)
|
50
|
-
rspec-support (3.12.
|
51
|
-
rubocop (1.
|
54
|
+
rspec-support (3.12.1)
|
55
|
+
rubocop (1.56.2)
|
56
|
+
base64 (~> 0.1.1)
|
52
57
|
json (~> 2.3)
|
58
|
+
language_server-protocol (>= 3.17.0)
|
53
59
|
parallel (~> 1.10)
|
54
|
-
parser (>= 3.2.
|
60
|
+
parser (>= 3.2.2.3)
|
55
61
|
rainbow (>= 2.2.2, < 4.0)
|
56
62
|
regexp_parser (>= 1.8, < 3.0)
|
57
63
|
rexml (>= 3.2.5, < 4.0)
|
58
|
-
rubocop-ast (>= 1.
|
64
|
+
rubocop-ast (>= 1.28.1, < 2.0)
|
59
65
|
ruby-progressbar (~> 1.7)
|
60
66
|
unicode-display_width (>= 2.4.0, < 3.0)
|
61
|
-
rubocop-ast (1.
|
62
|
-
parser (>= 3.
|
67
|
+
rubocop-ast (1.29.0)
|
68
|
+
parser (>= 3.2.1.0)
|
63
69
|
rubocop-bsm (0.6.1)
|
64
70
|
rubocop (~> 1.0)
|
65
71
|
rubocop-performance
|
66
72
|
rubocop-rake
|
67
73
|
rubocop-rspec
|
68
|
-
rubocop-capybara (2.
|
74
|
+
rubocop-capybara (2.18.0)
|
69
75
|
rubocop (~> 1.41)
|
70
|
-
rubocop-
|
76
|
+
rubocop-factory_bot (2.23.1)
|
77
|
+
rubocop (~> 1.33)
|
78
|
+
rubocop-performance (1.19.0)
|
71
79
|
rubocop (>= 1.7.0, < 2.0)
|
72
80
|
rubocop-ast (>= 0.4.0)
|
73
81
|
rubocop-rake (0.6.0)
|
74
82
|
rubocop (~> 1.0)
|
75
|
-
rubocop-rspec (2.
|
83
|
+
rubocop-rspec (2.23.2)
|
76
84
|
rubocop (~> 1.33)
|
77
85
|
rubocop-capybara (~> 2.17)
|
78
|
-
|
79
|
-
|
86
|
+
rubocop-factory_bot (~> 2.22)
|
87
|
+
ruby-progressbar (1.13.0)
|
88
|
+
sqlite3 (1.6.4-arm64-darwin)
|
89
|
+
sqlite3 (1.6.4-x86_64-linux)
|
80
90
|
tzinfo (2.0.6)
|
81
91
|
concurrent-ruby (~> 1.0)
|
82
92
|
unicode-display_width (2.4.2)
|
83
|
-
zeitwerk (2.6.
|
93
|
+
zeitwerk (2.6.11)
|
84
94
|
|
85
95
|
PLATFORMS
|
96
|
+
arm64-darwin-22
|
86
97
|
x86_64-linux
|
87
98
|
|
88
99
|
DEPENDENCIES
|
89
100
|
activerecord (~> 6.1)
|
90
101
|
activesupport (~> 6.1)
|
91
102
|
activesupport_cache_database!
|
92
|
-
bundler
|
93
103
|
mysql2
|
94
104
|
pg
|
95
105
|
rake
|
@@ -98,4 +108,4 @@ DEPENDENCIES
|
|
98
108
|
sqlite3
|
99
109
|
|
100
110
|
BUNDLED WITH
|
101
|
-
2.
|
111
|
+
2.4.9
|
data/README.md
CHANGED
@@ -11,36 +11,62 @@ Tested with:
|
|
11
11
|
- SQlite3
|
12
12
|
- MySQL/MariaDB
|
13
13
|
|
14
|
-
##
|
14
|
+
## Install
|
15
|
+
Add gem to Gemfile and bundle install.
|
15
16
|
|
16
|
-
|
17
|
+
```gem 'activesupport-cache-database'```
|
17
18
|
|
18
|
-
|
19
|
-
# db/migrate/20190908102030_create_activesupport_cache_entries.rb
|
20
|
-
require 'active_support/cache/database_store/migration'
|
19
|
+
This gem requires a database table `activesupport_cache_entries` to be created. To do so generate a migration that would create required table.
|
21
20
|
|
22
|
-
|
23
|
-
def up
|
24
|
-
ActiveSupport::Cache::DatabaseStore::Migration.migrate(:up)
|
25
|
-
end
|
21
|
+
```rails generate cache:database:install```
|
26
22
|
|
27
|
-
|
28
|
-
ActiveSupport::Cache::DatabaseStore::Migration.migrate(:down)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
```
|
23
|
+
Make sure to read through migration file, before running a migration. You might want to tweak it to fit your usecase.
|
32
24
|
|
33
|
-
|
25
|
+
`rails db:migrate`
|
26
|
+
|
27
|
+
## Usage
|
34
28
|
|
29
|
+
Open and use the new cache instance:
|
35
30
|
```ruby
|
36
31
|
cache = ActiveSupport::Cache::DatabaseStore.new namespace: 'my-scope'
|
37
32
|
value = cache.fetch('some-key') { 'default' }
|
38
33
|
```
|
39
34
|
|
40
|
-
To use as a Rails cache store, simply use a new instance.
|
41
|
-
that, for performance reasons, your database may not be the most suitable
|
42
|
-
general purpose cache backend.
|
35
|
+
To use as a Rails cache store, simply use a new instance.
|
43
36
|
|
44
37
|
```ruby
|
45
38
|
config.cache_store = ActiveSupport::Cache::DatabaseStore.new
|
46
39
|
```
|
40
|
+
|
41
|
+
## Variable Compression
|
42
|
+
By default this gem doesn't use any compression to store records, but there is an option to use `gzip` by providing a "compression" paramater. e.g
|
43
|
+
|
44
|
+
|
45
|
+
```
|
46
|
+
cache(project, compression: 'gzip') do
|
47
|
+
{code: 'to cache'}
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
There are some cases, when compression could be skipped - if value is numeric (e.g. increment/decrement counters) and when value is less than 1024 bytes. This is done to avoid unreasonable overhead on performance.
|
52
|
+
|
53
|
+
## Maintenance
|
54
|
+
After you have started caching into the database, you will likely see the database size growing significantly. It is crucial to implement an effective strategy to evict the cache from your DB.
|
55
|
+
|
56
|
+
There may be a large number of cache entries that do not possess an `expires_at` value, so it will be necessary to decide on an optimal timeframe for storing your cache.
|
57
|
+
|
58
|
+
This next piece of code should be run periodically:
|
59
|
+
```
|
60
|
+
ActiveSupport::Cache::DatabaseStore.new.cleanup(
|
61
|
+
created_before: 1.week.ago
|
62
|
+
)
|
63
|
+
```
|
64
|
+
Without providing a `created_before` value, only those caches with `expires_at` values will be cleaned, leaving behind plenty of dead cache.
|
65
|
+
|
66
|
+
If you're using PostgreSQL, consider running vacuum or pg_repack intermittently to delete data physically as well.
|
67
|
+
|
68
|
+
|
69
|
+
## Warning
|
70
|
+
There are two things you need to be aware about while using this gem:
|
71
|
+
- For performance reasons, your database may not be the most suitable general purpose cache backend. But in some cases, caching complex quieries in cache could be a good enough improvement.
|
72
|
+
- While already generally usable as a Rails cache store, this gem doesn't yet implement all required methods.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'activesupport_cache_database'
|
3
|
-
s.version = '0.
|
3
|
+
s.version = '0.5.0'
|
4
4
|
s.authors = ['Black Square Media Ltd']
|
5
5
|
s.email = ['info@blacksquaremedia.com']
|
6
6
|
s.summary = %(ActiveSupport::Cache::Store implementation backed by ActiveRecord.)
|
@@ -12,8 +12,8 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.require_paths = ['lib']
|
13
13
|
s.required_ruby_version = '>= 2.7'
|
14
14
|
|
15
|
-
s.add_dependency 'activerecord', '>= 6.
|
16
|
-
s.add_dependency 'activesupport', '>= 6.
|
15
|
+
s.add_dependency 'activerecord', '>= 6.1'
|
16
|
+
s.add_dependency 'activesupport', '>= 6.1'
|
17
17
|
|
18
18
|
s.metadata['rubygems_mfa_required'] = 'true'
|
19
19
|
end
|
@@ -12,11 +12,16 @@ module ActiveSupport
|
|
12
12
|
|
13
13
|
scope :fresh, -> { where(arel_table[:expires_at].gt(Time.zone.now)) }
|
14
14
|
scope :expired, -> { where(arel_table[:expires_at].lteq(Time.zone.now)) }
|
15
|
+
scope :created_before, ->(date = 1.month.ago) { where(arel_table[:created_at].lt(date)) }
|
15
16
|
|
16
17
|
def self.namespaced(namespace)
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
case ActiveRecord::Base.connection.adapter_name
|
19
|
+
when 'PostgreSQL'
|
20
|
+
ifx = Arel::Nodes::InfixOperation.new('IN', Arel::Nodes.build_quoted(namespace), arel_table[:key])
|
21
|
+
where(Arel::Nodes::NamedFunction.new('POSITION', [ifx]).eq(1))
|
22
|
+
else
|
23
|
+
where(arel_table[:key].matches("#{namespace}:%"))
|
24
|
+
end
|
20
25
|
end
|
21
26
|
end
|
22
27
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'active_support/cache'
|
2
2
|
require 'active_record'
|
3
|
+
require 'active_support/gzip'
|
3
4
|
|
4
5
|
module ActiveSupport
|
5
6
|
module Cache
|
@@ -11,7 +12,8 @@ module ActiveSupport
|
|
11
12
|
prepend Strategy::LocalCache
|
12
13
|
|
13
14
|
autoload :Model, 'active_support/cache/database_store/model'
|
14
|
-
|
15
|
+
|
16
|
+
COMPRESSION_HANDLERS = { 'gzip' => ActiveSupport::Gzip }.freeze
|
15
17
|
|
16
18
|
# Advertise cache versioning support.
|
17
19
|
def self.supports_cache_versioning?
|
@@ -19,23 +21,43 @@ module ActiveSupport
|
|
19
21
|
end
|
20
22
|
|
21
23
|
# param [Hash] options options
|
22
|
-
# option options [Class] :model
|
24
|
+
# option options [Class] :model (default: ActiveSupport::Cache::DatabaseStore::Model) model class
|
25
|
+
# option options [String] :compression (default: nil) Use "gzip" value to compress cache
|
23
26
|
def initialize(options = nil)
|
24
27
|
@model = (options || {}).delete(:model) || Model
|
28
|
+
@compression = (options || {}).delete(:compression)&.to_s
|
29
|
+
|
30
|
+
raise ArgumentError, "invalid compression option #{@compression.inspect}" if @compression && !COMPRESSION_HANDLERS.key?(@compression)
|
31
|
+
|
25
32
|
super(options)
|
26
33
|
end
|
27
34
|
|
28
35
|
# Preemptively iterates through all stored keys and removes the ones which have expired.
|
36
|
+
|
37
|
+
# params [Hash] options
|
38
|
+
# option options [String] :namespace
|
39
|
+
# option options [ActiveSupport::Duration] :created_before - provide a time duration after record without expire_at date will get removed.
|
29
40
|
def cleanup(options = nil)
|
30
41
|
options = merged_options(options)
|
31
42
|
scope = @model.expired
|
43
|
+
|
44
|
+
if (created_before = options[:created_before])
|
45
|
+
scope = scope.or(@model.created_before(created_before))
|
46
|
+
end
|
47
|
+
|
32
48
|
if (namespace = options[:namespace])
|
33
49
|
scope = scope.namespaced(namespace)
|
34
50
|
end
|
51
|
+
|
35
52
|
scope.delete_all
|
36
53
|
end
|
37
54
|
|
38
55
|
# Clears the entire cache. Be careful with this method.
|
56
|
+
#
|
57
|
+
# params [Hash] options
|
58
|
+
# option options [String] :namespace
|
59
|
+
#
|
60
|
+
# @return [Boolean]
|
39
61
|
def clear(options = nil)
|
40
62
|
options = merged_options(options)
|
41
63
|
if (namespace = options[:namespace])
|
@@ -57,13 +79,34 @@ module ActiveSupport
|
|
57
79
|
scope.count
|
58
80
|
end
|
59
81
|
|
82
|
+
# Increments an integer value in the cache.
|
83
|
+
def increment(name, amount = 1, options = nil)
|
84
|
+
options = merged_options(options)
|
85
|
+
scope = @model.all
|
86
|
+
if (namespace = options[:namespace])
|
87
|
+
scope = scope.namespaced(namespace)
|
88
|
+
end
|
89
|
+
|
90
|
+
entry = Entry.new(amount, **options.merge(version: normalize_version(name, options)))
|
91
|
+
|
92
|
+
attrs = { key: normalize_key(name, options), **entry_attributes(entry) }
|
93
|
+
scope.upsert(attrs, on_duplicate: Arel.sql(sanitize_sql_array(['value = EXCLUDED.value + ?', amount])))
|
94
|
+
end
|
95
|
+
|
96
|
+
# Decrements an integer value in the cache.
|
97
|
+
def decrement(name, amount = 1, options = nil)
|
98
|
+
increment(name, -amount, options)
|
99
|
+
end
|
100
|
+
|
60
101
|
private
|
61
102
|
|
62
103
|
def normalize_key(name, options = nil)
|
63
104
|
key = super.to_s
|
64
105
|
raise ArgumentError, 'Namespaced key exceeds the length limit' if key && key.bytesize > 255
|
65
106
|
|
66
|
-
key
|
107
|
+
# `key` is actually a BLOB column, with some DBs (such as SQLite) we need to explicitly
|
108
|
+
# tag the string as binary so that Arel can properly escape it for a SELECT query
|
109
|
+
key.b
|
67
110
|
end
|
68
111
|
|
69
112
|
def read_entry(key, _options = nil)
|
@@ -72,8 +115,7 @@ module ActiveSupport
|
|
72
115
|
|
73
116
|
def write_entry(key, entry, _options = nil)
|
74
117
|
record = @model.where(key: key).first_or_initialize
|
75
|
-
|
76
|
-
record.update! value: Marshal.dump(entry.value), version: entry.version.presence, expires_at: expires_at
|
118
|
+
record.update!(**entry_attributes(entry))
|
77
119
|
rescue ActiveRecord::RecordNotUnique
|
78
120
|
# If two servers initialize a new record with the same cache key and try to save it,
|
79
121
|
# the saves will race. We do not need to ensure a specific save wins, but we do need to ensure
|
@@ -87,6 +129,19 @@ module ActiveSupport
|
|
87
129
|
@model.where(key: key).destroy_all
|
88
130
|
end
|
89
131
|
|
132
|
+
def write_multi_entries(hash, **_options)
|
133
|
+
entries = hash.map {|key, entry| { key: key, created_at: Time.zone.now, **entry_attributes(entry) } }
|
134
|
+
|
135
|
+
# In rails 7, we can use update_only not to do anything. But for the sakes of compatibility, we don't use any additional parameters.
|
136
|
+
@model.upsert_all(entries)
|
137
|
+
end
|
138
|
+
|
139
|
+
def entry_attributes(entry)
|
140
|
+
expires_at = Time.zone.at(entry.expires_at) if entry.expires_at
|
141
|
+
|
142
|
+
compression_attributes(entry.value).update(version: entry.version.presence, expires_at: expires_at)
|
143
|
+
end
|
144
|
+
|
90
145
|
def read_multi_entries(names, options)
|
91
146
|
keyed = {}
|
92
147
|
names.each do |name|
|
@@ -111,10 +166,28 @@ module ActiveSupport
|
|
111
166
|
results
|
112
167
|
end
|
113
168
|
|
169
|
+
def compression_attributes(value)
|
170
|
+
binary = Marshal.dump(value)
|
171
|
+
|
172
|
+
if @compression && binary.bytesize >= 1024
|
173
|
+
handler = COMPRESSION_HANDLERS[@compression]
|
174
|
+
{ compression: @compression, value: handler.compress(binary) }
|
175
|
+
else
|
176
|
+
{ value: binary }
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def decompress(record)
|
181
|
+
return record.value if record.compression.nil?
|
182
|
+
|
183
|
+
COMPRESSION_HANDLERS.fetch(record.compression).decompress(record.value)
|
184
|
+
end
|
185
|
+
|
114
186
|
def from_record(record)
|
115
187
|
return unless record
|
116
188
|
|
117
|
-
|
189
|
+
value = Marshal.load decompress(record)
|
190
|
+
entry = Entry.new(value, version: record.version)
|
118
191
|
entry.expires_at = record.expires_at
|
119
192
|
entry
|
120
193
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
|
3
|
+
module Cache
|
4
|
+
module Database
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
7
|
+
include ActiveRecord::Generators::Migration
|
8
|
+
|
9
|
+
source_root File.join(__dir__, 'templates')
|
10
|
+
desc 'Add migrations for ActiveSupport::Cache::Database'
|
11
|
+
|
12
|
+
def self.next_migration_number(path)
|
13
|
+
next_migration_number = current_migration_number(path) + 1
|
14
|
+
ActiveRecord::Migration.next_migration_number(next_migration_number)
|
15
|
+
end
|
16
|
+
|
17
|
+
def copy_migrations
|
18
|
+
migration_template 'create_table_for_cache.rb', 'db/migrate/create_cache_database.rb'
|
19
|
+
migration_template 'add_cache_compression_column.rb', 'db/migrate/add_cache_compression_column.rb'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class CreateTableForCache < ActiveRecord::Migration[6.1]
|
2
|
+
def change
|
3
|
+
create_table :activesupport_cache_entries, primary_key: 'key', id: :binary, limit: 255 do |t|
|
4
|
+
t.binary :value, null: false
|
5
|
+
t.string :version
|
6
|
+
t.timestamp :created_at, null: false, index: true
|
7
|
+
t.timestamp :expires_at
|
8
|
+
end
|
9
|
+
|
10
|
+
add_index :activesupport_cache_entries, :expires_at, where: 'expires_at IS NOT NULL'
|
11
|
+
add_index :activesupport_cache_entries, :version, where: 'version IS NOT NULL'
|
12
|
+
|
13
|
+
# if your using Postgres you might want to turn cache table into unlogged tables.
|
14
|
+
# This comes with 50% write performance improvement, but comes with multiple
|
15
|
+
# downsides you need to be aware off:
|
16
|
+
# - No validation, data will be lost in case of forced restart.
|
17
|
+
# - No replication to read replicas
|
18
|
+
#
|
19
|
+
# Since we're working with cache here, these drawbacks seem accaptable.
|
20
|
+
#
|
21
|
+
# Uncomment a following line if those are acceptable for you:
|
22
|
+
# ActiveRecord::Base.connection.execute("ALTER TABLE activesupport_cache_entries SET UNLOGGED")
|
23
|
+
end
|
24
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activesupport_cache_database
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Black Square Media Ltd
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-11-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '6.
|
19
|
+
version: '6.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '6.
|
26
|
+
version: '6.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activesupport
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '6.
|
33
|
+
version: '6.1'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '6.
|
40
|
+
version: '6.1'
|
41
41
|
description: Use your DB as a cache store
|
42
42
|
email:
|
43
43
|
- info@blacksquaremedia.com
|
@@ -49,6 +49,7 @@ files:
|
|
49
49
|
- ".github/workflows/test.yml"
|
50
50
|
- ".gitignore"
|
51
51
|
- ".rubocop.yml"
|
52
|
+
- CHANGELOG.md
|
52
53
|
- Gemfile
|
53
54
|
- Gemfile.lock
|
54
55
|
- Gemfile.rails6
|
@@ -58,9 +59,11 @@ files:
|
|
58
59
|
- Rakefile
|
59
60
|
- activesupport_cache_database.gemspec
|
60
61
|
- lib/active_support/cache/database_store.rb
|
61
|
-
- lib/active_support/cache/database_store/migration.rb
|
62
62
|
- lib/active_support/cache/database_store/model.rb
|
63
63
|
- lib/activesupport_cache_database.rb
|
64
|
+
- lib/generators/cache/database/install_generator.rb
|
65
|
+
- lib/generators/cache/database/templates/add_cache_compression_column.rb
|
66
|
+
- lib/generators/cache/database/templates/create_table_for_cache.rb
|
64
67
|
homepage: https://github.com/bsm/activesupport-cache-database
|
65
68
|
licenses:
|
66
69
|
- Apache-2.0
|
@@ -81,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
84
|
- !ruby/object:Gem::Version
|
82
85
|
version: '0'
|
83
86
|
requirements: []
|
84
|
-
rubygems_version: 3.4.
|
87
|
+
rubygems_version: 3.4.19
|
85
88
|
signing_key:
|
86
89
|
specification_version: 4
|
87
90
|
summary: ActiveSupport::Cache::Store implementation backed by ActiveRecord.
|
@@ -1,18 +0,0 @@
|
|
1
|
-
require 'active_support/cache/database_store'
|
2
|
-
|
3
|
-
module ActiveSupport
|
4
|
-
module Cache
|
5
|
-
class DatabaseStore < Store
|
6
|
-
class Migration < ::ActiveRecord::Migration[5.2]
|
7
|
-
def change
|
8
|
-
create_table :activesupport_cache_entries, primary_key: 'key', id: :binary, limit: 255 do |t|
|
9
|
-
t.binary :value, null: false
|
10
|
-
t.string :version, index: true
|
11
|
-
t.timestamp :created_at, null: false, index: true
|
12
|
-
t.timestamp :expires_at, index: true
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|