aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/cache/strategy/local_cache.rb
blob: 86c7703c278ee0aa685ebc4338db48c0af7cdb95 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/string/inflections'

module ActiveSupport
  module Cache
    module Strategy
      module LocalCache
        # this allows caching of the fact that there is nothing in the remote cache
        NULL = 'remote_cache_store:null'

        def with_local_cache
          Thread.current[thread_local_key] = MemoryStore.new
          yield
        ensure
          Thread.current[thread_local_key] = nil
        end

        def middleware
          @middleware ||= begin
            klass = Class.new
            klass.class_eval(<<-EOS, __FILE__, __LINE__)
              def initialize(app)
                @app = app
              end

              def call(env)
                Thread.current[:#{thread_local_key}] = MemoryStore.new
                @app.call(env)
              ensure
                Thread.current[:#{thread_local_key}] = nil
              end
            EOS

            def klass.to_s
              "ActiveSupport::Cache::Strategy::LocalCache"
            end

            klass
          end
        end

        def read(key, options = nil)
          value = local_cache && local_cache.read(key)
          if value == NULL
            nil
          elsif value.nil?
            value = super
            local_cache.mute { local_cache.write(key, value || NULL) } if local_cache
            value.duplicable? ? value.dup : value
          else
            # forcing the value to be immutable
            value.duplicable? ? value.dup : value
          end
        end

        def write(key, value, options = nil)
          value = value.to_s if respond_to?(:raw?) && raw?(options)
          local_cache.mute { local_cache.write(key, value || NULL) } if local_cache
          super
        end

        def delete(key, options = nil)
          local_cache.mute { local_cache.write(key, NULL) } if local_cache
          super
        end

        def exist(key, options = nil)
          value = local_cache.read(key) if local_cache
          if value == NULL
            false
          elsif value
            true
          else
            super
          end
        end

        def increment(key, amount = 1)
          if value = super
            local_cache.mute { local_cache.write(key, value.to_s) } if local_cache
            value
          else
            nil
          end
        end

        def decrement(key, amount = 1)
          if value = super
            local_cache.mute { local_cache.write(key, value.to_s) } if local_cache
            value
          else
            nil
          end
        end

        def clear
          local_cache.clear if local_cache
          super
        end

        private
          def thread_local_key
            @thread_local_key ||= "#{self.class.name.underscore}_local_cache".gsub("/", "_").to_sym
          end

          def local_cache
            Thread.current[thread_local_key]
          end
      end
    end
  end
end