kasket 4.13.0 → 4.14.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c1c83230c5c06c292c4dd62d839372d06a54a763ed7dfd0ea5ed339fb57913a
4
- data.tar.gz: 96df87b7c7ee9f8f19e593b3bc3bcbad257d928e0cdc4e0b3f5b22afed00b9c6
3
+ metadata.gz: a6e7fba4b86593d0f6df819b8e4af5417db71843ca84f6aa713688fad70af03d
4
+ data.tar.gz: d0b55338667baf18f1f85b74eb6063e1c2c216fb66bcc6f66c8f0c72a8233b58
5
5
  SHA512:
6
- metadata.gz: 1ced9df5e050870ed7446bcf1b2e469fa2954ce1e7afcd67b799b14bb25a7323690242ca5ebb097c8d7df7f5c0c70317a72f292c202bebcc189b7afb64e905d1
7
- data.tar.gz: aef1d32b14c4d6c3526b2b9093fec72f2c1998c04bebcf40861a30a1226234e4a70976168e605358fb452ed9152e49a7d69564c2fd7583541056c66cbe7fc8f0
6
+ metadata.gz: 258485dd05909a3741841bac6ef9c1fbf41ac37c8978f9efa2284cf0caa3a05dbc38ba76c7f74b8ee033ff6106ddbbcd9a0b86943323e08e4e2dd8d95ee86a99
7
+ data.tar.gz: bf59e003102eef756e49977f99441e4f0930c0032bce7dc183f47e27b2e0887f5d682bdb1b34cd5e3892136e9ebf21d18dbe9ad5c685d3daaf8271b2d5c4c2a1
data/README.md CHANGED
@@ -48,6 +48,19 @@ semantics instead. In this mode, the model will be updated in the cache as well
48
48
  Kasket.setup(write_through: true)
49
49
  ```
50
50
 
51
+ #### Events Callback
52
+
53
+ You can configure a callable object to listen to events. This can be useful to emit metrics and observe Kasket's behaviour.
54
+
55
+ ```ruby
56
+ Kasket.setup(events_callback: -> (event, ar_klass) do
57
+ MyMetrics.increase_some_counter("kasket.#{event}", tags: ["table:#{ar_klass.table_name}"])
58
+ end)
59
+ ```
60
+
61
+ The following events are emitted:
62
+ * `"cache_hit"`, when Kasket has found some record's data in the cache, which can be returned.
63
+
51
64
  ## Configuring caching of your models
52
65
 
53
66
  You can configure Kasket for any ActiveRecord model, and subclasses will automatically inherit the caching
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kasket
4
+ # Interface to the internal instrumentation event.
5
+ module Events
6
+ class << self
7
+ # Invokes the configured events callback, if provided.
8
+ #
9
+ # The callback behaves like a listener, and receives the same arguments
10
+ # that are passed to this `report` method.
11
+ #
12
+ # @param [String] event the type of event being instrumented.
13
+ # @param [class] ar_klass the ActiveRecord::Base subclass that the event
14
+ # refers to.
15
+ #
16
+ # @return [nil]
17
+ #
18
+ def report(event, ar_klass)
19
+ return unless fn
20
+
21
+ fn.call(event, ar_klass)
22
+ nil
23
+ end
24
+
25
+ private
26
+
27
+ def fn
28
+ return @fn if defined?(@fn)
29
+
30
+ @fn = Kasket::CONFIGURATION[:events_callback]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -36,8 +36,15 @@ module Kasket
36
36
  result_set = if value.is_a?(TrueClass)
37
37
  find_by_sql_without_kasket(sql, binds, *restargs, **kwargs, &blk)
38
38
  elsif value.is_a?(Array)
39
+ # The data from the Kasket cache is a list of keys to other Kasket entries.
40
+ # This usually happens when we're trying to load a collection association,
41
+ # e.g. a list of comments using their post_id in the query.
42
+ # Do not report a cache hit yet, and defer it until we've verified that at
43
+ # least one of the retrieved keys is actually in the cache.
39
44
  filter_pending_records(find_by_sql_with_kasket_on_id_array(value))
40
45
  else
46
+ # Direct cache hit for the key.
47
+ Events.report("cache_hit", self)
41
48
  filter_pending_records(Array.wrap(value).collect { |record| instantiate(record.dup, &blk) })
42
49
  end
43
50
 
@@ -60,6 +67,9 @@ module Kasket
60
67
  key_attributes_map = Kasket.cache.read_multi(*keys)
61
68
 
62
69
  found_keys, missing_keys = keys.partition {|k| key_attributes_map[k] }
70
+ # Only report a cache hit if at least some keys were found in the cache.
71
+ Events.report("cache_hit", self) if found_keys.any?
72
+
63
73
  found_keys.each {|k| key_attributes_map[k] = instantiate(key_attributes_map[k].dup, &blk) }
64
74
  key_attributes_map.merge!(missing_records_from_db(missing_keys))
65
75
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Kasket
3
- VERSION = '4.13.0'
3
+ VERSION = '4.14.0'
4
4
  class Version
5
5
  MAJOR = Kasket::VERSION.split('.')[0]
6
6
  MINOR = Kasket::VERSION.split('.')[1]
data/lib/kasket.rb CHANGED
@@ -14,21 +14,35 @@ module Kasket
14
14
  autoload :Visitor, 'kasket/visitor'
15
15
  autoload :SelectManagerMixin, 'kasket/select_manager_mixin'
16
16
  autoload :RelationMixin, 'kasket/relation_mixin'
17
+ autoload :Events, 'kasket/events'
17
18
 
18
19
  CONFIGURATION = { # rubocop:disable Style/MutableConstant
19
20
  max_collection_size: 100,
20
21
  write_through: false,
21
- default_expires_in: nil
22
+ default_expires_in: nil,
23
+ events_callback: nil,
22
24
  }
23
25
 
24
26
  module_function
25
27
 
28
+ # Configure Kasket.
29
+ #
30
+ # @param [Hash] options the configuration options for Kasket.
31
+ # @option options [Integer] :max_collection_size max size limit for a cacheable
32
+ # collection of records.
33
+ # @option options [Boolean] :write_through
34
+ # @option options [Integer, nil] :default_expires_in the cache TTL.
35
+ # @option options [#call] :events_callback a callable object used to instrument
36
+ # Kasket operations. It is invoked with two arguments: the name of the event,
37
+ # as a String, and the Klass of the ActiveRecord model the event is about.
38
+ #
26
39
  def setup(options = {})
27
40
  return if ActiveRecord::Base.respond_to?(:has_kasket)
28
41
 
29
42
  CONFIGURATION[:max_collection_size] = options[:max_collection_size] if options[:max_collection_size]
30
43
  CONFIGURATION[:write_through] = options[:write_through] if options[:write_through]
31
44
  CONFIGURATION[:default_expires_in] = options[:default_expires_in] if options[:default_expires_in]
45
+ CONFIGURATION[:events_callback] = options[:events_callback] if options[:events_callback]
32
46
 
33
47
  ActiveRecord::Base.extend(Kasket::ConfigurationMixin)
34
48
  ActiveRecord::Relation.include(Kasket::RelationMixin)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kasket
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.13.0
4
+ version: 4.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mick Staugaard
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-04-21 00:00:00.000000000 Z
12
+ date: 2024-01-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -42,6 +42,7 @@ files:
42
42
  - lib/kasket.rb
43
43
  - lib/kasket/configuration_mixin.rb
44
44
  - lib/kasket/dirty_mixin.rb
45
+ - lib/kasket/events.rb
45
46
  - lib/kasket/query_parser.rb
46
47
  - lib/kasket/read_mixin.rb
47
48
  - lib/kasket/relation_mixin.rb
@@ -68,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
69
  - !ruby/object:Gem::Version
69
70
  version: '0'
70
71
  requirements: []
71
- rubygems_version: 3.0.3.1
72
+ rubygems_version: 3.5.3
72
73
  signing_key:
73
74
  specification_version: 4
74
75
  summary: A write back caching layer on active record