activemodel-caching 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []