solid_cache_mongoid 0.1.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.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +189 -0
  4. data/Rakefile +48 -0
  5. data/app/jobs/solid_cache_mongoid/expiry_job.rb +9 -0
  6. data/app/models/solid_cache_mongoid/entry/expiration.rb +55 -0
  7. data/app/models/solid_cache_mongoid/entry/size/estimate.rb +133 -0
  8. data/app/models/solid_cache_mongoid/entry/size/moving_average_estimate.rb +62 -0
  9. data/app/models/solid_cache_mongoid/entry/size.rb +21 -0
  10. data/app/models/solid_cache_mongoid/entry.rb +145 -0
  11. data/app/models/solid_cache_mongoid/record.rb +77 -0
  12. data/lib/active_support/cache/solid_cache_mongoid_store.rb +9 -0
  13. data/lib/generators/solid_cache/install/USAGE +9 -0
  14. data/lib/generators/solid_cache/install/install_generator.rb +20 -0
  15. data/lib/generators/solid_cache/install/templates/config/cache.yml.tt +20 -0
  16. data/lib/generators/solid_cache/install/templates/db/cache_schema.rb +12 -0
  17. data/lib/generators/solid_cache/install/templates/db/cache_structure.mysql.sql +56 -0
  18. data/lib/generators/solid_cache/install/templates/db/cache_structure.postgresql.sql +128 -0
  19. data/lib/generators/solid_cache/install/templates/db/cache_structure.sqlite3.sql +6 -0
  20. data/lib/solid_cache_mongoid/configuration.rb +31 -0
  21. data/lib/solid_cache_mongoid/connections/unmanaged.rb +33 -0
  22. data/lib/solid_cache_mongoid/connections.rb +9 -0
  23. data/lib/solid_cache_mongoid/engine.rb +38 -0
  24. data/lib/solid_cache_mongoid/store/api.rb +178 -0
  25. data/lib/solid_cache_mongoid/store/connections.rb +88 -0
  26. data/lib/solid_cache_mongoid/store/entries.rb +79 -0
  27. data/lib/solid_cache_mongoid/store/execution.rb +52 -0
  28. data/lib/solid_cache_mongoid/store/expiry.rb +49 -0
  29. data/lib/solid_cache_mongoid/store/failsafe.rb +39 -0
  30. data/lib/solid_cache_mongoid/store/stats.rb +30 -0
  31. data/lib/solid_cache_mongoid/store.rb +20 -0
  32. data/lib/solid_cache_mongoid/version.rb +5 -0
  33. data/lib/solid_cache_mongoid.rb +16 -0
  34. data/lib/tasks/solid_cache_tasks.rake +25 -0
  35. metadata +201 -0
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidCacheMongoid
4
+ module Record
5
+ extend ActiveSupport::Concern
6
+ include Mongoid::Document
7
+ include Mongoid::Timestamps
8
+ include Mongoid::Locker
9
+ included do
10
+ NULL_INSTRUMENTER = ActiveSupport::Notifications::Instrumenter.new(ActiveSupport::Notifications::Fanout.new)
11
+
12
+ encrypt_with(key_id: ENV.fetch("SOLID_CACHE_KEY_ENCRYPT", nil) || Rails.application.secret_key_base) if SolidCacheMongoid.configuration.encrypt?
13
+
14
+ field :key, type: BSON::Binary, encrypt: SolidCacheMongoid.configuration.encryption_context_properties
15
+ field :value, type: BSON::Binary, encrypt: SolidCacheMongoid.configuration.encryption_context_properties
16
+ field :key_hash, type: Integer
17
+ field :byte_size, type: Integer
18
+ field :locking_name, type: String
19
+ field :locked_at, type: Time
20
+
21
+ index({ byte_size: 1 })
22
+ index({ key_hash: 1, byte_size: 1 })
23
+ index({ key_hash: 1 }, { unique: true })
24
+
25
+ store_in collection: SolidCacheMongoid.configuration.collection if SolidCacheMongoid.configuration.collection.present?
26
+ store_in client: SolidCacheMongoid.configuration.client if SolidCacheMongoid.configuration.client.present?
27
+ store_in database: SolidCacheMongoid.configuration.database if SolidCacheMongoid.configuration.database.present?
28
+ end
29
+
30
+ class_methods do
31
+ def disable_instrumentation(&block)
32
+ with_instrumenter(NULL_INSTRUMENTER, &block)
33
+ end
34
+
35
+ def with_instrumenter(instrumenter)
36
+ if ActiveSupport::Notifications.respond_to?(:instrumenter) && ActiveSupport::Notifications.respond_to?(:instrumenter=)
37
+ old = ActiveSupport::Notifications.instrumenter
38
+ ActiveSupport::Notifications.instrumenter = instrumenter
39
+ begin
40
+ yield
41
+ ensure
42
+ ActiveSupport::Notifications.instrumenter = old
43
+ end
44
+ else
45
+ # Fallback al comportamiento previo que usaba IsolatedExecutionState
46
+ old = ActiveSupport::IsolatedExecutionState[:active_record_instrumenter]
47
+ ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = instrumenter
48
+ begin
49
+ yield
50
+ ensure
51
+ ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = old
52
+ end
53
+ end
54
+ end
55
+
56
+ def without_query_cache(&block)
57
+ Mongo::QueryCache.uncached(&block)
58
+ end
59
+ alias :uncached :without_query_cache
60
+
61
+ def with_query_cache(&block)
62
+ Mongo::QueryCache.cache(&block)
63
+ end
64
+ alias :cache :with_query_cache
65
+
66
+ def lease_connection
67
+ # Obtiene el cliente Mongo actual del modelo
68
+ client = self.mongo_client
69
+
70
+ # Asegura que hay una conexión disponible
71
+ client.reconnect unless client.cluster.connected?
72
+
73
+ yield client
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "solid_cache_mongoid"
4
+
5
+ module ActiveSupport
6
+ module Cache
7
+ SolidCacheMongoidStore = SolidCacheMongoid::Store
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ Description:
2
+ Installs solid_cache as the Rails cache store
3
+
4
+ Example:
5
+ bin/rails generate solid_cache:install
6
+
7
+ This will create:
8
+ Installs the solid_cache migrations
9
+ Replaces the cache store in envionment configuration
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SolidCacheMongoid::InstallGenerator < Rails::Generators::Base
4
+ source_root File.expand_path("templates", __dir__)
5
+
6
+ def copy_files
7
+ template "config/cache.yml"
8
+ end
9
+
10
+ def configure_cache_store_adapter
11
+ gsub_file Pathname.new(destination_root).join("config/environments/production.rb"),
12
+ /(# )?config\.cache_store = (:.*)/, "config.cache_store = :solid_cache_mongoid_store"
13
+
14
+ gsub_file Pathname.new(destination_root).join("config/environments/development.rb"),
15
+ /(# )?config\.cache_store = (:.*)/, "config.cache_store = :solid_cache_mongoid_store"
16
+
17
+ gsub_file Pathname.new(destination_root).join("config/environments/test.rb"),
18
+ /(# )?config\.cache_store = (:.*)/, "config.cache_store = :solid_cache_mongoid_store"
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ default: &default
2
+ # database: <%= ENV.fetch("SOLID_CACHE_DATABASE", "solid_cache") %>
3
+ # collection: solid_cache_entries
4
+ # client: default
5
+ # encrypt: false
6
+ store_options:
7
+ # Cap age of oldest cache entry to fulfill retention policies
8
+ # max_age: <%%= 60.days.to_i %>
9
+ max_size: <%%= 256.megabytes %>
10
+ namespace: <%%= Rails.env %>
11
+
12
+ development:
13
+ <<: *default
14
+
15
+ test:
16
+ <<: *default
17
+
18
+ production:
19
+ database: <%= ENV.fetch("SOLID_CACHE_DATABASE", "solid_cache") %>
20
+ <<: *default
@@ -0,0 +1,12 @@
1
+ ActiveRecord::Schema[7.2].define(version: 1) do
2
+ create_table "solid_cache_entries", force: :cascade do |t|
3
+ t.binary "key", limit: 1024, null: false
4
+ t.binary "value", limit: 536870912, null: false
5
+ t.datetime "created_at", null: false
6
+ t.integer "key_hash", limit: 8, null: false
7
+ t.integer "byte_size", limit: 4, null: false
8
+ t.index ["byte_size"], name: "index_solid_cache_entries_on_byte_size"
9
+ t.index ["key_hash", "byte_size"], name: "index_solid_cache_entries_on_key_hash_and_byte_size"
10
+ t.index ["key_hash"], name: "index_solid_cache_entries_on_key_hash", unique: true
11
+ end
12
+ end
@@ -0,0 +1,56 @@
1
+
2
+ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
3
+ /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
4
+ /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
5
+ /*!50503 SET NAMES utf8mb4 */;
6
+ /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
7
+ /*!40103 SET TIME_ZONE='+00:00' */;
8
+ /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
9
+ /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
10
+ /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
11
+ /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
12
+ DROP TABLE IF EXISTS `ar_internal_metadata`;
13
+ /*!40101 SET @saved_cs_client = @@character_set_client */;
14
+ /*!50503 SET character_set_client = utf8mb4 */;
15
+ CREATE TABLE `ar_internal_metadata` (
16
+ `key` varchar(255) NOT NULL,
17
+ `value` varchar(255) DEFAULT NULL,
18
+ `created_at` datetime(6) NOT NULL,
19
+ `updated_at` datetime(6) NOT NULL,
20
+ PRIMARY KEY (`key`)
21
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
22
+ /*!40101 SET character_set_client = @saved_cs_client */;
23
+ DROP TABLE IF EXISTS `schema_migrations`;
24
+ /*!40101 SET @saved_cs_client = @@character_set_client */;
25
+ /*!50503 SET character_set_client = utf8mb4 */;
26
+ CREATE TABLE `schema_migrations` (
27
+ `version` varchar(255) NOT NULL,
28
+ PRIMARY KEY (`version`)
29
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
30
+ /*!40101 SET character_set_client = @saved_cs_client */;
31
+ DROP TABLE IF EXISTS `solid_cache_entries`;
32
+ /*!40101 SET @saved_cs_client = @@character_set_client */;
33
+ /*!50503 SET character_set_client = utf8mb4 */;
34
+ CREATE TABLE `solid_cache_entries` (
35
+ `id` bigint NOT NULL AUTO_INCREMENT,
36
+ `key` varbinary(1024) NOT NULL,
37
+ `value` longblob NOT NULL,
38
+ `created_at` datetime(6) NOT NULL,
39
+ `key_hash` bigint NOT NULL,
40
+ `byte_size` int NOT NULL,
41
+ PRIMARY KEY (`id`),
42
+ UNIQUE KEY `index_solid_cache_entries_on_key_hash` (`key_hash`),
43
+ KEY `index_solid_cache_entries_on_byte_size` (`byte_size`),
44
+ KEY `index_solid_cache_entries_on_key_hash_and_byte_size` (`key_hash`,`byte_size`)
45
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
46
+ /*!40101 SET character_set_client = @saved_cs_client */;
47
+ /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
48
+
49
+ /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
50
+ /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
51
+ /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
52
+ /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
53
+ /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
54
+ /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
55
+ /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
56
+
@@ -0,0 +1,128 @@
1
+ SET statement_timeout = 0;
2
+ SET lock_timeout = 0;
3
+ SET idle_in_transaction_session_timeout = 0;
4
+ SET transaction_timeout = 0;
5
+ SET client_encoding = 'UTF8';
6
+ SET standard_conforming_strings = on;
7
+ SELECT pg_catalog.set_config('search_path', '', false);
8
+ SET check_function_bodies = false;
9
+ SET xmloption = content;
10
+ SET client_min_messages = warning;
11
+ SET row_security = off;
12
+
13
+ SET default_tablespace = '';
14
+
15
+ SET default_table_access_method = heap;
16
+
17
+ --
18
+ -- Name: ar_internal_metadata; Type: TABLE; Schema: public; Owner: -
19
+ --
20
+
21
+ CREATE TABLE public.ar_internal_metadata (
22
+ key character varying NOT NULL,
23
+ value character varying,
24
+ created_at timestamp(6) without time zone NOT NULL,
25
+ updated_at timestamp(6) without time zone NOT NULL
26
+ );
27
+
28
+
29
+ --
30
+ -- Name: schema_migrations; Type: TABLE; Schema: public; Owner: -
31
+ --
32
+
33
+ CREATE TABLE public.schema_migrations (
34
+ version character varying NOT NULL
35
+ );
36
+
37
+
38
+ --
39
+ -- Name: solid_cache_entries; Type: TABLE; Schema: public; Owner: -
40
+ --
41
+
42
+ CREATE TABLE public.solid_cache_entries (
43
+ id bigint NOT NULL,
44
+ key bytea NOT NULL,
45
+ value bytea NOT NULL,
46
+ created_at timestamp(6) without time zone NOT NULL,
47
+ key_hash bigint NOT NULL,
48
+ byte_size integer NOT NULL
49
+ );
50
+
51
+
52
+ --
53
+ -- Name: solid_cache_entries_id_seq; Type: SEQUENCE; Schema: public; Owner: -
54
+ --
55
+
56
+ CREATE SEQUENCE public.solid_cache_entries_id_seq
57
+ START WITH 1
58
+ INCREMENT BY 1
59
+ NO MINVALUE
60
+ NO MAXVALUE
61
+ CACHE 1;
62
+
63
+
64
+ --
65
+ -- Name: solid_cache_entries_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
66
+ --
67
+
68
+ ALTER SEQUENCE public.solid_cache_entries_id_seq OWNED BY public.solid_cache_entries.id;
69
+
70
+
71
+ --
72
+ -- Name: solid_cache_entries id; Type: DEFAULT; Schema: public; Owner: -
73
+ --
74
+
75
+ ALTER TABLE ONLY public.solid_cache_entries ALTER COLUMN id SET DEFAULT nextval('public.solid_cache_entries_id_seq'::regclass);
76
+
77
+
78
+ --
79
+ -- Name: ar_internal_metadata ar_internal_metadata_pkey; Type: CONSTRAINT; Schema: public; Owner: -
80
+ --
81
+
82
+ ALTER TABLE ONLY public.ar_internal_metadata
83
+ ADD CONSTRAINT ar_internal_metadata_pkey PRIMARY KEY (key);
84
+
85
+
86
+ --
87
+ -- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
88
+ --
89
+
90
+ ALTER TABLE ONLY public.schema_migrations
91
+ ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
92
+
93
+
94
+ --
95
+ -- Name: solid_cache_entries solid_cache_entries_pkey; Type: CONSTRAINT; Schema: public; Owner: -
96
+ --
97
+
98
+ ALTER TABLE ONLY public.solid_cache_entries
99
+ ADD CONSTRAINT solid_cache_entries_pkey PRIMARY KEY (id);
100
+
101
+
102
+ --
103
+ -- Name: index_solid_cache_entries_on_byte_size; Type: INDEX; Schema: public; Owner: -
104
+ --
105
+
106
+ CREATE INDEX index_solid_cache_entries_on_byte_size ON public.solid_cache_entries USING btree (byte_size);
107
+
108
+
109
+ --
110
+ -- Name: index_solid_cache_entries_on_key_hash; Type: INDEX; Schema: public; Owner: -
111
+ --
112
+
113
+ CREATE UNIQUE INDEX index_solid_cache_entries_on_key_hash ON public.solid_cache_entries USING btree (key_hash);
114
+
115
+
116
+ --
117
+ -- Name: index_solid_cache_entries_on_key_hash_and_byte_size; Type: INDEX; Schema: public; Owner: -
118
+ --
119
+
120
+ CREATE INDEX index_solid_cache_entries_on_key_hash_and_byte_size ON public.solid_cache_entries USING btree (key_hash, byte_size);
121
+
122
+
123
+ --
124
+ -- PostgreSQL database dump complete
125
+ --
126
+
127
+ SET search_path TO "$user", public;
128
+
@@ -0,0 +1,6 @@
1
+ CREATE TABLE IF NOT EXISTS "schema_migrations" ("version" varchar NOT NULL PRIMARY KEY);
2
+ CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
3
+ CREATE TABLE IF NOT EXISTS "solid_cache_entries" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "key" blob(1024) NOT NULL, "value" blob(536870912) NOT NULL, "created_at" datetime(6) NOT NULL, "key_hash" integer(8) NOT NULL, "byte_size" integer(4) NOT NULL);
4
+ CREATE INDEX "index_solid_cache_entries_on_byte_size" ON "solid_cache_entries" ("byte_size");
5
+ CREATE INDEX "index_solid_cache_entries_on_key_hash_and_byte_size" ON "solid_cache_entries" ("key_hash", "byte_size");
6
+ CREATE UNIQUE INDEX "index_solid_cache_entries_on_key_hash" ON "solid_cache_entries" ("key_hash");
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidCacheMongoid
4
+ class Configuration
5
+ attr_reader :store_options, :database, :client, :collection, :executor, :size_estimate_samples, :encrypt, :encryption_context_properties
6
+
7
+ def initialize(
8
+ store_options: {}, database: nil, collection: nil, client: nil,
9
+ executor: nil, encrypt: false, encryption_context_properties: nil, size_estimate_samples: 10_000
10
+ )
11
+ @store_options = store_options
12
+ @size_estimate_samples = size_estimate_samples
13
+ @executor = executor
14
+ @encrypt = encrypt
15
+ @client = client
16
+ @collection = collection || "solid_cache_entries"
17
+ @database = database || "solid_cache_mongoid"
18
+ @encryption_context_properties = encryption_context_properties
19
+ @encryption_context_properties ||= default_encryption_context_properties if encrypt?
20
+ end
21
+
22
+ def encrypt?
23
+ encrypt.present?
24
+ end
25
+
26
+ private
27
+ def default_encryption_context_properties
28
+ { deterministic: false }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidCacheMongoid
4
+ module Connections
5
+ class Unmanaged
6
+ def with_each
7
+ return enum_for(:with_each) unless block_given?
8
+
9
+ yield
10
+ end
11
+
12
+ def with(name)
13
+ yield
14
+ end
15
+
16
+ def with_connection_for(key)
17
+ yield
18
+ end
19
+
20
+ def assign(keys)
21
+ { default: keys }
22
+ end
23
+
24
+ def count
25
+ 1
26
+ end
27
+
28
+ def names
29
+ [ :default ]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidCacheMongoid
4
+ module Connections
5
+ def self.from_config(_ = nil)
6
+ Unmanaged.new
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "mongoid"
5
+
6
+ module SolidCacheMongoid
7
+ class Engine < ::Rails::Engine
8
+ isolate_namespace SolidCacheMongoid
9
+
10
+ config.solid_cache = ActiveSupport::OrderedOptions.new
11
+
12
+ initializer "solid_cache_mongoid.config", before: :initialize_cache do |app|
13
+ config_paths = %w[config/cache config/solid_cache_mongoid]
14
+
15
+ config_paths.each do |path|
16
+ app.paths.add path, with: ENV["SOLID_CACHE_CONFIG"] || "#{path}.yml"
17
+ end
18
+
19
+ config_pathname = config_paths.map { |path| Pathname.new(app.config.paths[path].first) }.find(&:exist?)
20
+
21
+ options = config_pathname ? app.config_for(config_pathname).to_h.deep_symbolize_keys : {}
22
+
23
+ options[:size_estimate_samples] = config.solid_cache.size_estimate_samples if config.solid_cache.size_estimate_samples
24
+ options[:encrypt] = config.solid_cache.encrypt if config.solid_cache.encrypt
25
+ options[:encryption_context_properties] = config.solid_cache.encryption_context_properties if config.solid_cache.encryption_context_properties
26
+
27
+ SolidCacheMongoid.configuration = SolidCacheMongoid::Configuration.new(**options)
28
+ end
29
+
30
+ initializer "solid_cache_mongoid.app_executor", before: :run_prepare_callbacks do |app|
31
+ SolidCacheMongoid.executor = config.solid_cache.executor || app.executor
32
+ end
33
+
34
+ config.after_initialize do
35
+ Rails.cache.setup! if Rails.cache.is_a?(Store)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidCacheMongoid
4
+ class Store
5
+ module Api
6
+ DEFAULT_MAX_KEY_BYTESIZE = 1024
7
+
8
+ attr_reader :max_key_bytesize
9
+
10
+ def initialize(options = {})
11
+ super(options)
12
+
13
+ @max_key_bytesize = options.fetch(:max_key_bytesize, DEFAULT_MAX_KEY_BYTESIZE)
14
+ end
15
+
16
+ def increment(name, amount = 1, options = nil)
17
+ options = merged_options(options)
18
+ key = normalize_key(name, options)
19
+
20
+ instrument :increment, key, amount: amount do
21
+ adjust(name, amount, options)
22
+ end
23
+ end
24
+
25
+ def decrement(name, amount = 1, options = nil)
26
+ options = merged_options(options)
27
+ key = normalize_key(name, options)
28
+
29
+ instrument :decrement, key, amount: amount do
30
+ adjust(name, -amount, options)
31
+ end
32
+ end
33
+
34
+ def cleanup(options = nil)
35
+ raise NotImplementedError.new("#{self.class.name} does not support cleanup")
36
+ end
37
+
38
+ def clear(options = nil)
39
+ entry_clear
40
+ end
41
+
42
+ private
43
+ def read_entry(key, **options)
44
+ deserialize_entry(read_serialized_entry(key, **options), **options)
45
+ end
46
+
47
+ def read_serialized_entry(key, **options)
48
+ entry_read(key)
49
+ end
50
+
51
+ def write_entry(key, entry, raw: false, unless_exist: false, **options)
52
+ payload = serialize_entry(entry, raw: raw, **options)
53
+
54
+ if unless_exist
55
+ written = false
56
+ entry_lock_and_write(key) do |value|
57
+ if value.nil? || deserialize_entry(value, **options).expired?
58
+ written = true
59
+ payload
60
+ end
61
+ end
62
+ else
63
+ written = entry_write(key, payload)
64
+ end
65
+
66
+ write_serialized_entry(key, payload, raw: raw, returning: written, **options)
67
+ written
68
+ end
69
+
70
+ def write_serialized_entry(key, payload, raw: false, unless_exist: false, expires_in: nil, race_condition_ttl: nil, returning: true, **options)
71
+ returning
72
+ end
73
+
74
+ def read_serialized_entries(keys)
75
+ entry_read_multi(keys).reduce(&:merge!)
76
+ end
77
+
78
+ def read_multi_entries(names, **options)
79
+ keys_and_names = names.index_by { |name| normalize_key(name, options) }
80
+ serialized_entries = read_serialized_entries(keys_and_names.keys)
81
+
82
+ keys_and_names.each_with_object({}) do |(key, name), results|
83
+ serialized_entry = serialized_entries[key]
84
+ entry = deserialize_entry(serialized_entry, **options)
85
+
86
+ next unless entry
87
+
88
+ version = normalize_version(name, options)
89
+
90
+ if entry.expired?
91
+ delete_entry(key, **options)
92
+ elsif !entry.mismatched?(version)
93
+ if defined? ActiveSupport::Cache::DeserializationError
94
+ begin
95
+ results[name] = entry.value
96
+ rescue ActiveSupport::Cache::DeserializationError
97
+ end
98
+ else
99
+ results[name] = entry.value
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ def write_multi_entries(entries, expires_in: nil, **options)
106
+ if entries.any?
107
+ serialized_entries = serialize_entries(entries, **options)
108
+ # to add them to the local cache
109
+ serialized_entries.each do |entries|
110
+ write_serialized_entry(entries[:key], entries[:value])
111
+ end
112
+
113
+ entry_write_multi(serialized_entries).all?
114
+ end
115
+ end
116
+
117
+ def delete_entry(key, **options)
118
+ entry_delete(key)
119
+ end
120
+
121
+ def delete_multi_entries(entries, **options)
122
+ entry_delete_multi(entries).compact.sum
123
+ end
124
+
125
+ def serialize_entry(entry, raw: false, **options)
126
+ super(entry, raw: raw, **options)
127
+ end
128
+
129
+ def serialize_entries(entries, **options)
130
+ entries.map do |key, entry|
131
+ { key: key, value: serialize_entry(entry, **options) }
132
+ end
133
+ end
134
+
135
+ def deserialize_entry(payload, **)
136
+ super(payload)
137
+ end
138
+
139
+ def normalize_key(key, options)
140
+ truncate_key super&.b
141
+ end
142
+
143
+ def truncate_key(key)
144
+ if key && key.bytesize > max_key_bytesize
145
+ suffix = ":hash:#{ActiveSupport::Digest.hexdigest(key)}"
146
+ truncate_at = max_key_bytesize - suffix.bytesize
147
+ "#{key.byteslice(0, truncate_at)}#{suffix}".b
148
+ else
149
+ key
150
+ end
151
+ end
152
+
153
+ def adjust(name, amount, options)
154
+ options = merged_options(options)
155
+ key = normalize_key(name, options)
156
+
157
+ new_value = entry_lock_and_write(key) do |value|
158
+ serialize_entry(adjusted_entry(value, amount, options))
159
+ end
160
+ deserialize_entry(new_value, **options).value if new_value
161
+ end
162
+
163
+ def adjusted_entry(value, amount, options)
164
+ entry = deserialize_entry(value, **options)
165
+
166
+ if entry && !entry.expired?
167
+ ActiveSupport::Cache::Entry.new \
168
+ amount + entry.value.to_i, **options.dup.merge(expires_in: nil, expires_at: entry.expires_at)
169
+ elsif /\A\d+\z/.match?(value)
170
+ # This is to match old raw values
171
+ ActiveSupport::Cache::Entry.new(amount + value.to_i, **options)
172
+ else
173
+ ActiveSupport::Cache::Entry.new(amount, **options)
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end