activemodel-caching 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3e5807404927b4493c98793dc6c099499ab0c4debe20508b48f9f901ccb0c6b7
4
+ data.tar.gz: aba1ed74fe1f01fa24fe90d6783422ca13a3f4b1b94d20efc9374a187afed52b
5
+ SHA512:
6
+ metadata.gz: f741d748d3f3580af8524554da1f1146d7762f406bba7279332c36fd097cb5d4f40213a54ab8c097e5c281187d54272600f1eab5d45d855a864177be488083d0
7
+ data.tar.gz: 5ebfb56ecb0debd6b2afa33ced087e7eefbd36bef5ba39705c32ad8eceff9fcc29fdeefa2c22af0eb0039351b55aac727a402dccf6a9fd565bb2bf845b81bb4c
data/.rubocop.yml ADDED
@@ -0,0 +1,29 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+
4
+ Metrics/BlockLength:
5
+ Enabled: false
6
+
7
+ Metrics/AbcSize:
8
+ Enabled: false
9
+
10
+ Metrics/ClassLength:
11
+ Enabled: false
12
+
13
+ Metrics/LineLength:
14
+ Enabled: false
15
+
16
+ Metrics/MethodLength:
17
+ Enabled: false
18
+
19
+ Metrics/ModuleLength:
20
+ Enabled: false
21
+
22
+ Style/ClassVars:
23
+ Enabled: false
24
+
25
+ Style/StringLiterals:
26
+ EnforcedStyle: double_quotes
27
+
28
+ Style/StringLiteralsInInterpolation:
29
+ EnforcedStyle: double_quotes
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-11-13
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Emmanuel Cousin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,162 @@
1
+ # ActiveModel::Caching
2
+
3
+ A library providing easy-to-use object-level caching methods for various data types in a Ruby on Rails application, allowing you to cache different attribute types directly on your models.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'activemodel-caching'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```bash
16
+ bundle install
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```bash
22
+ gem install activemodel-caching
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ To use the caching methods provided by `ActiveModel::Caching`, include the module in your model and set up the cache store.
28
+
29
+ ### Setup
30
+
31
+ You can set up a cache store globally in an initializer, for example, in `config/initializers/active_model_caching.rb`:
32
+
33
+ ```ruby
34
+ ActiveModel::Caching.setup do |config|
35
+ config.cache_store = ActiveSupport::Cache::MemoryStore.new # or any other cache store you prefer
36
+ end
37
+ ```
38
+
39
+ If you're using Rails, you can also default to Rails cache if you prefer:
40
+ ```ruby
41
+ ActiveModel::Caching.setup do |config|
42
+ config.cache_store = Rails.cache
43
+ end
44
+ ```
45
+
46
+ ### Basic Usage
47
+
48
+ To enable caching for an attribute, simply call one of the `cache_*` methods in your model class. Here are the methods you can use:
49
+
50
+ - `cache_string` - Caches a string value.
51
+ - `cache_integer` - Caches an integer value.
52
+ - `cache_decimal` - Caches a decimal value.
53
+ - `cache_datetime` - Caches a datetime value.
54
+ - `cache_flag` - Caches a boolean flag.
55
+ - `cache_float` - Caches a float value.
56
+ - `cache_enum` - Caches an enumerated value.
57
+ - `cache_json` - Caches a JSON object.
58
+ - `cache_list` - Caches an ordered list with an optional limit.
59
+ - `cache_unique_list` - Caches a unique list with an optional limit.
60
+ - `cache_set` - Caches a unique set with an optional limit.
61
+ - `cache_ordered_set` - Caches an ordered set with an optional limit.
62
+ - `cache_slots` - Caches available "slots" (e.g., seats) with helper methods.
63
+ - `cache_slot` - Caches a single-slot availability.
64
+ - `cache_counter` - Caches a counter that can be incremented and reset.
65
+ - `cache_limiter` - Caches a limited counter, enforcing a maximum count.
66
+ - `cache_hash` - Caches a hash structure.
67
+ - `cache_boolean` - Caches a boolean value.
68
+
69
+ #### Example
70
+
71
+ Here’s how you might define a model with various cached attributes:
72
+
73
+ ```ruby
74
+ class User
75
+ include ActiveModel::Caching
76
+
77
+ cache_string :session_token
78
+ cache_integer :view_count
79
+ cache_decimal :account_balance
80
+ cache_datetime :last_login
81
+ cache_flag :is_active
82
+ cache_enum :status, %w[active inactive suspended]
83
+ cache_json :preferences
84
+ cache_list :recent_searches, limit: 10
85
+ cache_set :tags, limit: 5
86
+ cache_slots :seats, available: 100
87
+ cache_counter :login_count
88
+ cache_boolean :is_verified
89
+ end
90
+ ```
91
+
92
+ With these, you’ll automatically have generated methods for interacting with the cache.
93
+
94
+ ### Detailed Method Descriptions
95
+
96
+ - **`cache_string(attribute_name, expires_in: nil)`**: Caches a string attribute.
97
+ - Example: `cache_string :username`
98
+
99
+ - **`cache_integer(attribute_name, expires_in: nil)`**: Caches an integer attribute.
100
+ - Example: `cache_integer :view_count`
101
+
102
+ - **`cache_decimal(attribute_name, expires_in: nil)`**: Caches a decimal attribute.
103
+ - Example: `cache_decimal :account_balance`
104
+
105
+ - **`cache_datetime(attribute_name, expires_in: nil)`**: Caches a datetime attribute.
106
+ - Example: `cache_datetime :last_login`
107
+
108
+ - **`cache_flag(attribute_name, expires_in: nil)`**: Caches a boolean flag.
109
+ - Example: `cache_flag :is_active`
110
+
111
+ - **`cache_enum(attribute_name, options, expires_in: nil)`**: Caches an enumerated value.
112
+ - Example: `cache_enum :status, %w[active inactive suspended]`
113
+
114
+ - **`cache_json(attribute_name, expires_in: nil)`**: Caches a JSON object.
115
+ - Example: `cache_json :user_preferences`
116
+
117
+ - **`cache_list(attribute_name, limit: nil, expires_in: nil)`**: Caches an ordered list, with an optional limit.
118
+ - Example: `cache_list :recent_posts, limit: 5`
119
+
120
+ - **`cache_set(attribute_name, limit: nil, expires_in: nil)`**: Caches a unique set, with an optional limit.
121
+ - Example: `cache_set :tags, limit: 10`
122
+
123
+ - **`cache_slots(attribute_name, available:, expires_in: nil)`**: Caches a set number of slots with helper methods.
124
+ - Example: `cache_slots :seats, available: 100`
125
+
126
+ - **`cache_counter(attribute_name, expires_in: nil)`**: Caches a counter.
127
+ - Example: `cache_counter :login_count`
128
+
129
+ - **`cache_boolean(attribute_name, expires_in: nil)`**: Caches a boolean value.
130
+ - Example: `cache_boolean :is_verified`
131
+
132
+ ### Example Methods
133
+
134
+ For each cached attribute, methods are generated for getting and setting values. For example:
135
+
136
+ ```ruby
137
+ user = User.new
138
+
139
+ # Cache a string
140
+ user.session_token = "abc123"
141
+ puts user.session_token # => "abc123"
142
+
143
+ # Increment a counter
144
+ user.increment_login_count
145
+ puts user.login_count # => 1
146
+
147
+ # Reserve a slot
148
+ if user.available_seats?
149
+ user.reserve_seats!
150
+ end
151
+
152
+ # Reset a slot
153
+ user.reset_seats!
154
+ ```
155
+
156
+ ## Contributing
157
+
158
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/your-username/active_model-caching](https://github.com/your-username/active_model-caching).
159
+
160
+ ## License
161
+
162
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Caching
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,515 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "caching/version"
4
+
5
+ require "base64"
6
+ require "bigdecimal/util"
7
+ require "json"
8
+ require "active_support"
9
+ require "active_support/time"
10
+
11
+ module ActiveModel
12
+ # Provides with a set of methods allowing to cache data structures at the object level
13
+ module Caching
14
+ mattr_accessor :cache_store
15
+ @@cache_store = ActiveSupport::Cache::MemoryStore.new
16
+
17
+ class << self
18
+ def setup
19
+ yield self
20
+ end
21
+ end
22
+
23
+ extend ActiveSupport::Concern
24
+
25
+ class_methods do
26
+ # Caches a string value for the given attribute.
27
+ #
28
+ # @param attribute_name [Symbol] the name of the string attribute to cache.
29
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
30
+ #
31
+ # @example
32
+ # cache_string :session_token
33
+ def cache_string(attribute_name, expires_in: nil)
34
+ define_method(attribute_name) do
35
+ cache_store.read(cache_key_for(attribute_name))
36
+ end
37
+
38
+ define_method(:"#{attribute_name}=") do |value|
39
+ cache_store.write(cache_key_for(attribute_name), value, expires_in: expires_in)
40
+ end
41
+
42
+ attribute_name
43
+ end
44
+
45
+ # Caches an integer value for the given attribute.
46
+ #
47
+ # @param attribute_name [Symbol] the name of the integer attribute to cache.
48
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
49
+ #
50
+ # @example
51
+ # cache_integer :view_count
52
+ def cache_integer(attribute_name, expires_in: nil)
53
+ define_method(attribute_name) do
54
+ cache_store.read(cache_key_for(attribute_name)).to_i
55
+ end
56
+
57
+ define_method(:"#{attribute_name}=") do |value|
58
+ cache_store.write(cache_key_for(attribute_name), value.to_i, expires_in: expires_in)
59
+ end
60
+
61
+ attribute_name
62
+ end
63
+
64
+ # Caches a decimal value for the given attribute.
65
+ #
66
+ # @param attribute_name [Symbol] the name of the decimal attribute to cache.
67
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
68
+ #
69
+ # @example
70
+ # cache_decimal :account_balance
71
+ def cache_decimal(attribute_name, expires_in: nil)
72
+ define_method(attribute_name) do
73
+ cache_store.read(cache_key_for(attribute_name)).to_d
74
+ end
75
+
76
+ define_method(:"#{attribute_name}=") do |value|
77
+ cache_store.write(cache_key_for(attribute_name), value.to_d, expires_in: expires_in)
78
+ end
79
+
80
+ attribute_name
81
+ end
82
+
83
+ # Caches a datetime value for the given attribute.
84
+ #
85
+ # @param attribute_name [Symbol] the name of the datetime attribute to cache.
86
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
87
+ #
88
+ # @example
89
+ # cache_datetime :last_login
90
+ def cache_datetime(attribute_name, expires_in: nil)
91
+ define_method(attribute_name) do
92
+ cache_store.read(cache_key_for(attribute_name))&.to_time
93
+ end
94
+
95
+ define_method(:"#{attribute_name}=") do |value|
96
+ cache_store.write(cache_key_for(attribute_name), value&.to_time, expires_in: expires_in)
97
+ end
98
+
99
+ attribute_name
100
+ end
101
+
102
+ # Caches a flag (boolean) value for the given attribute.
103
+ #
104
+ # @param attribute_name [Symbol] the name of the flag attribute to cache.
105
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
106
+ #
107
+ # @example
108
+ # cache_flag :is_active
109
+ def cache_flag(attribute_name, expires_in: nil)
110
+ define_method(attribute_name) do
111
+ cache_store.read(cache_key_for(attribute_name)).present?
112
+ end
113
+
114
+ define_method(:"#{attribute_name}=") do |value|
115
+ cache_store.write(cache_key_for(attribute_name), !!value, expires_in: expires_in)
116
+ end
117
+
118
+ attribute_name
119
+ end
120
+
121
+ # Caches a float value for the given attribute.
122
+ #
123
+ # @param attribute_name [Symbol] the name of the float attribute to cache.
124
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
125
+ #
126
+ # @example
127
+ # cache_float :average_rating
128
+ def cache_float(attribute_name, expires_in: nil)
129
+ define_method(attribute_name) do
130
+ cache_store.read(cache_key_for(attribute_name)).to_f
131
+ end
132
+
133
+ define_method(:"#{attribute_name}=") do |value|
134
+ cache_store.write(cache_key_for(attribute_name), value.to_f, expires_in: expires_in)
135
+ end
136
+
137
+ attribute_name
138
+ end
139
+
140
+ # Caches an enum value for the given attribute, storing the value among defined options.
141
+ #
142
+ # @param attribute_name [Symbol] the name of the enum attribute to cache.
143
+ # @param options [Array] the list of acceptable values for the enum.
144
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
145
+ #
146
+ # @example
147
+ # cache_enum :status, %w[active inactive suspended]
148
+ def cache_enum(attribute_name, options, expires_in: nil)
149
+ define_method(attribute_name) do
150
+ value = cache_store.read(cache_key_for(attribute_name))
151
+ options.include?(value) ? value : options.first # Default to first option if invalid
152
+ end
153
+
154
+ define_method(:"#{attribute_name}=") do |value|
155
+ raise ArgumentError, "Invalid value for #{attribute_name}" unless options.include?(value)
156
+
157
+ cache_store.write(cache_key_for(attribute_name), value, expires_in: expires_in)
158
+ end
159
+
160
+ attribute_name
161
+ end
162
+
163
+ # Caches a JSON value for the given attribute.
164
+ #
165
+ # @param attribute_name [Symbol] the name of the JSON attribute to cache.
166
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
167
+ #
168
+ # @example
169
+ # cache_json :user_preferences
170
+ def cache_json(attribute_name, expires_in: nil)
171
+ define_method(attribute_name) do
172
+ JSON.parse(cache_store.read(cache_key_for(attribute_name)) || nil.to_json, symbolize_names: true)
173
+ end
174
+
175
+ define_method(:"#{attribute_name}=") do |value|
176
+ cache_store.write(cache_key_for(attribute_name), value.to_json, expires_in: expires_in)
177
+ end
178
+
179
+ attribute_name
180
+ end
181
+
182
+ # Caches a list of values for the given attribute, maintaining order and enforcing a limit.
183
+ #
184
+ # @param attribute_name [Symbol] the name of the list attribute to cache.
185
+ # @param limit [Integer, nil] optional maximum number of items in the list.
186
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
187
+ #
188
+ # @example
189
+ # cache_list :recent_posts, limit: 5
190
+ def cache_list(attribute_name, limit: nil, expires_in: nil)
191
+ define_method(attribute_name) do
192
+ cache_store.read(cache_key_for(attribute_name)) || []
193
+ end
194
+
195
+ define_method(:"add_to_#{attribute_name}") do |*values|
196
+ list = send(attribute_name)
197
+ values.each do |value|
198
+ list << value
199
+ end
200
+ list.shift if limit && list.size > limit # Remove oldest item if limit is exceeded
201
+ cache_store.write(cache_key_for(attribute_name), list, expires_in: expires_in)
202
+ end
203
+
204
+ define_method(:"remove_from_#{attribute_name}") do |*values|
205
+ list = send(attribute_name)
206
+ values.each do |value|
207
+ list.delete(value)
208
+ end
209
+ cache_store.write(cache_key_for(attribute_name), list, expires_in: expires_in)
210
+ end
211
+
212
+ attribute_name
213
+ end
214
+
215
+ # Caches a unique list of values for the given attribute, maintaining uniqueness and enforcing a limit.
216
+ #
217
+ # @param attribute_name [Symbol] the name of the unique list attribute to cache.
218
+ # @param limit [Integer, nil] optional maximum number of items in the list.
219
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
220
+ #
221
+ # @example
222
+ # cache_unique_list :favorite_articles, limit: 10
223
+ def cache_unique_list(attribute_name, limit: nil, expires_in: nil)
224
+ define_method(attribute_name) do
225
+ cache_store.read(cache_key_for(attribute_name)) || []
226
+ end
227
+
228
+ define_method(:"add_to_#{attribute_name}") do |*values|
229
+ unique_list = Set.new(send(attribute_name))
230
+ values.each do |value|
231
+ unique_list.add(value)
232
+ if limit && unique_list.size > limit
233
+ oldest_value = unique_list.to_a.shift # Remove the oldest item
234
+ unique_list.delete(oldest_value)
235
+ end
236
+ end
237
+ cache_store.write(cache_key_for(attribute_name), unique_list.to_a, expires_in: expires_in)
238
+ end
239
+
240
+ define_method(:"remove_from_#{attribute_name}") do |*values|
241
+ unique_list = send(attribute_name)
242
+ values.each do |value|
243
+ unique_list.delete(value)
244
+ end
245
+ cache_store.write(cache_key_for(attribute_name), unique_list.to_a, expires_in: expires_in)
246
+ end
247
+
248
+ attribute_name
249
+ end
250
+
251
+ # Caches a set of unique values for the given attribute, maintaining uniqueness and enforcing a limit.
252
+ #
253
+ # @param attribute_name [Symbol] the name of the set attribute to cache.
254
+ # @param limit [Integer, nil] optional maximum number of items in the set.
255
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
256
+ #
257
+ # @example
258
+ # cache_set :tags, limit: 5
259
+ def cache_set(attribute_name, limit: nil, expires_in: nil)
260
+ define_method(attribute_name) do
261
+ Set.new(cache_store.read(cache_key_for(attribute_name)) || [])
262
+ end
263
+
264
+ define_method(:"add_to_#{attribute_name}") do |*values|
265
+ set = send(attribute_name)
266
+ values.each do |value|
267
+ set.add(value)
268
+ if limit && set.size > limit
269
+ oldest_value = set.to_a.shift # Remove the oldest item
270
+ set.delete(oldest_value)
271
+ end
272
+ end
273
+ cache_store.write(cache_key_for(attribute_name), set.to_a, expires_in: expires_in)
274
+ end
275
+
276
+ define_method(:"remove_from_#{attribute_name}") do |*values|
277
+ set = send(attribute_name)
278
+ values.each do |value|
279
+ set.delete(value)
280
+ end
281
+ cache_store.write(cache_key_for(attribute_name), set.to_a, expires_in: expires_in)
282
+ end
283
+
284
+ attribute_name
285
+ end
286
+
287
+ # Caches an ordered set of values for the given attribute, maintaining order and enforcing a limit.
288
+ #
289
+ # @param attribute_name [Symbol] the name of the ordered set attribute to cache.
290
+ # @param limit [Integer, nil] optional maximum number of items in the ordered set.
291
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
292
+ #
293
+ # @example
294
+ # cache_ordered_set :recent_views, limit: 10
295
+ def cache_ordered_set(attribute_name, limit: nil, expires_in: nil)
296
+ define_method(attribute_name) do
297
+ Set.new(cache_store.read(cache_key_for(attribute_name)) || [])
298
+ end
299
+
300
+ define_method(:"add_to_#{attribute_name}") do |*values|
301
+ ordered_set = send(attribute_name)
302
+ values.each do |value|
303
+ ordered_set.delete(value)
304
+ ordered_set.add(value)
305
+ if limit && ordered_set.size > limit
306
+ oldest_value = ordered_set.to_a.shift # Remove the oldest item
307
+ ordered_set.delete(oldest_value)
308
+ end
309
+ end
310
+ cache_store.write(cache_key_for(attribute_name), ordered_set, expires_in: expires_in)
311
+ end
312
+
313
+ define_method(:"remove_from_#{attribute_name}") do |*values|
314
+ ordered_set = send(attribute_name)
315
+ values.each do |value|
316
+ ordered_set.delete(value)
317
+ end
318
+ cache_store.write(cache_key_for(attribute_name), ordered_set, expires_in: expires_in)
319
+ end
320
+
321
+ attribute_name
322
+ end
323
+
324
+ ##
325
+ # Caches a limited number of available "slots" for the given attribute.
326
+ # Slots represent a count of available resources, such as seats or reservations,
327
+ # which can be reserved and released. This method generates several helper
328
+ # methods to manage the slots, including checking availability, reserving a slot,
329
+ # releasing a slot, and resetting the slots.
330
+ #
331
+ # @param attribute_name [Symbol] the name of the slot attribute to cache.
332
+ # @param available [Integer] the maximum number of available slots.
333
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
334
+ #
335
+ # @example
336
+ # cache_slots :seats, available: 10
337
+ #
338
+ # This will generate the following methods:
339
+ # - `seats`: retrieves the current number of taken slots.
340
+ # - `available_seats?`: checks if there are any available slots left.
341
+ # - `reserve_seats!`: reserves a slot if available, incrementing the taken count.
342
+ # - `release_seats!`: releases a slot, decrementing the taken count.
343
+ # - `reset_seats!`: resets the count of taken slots to zero.
344
+ #
345
+ # @return [Symbol] the name of the attribute.
346
+ def cache_slots(attribute_name, available:, expires_in: nil)
347
+ define_method(attribute_name) do
348
+ cache_store.read(cache_key_for(attribute_name)).to_i
349
+ end
350
+
351
+ define_method(:"available_#{attribute_name}?") do
352
+ taken = send(attribute_name)
353
+ taken < available
354
+ end
355
+
356
+ define_method(:"reserve_#{attribute_name}!") do
357
+ taken = send(attribute_name)
358
+ if send(:"available_#{attribute_name}?")
359
+ cache_store.write(cache_key_for(attribute_name), taken + 1, expires_in: expires_in)
360
+ taken + 1
361
+ else
362
+ taken
363
+ end
364
+ end
365
+
366
+ define_method(:"release_#{attribute_name}!") do
367
+ taken = send(attribute_name)
368
+ if taken.positive?
369
+ cache_store.write(cache_key_for(attribute_name), taken - 1, expires_in: expires_in)
370
+ taken - 1
371
+ else
372
+ taken
373
+ end
374
+ end
375
+
376
+ define_method(:"reset_#{attribute_name}!") do
377
+ taken = send(attribute_name)
378
+ if taken.positive?
379
+ cache_store.write(cache_key_for(attribute_name), 0, expires_in: expires_in)
380
+ else
381
+ taken
382
+ end
383
+ end
384
+
385
+ attribute_name
386
+ end
387
+
388
+ ##
389
+ # Caches a single slot for the given attribute.
390
+ # A single slot represents a binary (available/taken) resource that can be reserved
391
+ # or released, functioning similarly to {#cache_slots} with a fixed availability of 1.
392
+ #
393
+ # @param attribute_name [Symbol] the name of the slot attribute to cache.
394
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
395
+ #
396
+ # @example
397
+ # cache_slot :parking_space
398
+ #
399
+ # This will generate the following methods:
400
+ # - `parking_space`: retrieves the current state (0 or 1).
401
+ # - `available_parking_space?`: checks if the slot is available.
402
+ # - `reserve_parking_space!`: reserves the slot if available.
403
+ # - `release_parking_space!`: releases the slot.
404
+ # - `reset_parking_space!`: resets the slot to zero (unreserved).
405
+ #
406
+ # @return [Symbol] the name of the attribute.
407
+ def cache_slot(attribute_name, expires_in: nil)
408
+ cache_slots(attribute_name, available: 1, expires_in: expires_in)
409
+ attribute_name
410
+ end
411
+
412
+ # Caches a counter value for the given attribute.
413
+ #
414
+ # @param attribute_name [Symbol] the name of the counter attribute to cache.
415
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
416
+ #
417
+ # @example
418
+ # cache_counter :login_count
419
+ def cache_counter(attribute_name, expires_in: nil)
420
+ define_method(attribute_name) do
421
+ cache_store.read(cache_key_for(attribute_name)).to_i
422
+ end
423
+
424
+ define_method(:"increment_#{attribute_name}") do
425
+ new_value = send(attribute_name) + 1
426
+ cache_store.write(cache_key_for(attribute_name), new_value, expires_in: expires_in)
427
+ end
428
+
429
+ define_method(:"reset_#{attribute_name}") do
430
+ cache_store.write(cache_key_for(attribute_name), 0, expires_in: expires_in)
431
+ end
432
+
433
+ attribute_name
434
+ end
435
+
436
+ # Caches a limiter value for the given attribute, enforcing a limit.
437
+ #
438
+ # @param attribute_name [Symbol] the name of the limiter attribute to cache.
439
+ # @param limit [Integer] the maximum allowed count.
440
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
441
+ #
442
+ # @example
443
+ # cache_limiter :api_requests, limit: 100
444
+ def cache_limiter(attribute_name, limit:, expires_in: nil)
445
+ define_method(attribute_name) do
446
+ cache_store.read(cache_key_for(attribute_name)).to_i
447
+ end
448
+
449
+ define_method(:"increment_#{attribute_name}") do
450
+ current_value = send(attribute_name)
451
+ new_value = current_value + 1
452
+
453
+ if new_value <= limit
454
+ cache_store.write(cache_key_for(attribute_name), new_value, expires_in: expires_in)
455
+ true # Increment successful
456
+ else
457
+ false # Increment failed due to limit
458
+ end
459
+ end
460
+
461
+ define_method(:"reset_#{attribute_name}") do
462
+ cache_store.write(cache_key_for(attribute_name), 0, expires_in: expires_in)
463
+ end
464
+
465
+ attribute_name
466
+ end
467
+
468
+ # Caches a hash for the given attribute.
469
+ #
470
+ # @param attribute_name [Symbol] the name of the hash attribute to cache.
471
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
472
+ #
473
+ # @example
474
+ # cache_hash :user_settings
475
+ def cache_hash(attribute_name, expires_in: nil)
476
+ define_method(attribute_name) do
477
+ JSON.parse(cache_store.read(cache_key_for(attribute_name)) || "{}", symbolize_names: true)
478
+ end
479
+
480
+ define_method(:"#{attribute_name}=") do |value|
481
+ cache_store.write(cache_key_for(attribute_name), value.to_json, expires_in: expires_in)
482
+ end
483
+
484
+ attribute_name
485
+ end
486
+
487
+ # Caches a boolean value for the given attribute.
488
+ #
489
+ # @param attribute_name [Symbol] the name of the boolean attribute to cache.
490
+ # @param expires_in [ActiveSupport::Duration, nil] optional expiration time for the cache entry.
491
+ #
492
+ # @example
493
+ # cache_boolean :is_verified
494
+ def cache_boolean(attribute_name, expires_in: nil)
495
+ define_method(attribute_name) do
496
+ cache_store.read(cache_key_for(attribute_name)).present?
497
+ end
498
+
499
+ define_method(:"#{attribute_name}=") do |value|
500
+ cache_store.write(cache_key_for(attribute_name), !!value, expires_in: expires_in)
501
+ end
502
+ end
503
+ end
504
+
505
+ private
506
+
507
+ # Generates a cache key for the given attribute.
508
+ #
509
+ # @param attribute_name [Symbol] the name of the attribute.
510
+ # @return [String] the generated cache key.
511
+ def cache_key_for(attribute_name)
512
+ to_global_id(attribute_name: attribute_name)
513
+ end
514
+ end
515
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/caching"
@@ -0,0 +1,6 @@
1
+ module ActiveModel
2
+ module Caching
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activemodel-caching
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Emmanuel Cousin
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-11-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '7.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: base64
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bigdecimal
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 3.1.2
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 3.1.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: json
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '2.8'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '2.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: globalid
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '1.2'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '1.2'
83
+ description: ActiveModel::Caching is a versatile gem for managing structured, temporary
84
+ data using a caching backend, typically Rails cache for Rails applications. This
85
+ gem provides an easy-to-use API for storing, retrieving, and manipulating data structures
86
+ like scalars, lists, and JSON, making it simple to handle transient data without
87
+ adding extra dependencies.
88
+ email:
89
+ - emmanuel@hey.com
90
+ executables: []
91
+ extensions: []
92
+ extra_rdoc_files: []
93
+ files:
94
+ - ".rubocop.yml"
95
+ - CHANGELOG.md
96
+ - LICENSE.txt
97
+ - README.md
98
+ - Rakefile
99
+ - lib/active_model/caching.rb
100
+ - lib/active_model/caching/version.rb
101
+ - lib/activemodel/caching.rb
102
+ - sig/activemodel/caching.rbs
103
+ homepage: https://github.com/EmCousin/activemodel-caching
104
+ licenses:
105
+ - MIT
106
+ metadata:
107
+ allowed_push_host: https://rubygems.org
108
+ homepage_uri: https://github.com/EmCousin/activemodel-caching
109
+ source_code_uri: https://github.com/EmCousin/activemodel-caching
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: 3.0.0
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubygems_version: 3.5.20
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: ActiveModel::Caching is a flexible gem for managing temporary data structures
129
+ (scalars, lists, JSON) using a caching backend, typically Rails cache but adaptable
130
+ to other solutions. It offers a simple, Rails-friendly API for efficient, transient
131
+ data handling.
132
+ test_files: []