File: expires.rb

package info (click to toggle)
ruby-moneta 1.6.0-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,776 kB
  • sloc: ruby: 13,201; sh: 178; makefile: 7
file content (188 lines) | stat: -rw-r--r-- 5,429 bytes parent folder | download | duplicates (2)
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
module Moneta
  # Adds expiration support to the underlying store
  #
  # `#store`, `#load` and `#key?` support the `:expires` option to set/update
  # the expiration time.
  #
  # @api public
  class Expires < Proxy
    include ExpiresSupport

    # @param [Moneta store] adapter The underlying store
    # @param [Hash] options
    # @option options [String] :expires Default expiration time
    def initialize(adapter, options = {})
      raise 'Store already supports feature :expires' if adapter.supports?(:expires)
      super
    end

    # (see Proxy#key?)
    def key?(key, options = {})
      # Transformer might raise exception
      load_entry(key, options) != nil
    rescue
      super(key, Utils.without(options, :expires))
    end

    # (see Proxy#load)
    def load(key, options = {})
      return super if options.include?(:raw)
      value, = load_entry(key, options)
      value
    end

    # (see Proxy#store)
    def store(key, value, options = {})
      return super if options.include?(:raw)
      expires = expires_at(options)
      super(key, new_entry(value, expires), Utils.without(options, :expires))
      value
    end

    # (see Proxy#delete)
    def delete(key, options = {})
      return super if options.include?(:raw)
      value, expires = super
      value if !expires || Time.now <= Time.at(expires)
    end

    # (see Proxy#store)
    def create(key, value, options = {})
      return super if options.include?(:raw)
      expires = expires_at(options)
      @adapter.create(key, new_entry(value, expires), Utils.without(options, :expires))
    end

    # (see Defaults#values_at)
    def values_at(*keys, **options)
      return super if options.include?(:raw)
      new_expires = expires_at(options, nil)
      options = Utils.without(options, :expires)
      with_updates(options) do |updates|
        keys.zip(@adapter.values_at(*keys, **options)).map do |key, entry|
          entry = invalidate_entry(key, entry, new_expires) do |new_entry|
            updates[key] = new_entry
          end
          next if entry == nil
          value, = entry
          value
        end
      end
    end

    # (see Defaults#fetch_values)
    def fetch_values(*keys, **options)
      return super if options.include?(:raw)
      new_expires = expires_at(options, nil)
      options = Utils.without(options, :expires)
      substituted = {}
      block = if block_given?
                lambda do |key|
                  substituted[key] = true
                  yield key
                end
              end

      with_updates(options) do |updates|
        keys.zip(@adapter.fetch_values(*keys, **options, &block)).map do |key, entry|
          next entry if substituted[key]
          entry = invalidate_entry(key, entry, new_expires) do |new_entry|
            updates[key] = new_entry
          end
          if entry == nil
            value = if block_given?
                      yield key
                    end
          else
            value, = entry
          end
          value
        end
      end
    end

    # (see Defaults#slice)
    def slice(*keys, **options)
      return super if options.include?(:raw)
      new_expires = expires_at(options, nil)
      options = Utils.without(options, :expires)

      with_updates(options) do |updates|
        @adapter.slice(*keys, **options).map do |key, entry|
          entry = invalidate_entry(key, entry, new_expires) do |new_entry|
            updates[key] = new_entry
          end
          next if entry == nil
          value, = entry
          [key, value]
        end.reject(&:nil?)
      end
    end

    # (see Defaults#merge!)
    def merge!(pairs, options = {})
      expires = expires_at(options)
      options = Utils.without(options, :expires)

      block = if block_given?
                lambda do |key, old_entry, entry|
                  old_entry = invalidate_entry(key, old_entry)
                  if old_entry == nil
                    entry # behave as if no replace is happening
                  else
                    old_value, = old_entry
                    new_value, = entry
                    new_entry(yield(key, old_value, new_value), expires)
                  end
                end
              end

      entry_pairs = pairs.map do |key, value|
        [key, new_entry(value, expires)]
      end
      @adapter.merge!(entry_pairs, options, &block)
      self
    end

    private

    def load_entry(key, options)
      new_expires = expires_at(options, nil)
      options = Utils.without(options, :expires)
      entry = @adapter.load(key, options)
      invalidate_entry(key, entry, new_expires) do |new_entry|
        @adapter.store(key, new_entry, options)
      end
    end

    def invalidate_entry(key, entry, new_expires = nil)
      if entry != nil
        value, expires = entry
        if expires && Time.now > Time.at(expires)
          delete(key)
          entry = nil
        elsif new_expires != nil
          yield new_entry(value, new_expires) if block_given?
        end
      end
      entry
    end

    def new_entry(value, expires)
      if expires
        [value, expires.to_r]
      elsif Array === value || value == nil
        [value]
      else
        value
      end
    end

    def with_updates(options)
      updates = {}
      yield(updates).tap do
        @adapter.merge!(updates, options) unless updates.empty?
      end
    end
  end
end