activesupport_cache_database 0.4.0 → 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: 858311266f294e87e4a3c96d9fdaa64fe35c7038b5e6949e9f460c65c694def3
4
- data.tar.gz: 8d2a67ebeac765e4f953e6fbc595a17e8eadacad6d678c534de4d86296dff30d
3
+ metadata.gz: de874398941cbdc132a9e5053c5ac020907b06824714f440a9cd4f79ffbf63e6
4
+ data.tar.gz: 8328f4136ca8cc455dad96ea244955779960050f874d9ef55b13106dd3be571c
5
5
  SHA512:
6
- metadata.gz: cd6fadeb496e34d88d93def2efd1c2d6cd0db3617f71e129698e17a421ab7e0b365438d8cac945df1d0f4a07a03f8bee06e0f2f03e2b803f05cb12289f47f8b4
7
- data.tar.gz: 6d51e3d506c5dab9d8fbf68063d033808bbfbe2f006c851ca73dbec3e0b552196522e8fa60b4f6b161e37d17de6c078a2c1ad8555f1635d5316cf47e235351df
6
+ metadata.gz: 5e68b16bec37e6cc86f3cb41a9767b14db70a8b90a7f83247bb61a70b1e0d31a6d63dcd06c9074610648e64d2b2f13dac5c43d4d852ee6490dc5616d145231ec
7
+ data.tar.gz: 56d6d46356b8ae59baa1b5e69688cc11e810c768e7b79bdcded38762e001e1aea38a3e74732a139a74560a5263bf1156e460679023c1df00ed3a5355d8c5306b
@@ -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
- gemfiles: ["Gemfile", "Gemfile.rails6"]
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: bundle exec rake
67
+ - run: |
68
+ bundle exec rake spec
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  .rubocop-*
2
2
  pkg/
3
3
  spec/*.sqlite*
4
+ .DS_Store
data/CHANGELOG.md CHANGED
@@ -1,7 +1,17 @@
1
1
  # Changelog
2
2
 
3
- ## v0.4.0 (2023-07-18)
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)
4
10
 
5
- ### New Features
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)
6
16
 
7
- - Allow to clean up cache entries without `expire_at` [#24](https://github.com/bsm/extsort/pull/24)
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,32 +1,33 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- activesupport_cache_database (0.4.0)
5
- activerecord (>= 6.0)
6
- activesupport (>= 6.0)
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.6)
12
- activesupport (= 7.0.6)
13
- activerecord (7.0.6)
14
- activemodel (= 7.0.6)
15
- activesupport (= 7.0.6)
16
- activesupport (7.0.6)
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
25
  i18n (1.14.1)
25
26
  concurrent-ruby (~> 1.0)
26
27
  json (2.6.3)
27
28
  language_server-protocol (3.17.0.3)
28
- mini_portile2 (2.8.2)
29
- minitest (5.18.1)
29
+ mini_portile2 (2.8.4)
30
+ minitest (5.19.0)
30
31
  mysql2 (0.5.5)
31
32
  parallel (1.23.0)
32
33
  parser (3.2.2.3)
@@ -37,7 +38,7 @@ GEM
37
38
  rainbow (3.1.1)
38
39
  rake (13.0.6)
39
40
  regexp_parser (2.8.1)
40
- rexml (3.2.5)
41
+ rexml (3.2.6)
41
42
  rspec (3.12.0)
42
43
  rspec-core (~> 3.12.0)
43
44
  rspec-expectations (~> 3.12.0)
@@ -51,7 +52,8 @@ GEM
51
52
  diff-lcs (>= 1.2.0, < 2.0)
52
53
  rspec-support (~> 3.12.0)
53
54
  rspec-support (3.12.1)
54
- rubocop (1.54.2)
55
+ rubocop (1.56.2)
56
+ base64 (~> 0.1.1)
55
57
  json (~> 2.3)
56
58
  language_server-protocol (>= 3.17.0)
57
59
  parallel (~> 1.10)
@@ -59,7 +61,7 @@ GEM
59
61
  rainbow (>= 2.2.2, < 4.0)
60
62
  regexp_parser (>= 1.8, < 3.0)
61
63
  rexml (>= 3.2.5, < 4.0)
62
- rubocop-ast (>= 1.28.0, < 2.0)
64
+ rubocop-ast (>= 1.28.1, < 2.0)
63
65
  ruby-progressbar (~> 1.7)
64
66
  unicode-display_width (>= 2.4.0, < 3.0)
65
67
  rubocop-ast (1.29.0)
@@ -73,17 +75,17 @@ GEM
73
75
  rubocop (~> 1.41)
74
76
  rubocop-factory_bot (2.23.1)
75
77
  rubocop (~> 1.33)
76
- rubocop-performance (1.18.0)
78
+ rubocop-performance (1.19.0)
77
79
  rubocop (>= 1.7.0, < 2.0)
78
80
  rubocop-ast (>= 0.4.0)
79
81
  rubocop-rake (0.6.0)
80
82
  rubocop (~> 1.0)
81
- rubocop-rspec (2.22.0)
83
+ rubocop-rspec (2.23.2)
82
84
  rubocop (~> 1.33)
83
85
  rubocop-capybara (~> 2.17)
84
86
  rubocop-factory_bot (~> 2.22)
85
87
  ruby-progressbar (1.13.0)
86
- sqlite3 (1.6.3)
88
+ sqlite3 (1.6.4)
87
89
  mini_portile2 (~> 2.8.0)
88
90
  tzinfo (2.0.6)
89
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.3.1)
5
- activerecord (>= 6.0)
6
- activesupport (>= 6.0)
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.2)
12
- activesupport (= 6.1.7.2)
13
- activerecord (6.1.7.2)
14
- activemodel (= 6.1.7.2)
15
- activesupport (= 6.1.7.2)
16
- activesupport (6.1.7.2)
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
- concurrent-ruby (1.2.0)
23
+ base64 (0.1.1)
24
+ concurrent-ruby (1.2.2)
24
25
  diff-lcs (1.5.0)
25
- i18n (1.12.0)
26
+ i18n (1.14.1)
26
27
  concurrent-ruby (~> 1.0)
27
28
  json (2.6.3)
28
- minitest (5.17.0)
29
+ language_server-protocol (3.17.0.3)
30
+ minitest (5.19.0)
29
31
  mysql2 (0.5.5)
30
- parallel (1.22.1)
31
- parser (3.2.0.0)
32
+ parallel (1.23.0)
33
+ parser (3.2.2.3)
32
34
  ast (~> 2.4.1)
33
- pg (1.4.5)
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.6.2)
37
- rexml (3.2.5)
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.0)
46
+ rspec-core (3.12.2)
43
47
  rspec-support (~> 3.12.0)
44
- rspec-expectations (3.12.2)
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.3)
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.0)
51
- rubocop (1.44.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.0.0)
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.24.1, < 2.0)
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.24.1)
62
- parser (>= 3.1.1.0)
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.17.0)
74
+ rubocop-capybara (2.18.0)
69
75
  rubocop (~> 1.41)
70
- rubocop-performance (1.15.2)
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.18.1)
83
+ rubocop-rspec (2.23.2)
76
84
  rubocop (~> 1.33)
77
85
  rubocop-capybara (~> 2.17)
78
- ruby-progressbar (1.11.0)
79
- sqlite3 (1.6.0-x86_64-linux)
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.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.3.16
111
+ 2.4.9
data/README.md CHANGED
@@ -11,36 +11,62 @@ Tested with:
11
11
  - SQlite3
12
12
  - MySQL/MariaDB
13
13
 
14
- ## Usage
14
+ ## Install
15
+ Add gem to Gemfile and bundle install.
15
16
 
16
- Include the data migration:
17
+ ```gem 'activesupport-cache-database'```
17
18
 
18
- ```ruby
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
- class CreateActivesupportCacheEntries < ActiveRecord::Migration[5.2]
23
- def up
24
- ActiveSupport::Cache::DatabaseStore::Migration.migrate(:up)
25
- end
21
+ ```rails generate cache:database:install```
26
22
 
27
- def down
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
- Open and use the new cache instance:
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. Please keep in mind
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.4.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.0'
16
- s.add_dependency 'activesupport', '>= 6.0'
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
@@ -15,7 +15,13 @@ module ActiveSupport
15
15
  scope :created_before, ->(date = 1.month.ago) { where(arel_table[:created_at].lt(date)) }
16
16
 
17
17
  def self.namespaced(namespace)
18
- where(arel_table[:key].matches("#{namespace}:%"))
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
19
25
  end
20
26
  end
21
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
- autoload :Migration, 'active_support/cache/database_store/migration'
15
+
16
+ COMPRESSION_HANDLERS = { 'gzip' => ActiveSupport::Gzip }.freeze
15
17
 
16
18
  # Advertise cache versioning support.
17
19
  def self.supports_cache_versioning?
@@ -19,9 +21,14 @@ module ActiveSupport
19
21
  end
20
22
 
21
23
  # param [Hash] options options
22
- # option options [Class] :model model class. Default: ActiveSupport::Cache::DatabaseStore::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
 
@@ -72,13 +79,34 @@ module ActiveSupport
72
79
  scope.count
73
80
  end
74
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
+
75
101
  private
76
102
 
77
103
  def normalize_key(name, options = nil)
78
104
  key = super.to_s
79
105
  raise ArgumentError, 'Namespaced key exceeds the length limit' if key && key.bytesize > 255
80
106
 
81
- 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
82
110
  end
83
111
 
84
112
  def read_entry(key, _options = nil)
@@ -87,8 +115,7 @@ module ActiveSupport
87
115
 
88
116
  def write_entry(key, entry, _options = nil)
89
117
  record = @model.where(key: key).first_or_initialize
90
- expires_at = Time.zone.at(entry.expires_at) if entry.expires_at
91
- record.update! value: Marshal.dump(entry.value), version: entry.version.presence, expires_at: expires_at
118
+ record.update!(**entry_attributes(entry))
92
119
  rescue ActiveRecord::RecordNotUnique
93
120
  # If two servers initialize a new record with the same cache key and try to save it,
94
121
  # the saves will race. We do not need to ensure a specific save wins, but we do need to ensure
@@ -102,6 +129,19 @@ module ActiveSupport
102
129
  @model.where(key: key).destroy_all
103
130
  end
104
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
+
105
145
  def read_multi_entries(names, options)
106
146
  keyed = {}
107
147
  names.each do |name|
@@ -126,10 +166,28 @@ module ActiveSupport
126
166
  results
127
167
  end
128
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
+
129
186
  def from_record(record)
130
187
  return unless record
131
188
 
132
- entry = Entry.new Marshal.load(record.value), version: record.version
189
+ value = Marshal.load decompress(record)
190
+ entry = Entry.new(value, version: record.version)
133
191
  entry.expires_at = record.expires_at
134
192
  entry
135
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,5 @@
1
+ class AddCacheCompressionColumn < ActiveRecord::Migration[5.2]
2
+ def change
3
+ add_column :activesupport_cache_entries, :compression, :string, null: true
4
+ end
5
+ 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.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-07-18 00:00:00.000000000 Z
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.0'
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.0'
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.0'
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.0'
40
+ version: '6.1'
41
41
  description: Use your DB as a cache store
42
42
  email:
43
43
  - info@blacksquaremedia.com
@@ -59,9 +59,11 @@ files:
59
59
  - Rakefile
60
60
  - activesupport_cache_database.gemspec
61
61
  - lib/active_support/cache/database_store.rb
62
- - lib/active_support/cache/database_store/migration.rb
63
62
  - lib/active_support/cache/database_store/model.rb
64
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
65
67
  homepage: https://github.com/bsm/activesupport-cache-database
66
68
  licenses:
67
69
  - Apache-2.0
@@ -82,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
82
84
  - !ruby/object:Gem::Version
83
85
  version: '0'
84
86
  requirements: []
85
- rubygems_version: 3.4.9
87
+ rubygems_version: 3.4.19
86
88
  signing_key:
87
89
  specification_version: 4
88
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