moneta 1.4.1 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +425 -0
  3. data/CHANGES +17 -0
  4. data/CONTRIBUTORS +2 -0
  5. data/Gemfile +161 -60
  6. data/README.md +21 -17
  7. data/lib/moneta/adapter.rb +52 -0
  8. data/lib/moneta/adapters/activerecord.rb +77 -68
  9. data/lib/moneta/adapters/activesupportcache.rb +22 -31
  10. data/lib/moneta/adapters/cassandra.rb +114 -116
  11. data/lib/moneta/adapters/client.rb +17 -18
  12. data/lib/moneta/adapters/couch.rb +31 -26
  13. data/lib/moneta/adapters/datamapper.rb +9 -5
  14. data/lib/moneta/adapters/daybreak.rb +15 -21
  15. data/lib/moneta/adapters/dbm.rb +6 -12
  16. data/lib/moneta/adapters/file.rb +21 -13
  17. data/lib/moneta/adapters/fog.rb +5 -6
  18. data/lib/moneta/adapters/gdbm.rb +6 -12
  19. data/lib/moneta/adapters/hbase.rb +10 -12
  20. data/lib/moneta/adapters/kyotocabinet.rb +22 -27
  21. data/lib/moneta/adapters/leveldb.rb +14 -20
  22. data/lib/moneta/adapters/lmdb.rb +19 -22
  23. data/lib/moneta/adapters/localmemcache.rb +7 -13
  24. data/lib/moneta/adapters/lruhash.rb +20 -20
  25. data/lib/moneta/adapters/memcached/dalli.rb +25 -33
  26. data/lib/moneta/adapters/memcached/native.rb +14 -20
  27. data/lib/moneta/adapters/memory.rb +5 -7
  28. data/lib/moneta/adapters/mongo.rb +53 -52
  29. data/lib/moneta/adapters/pstore.rb +21 -27
  30. data/lib/moneta/adapters/redis.rb +42 -37
  31. data/lib/moneta/adapters/restclient.rb +17 -25
  32. data/lib/moneta/adapters/riak.rb +8 -9
  33. data/lib/moneta/adapters/sdbm.rb +6 -12
  34. data/lib/moneta/adapters/sequel/mysql.rb +8 -8
  35. data/lib/moneta/adapters/sequel/postgres.rb +17 -17
  36. data/lib/moneta/adapters/sequel/postgres_hstore.rb +47 -47
  37. data/lib/moneta/adapters/sequel/sqlite.rb +9 -9
  38. data/lib/moneta/adapters/sequel.rb +56 -65
  39. data/lib/moneta/adapters/sqlite.rb +37 -35
  40. data/lib/moneta/adapters/tdb.rb +8 -14
  41. data/lib/moneta/adapters/tokyocabinet.rb +19 -17
  42. data/lib/moneta/adapters/tokyotyrant.rb +29 -30
  43. data/lib/moneta/adapters/yaml.rb +1 -5
  44. data/lib/moneta/config.rb +101 -0
  45. data/lib/moneta/expires.rb +0 -1
  46. data/lib/moneta/expires_support.rb +3 -4
  47. data/lib/moneta/pool.rb +27 -7
  48. data/lib/moneta/proxy.rb +29 -0
  49. data/lib/moneta/server.rb +21 -14
  50. data/lib/moneta/version.rb +1 -1
  51. data/lib/moneta/wrapper.rb +5 -0
  52. data/lib/moneta.rb +2 -0
  53. data/moneta.gemspec +1 -0
  54. data/spec/active_support/cache_moneta_store_spec.rb +13 -13
  55. data/spec/features/null.rb +28 -28
  56. data/spec/features/persist.rb +3 -3
  57. data/spec/features/returndifferent.rb +4 -4
  58. data/spec/features/returnsame.rb +4 -4
  59. data/spec/features/store.rb +104 -104
  60. data/spec/helper.rb +15 -4
  61. data/spec/moneta/adapters/activerecord/adapter_activerecord_existing_connection_spec.rb +3 -1
  62. data/spec/moneta/adapters/activerecord/adapter_activerecord_spec.rb +15 -7
  63. data/spec/moneta/adapters/activerecord/standard_activerecord_spec.rb +5 -2
  64. data/spec/moneta/adapters/activerecord/standard_activerecord_with_expires_spec.rb +5 -2
  65. data/spec/moneta/adapters/activesupportcache/adapter_activesupportcache_spec.rb +3 -3
  66. data/spec/moneta/adapters/activesupportcache/adapter_activesupportcache_with_default_expires_spec.rb +2 -2
  67. data/spec/moneta/adapters/cassandra/adapter_cassandra_spec.rb +1 -1
  68. data/spec/moneta/adapters/cassandra/adapter_cassandra_with_default_expires_spec.rb +1 -1
  69. data/spec/moneta/adapters/cassandra/standard_cassandra_spec.rb +1 -1
  70. data/spec/moneta/adapters/client/client_helper.rb +4 -3
  71. data/spec/moneta/adapters/datamapper/adapter_datamapper_spec.rb +25 -8
  72. data/spec/moneta/adapters/datamapper/standard_datamapper_spec.rb +2 -2
  73. data/spec/moneta/adapters/datamapper/standard_datamapper_with_expires_spec.rb +2 -2
  74. data/spec/moneta/adapters/datamapper/standard_datamapper_with_repository_spec.rb +2 -2
  75. data/spec/moneta/adapters/faraday_helper.rb +3 -2
  76. data/spec/moneta/adapters/lruhash/adapter_lruhash_spec.rb +10 -6
  77. data/spec/moneta/adapters/memcached/dalli/adapter_memcached_dalli_spec.rb +13 -3
  78. data/spec/moneta/adapters/memcached/native/adapter_memcached_native_spec.rb +13 -3
  79. data/spec/moneta/adapters/mongo/adapter_mongo_spec.rb +2 -2
  80. data/spec/moneta/adapters/mongo/adapter_mongo_with_default_expires_spec.rb +1 -1
  81. data/spec/moneta/adapters/redis/adapter_redis_spec.rb +13 -3
  82. data/spec/moneta/adapters/redis/standard_redis_spec.rb +8 -1
  83. data/spec/moneta/adapters/sequel/adapter_sequel_spec.rb +4 -4
  84. data/spec/moneta/adapters/sequel/helper.rb +10 -5
  85. data/spec/moneta/adapters/sequel/standard_sequel_spec.rb +1 -1
  86. data/spec/moneta/adapters/sequel/standard_sequel_with_expires_spec.rb +1 -1
  87. data/spec/moneta/adapters/sqlite/adapter_sqlite_spec.rb +1 -1
  88. data/spec/moneta/adapters/sqlite/standard_sqlite_spec.rb +1 -1
  89. data/spec/moneta/adapters/sqlite/standard_sqlite_with_expires_spec.rb +1 -1
  90. data/spec/moneta/config_spec.rb +219 -0
  91. data/spec/moneta/proxies/enumerable/enumerable_spec.rb +2 -2
  92. data/spec/moneta/proxies/pool/pool_spec.rb +31 -3
  93. data/spec/moneta/proxies/transformer/transformer_bson_spec.rb +3 -1
  94. data/spec/moneta/proxies/transformer/transformer_marshal_escape_spec.rb +2 -0
  95. data/spec/rack/session_moneta_spec.rb +44 -25
  96. data/spec/restserver.rb +3 -14
  97. metadata +25 -15
  98. data/.travis.yml +0 -146
  99. data/spec/moneta/adapters/memcached/dalli/adapter_memcached_dalli_with_default_expires_spec.rb +0 -15
  100. data/spec/moneta/adapters/memcached/native/adapter_memcached_native_with_default_expires_spec.rb +0 -15
  101. data/spec/moneta/adapters/redis/adapter_redis_with_default_expires_spec.rb +0 -10
  102. data/spec/moneta/proxies/proxy/proxy_redis_spec.rb +0 -13
@@ -4,12 +4,16 @@ module Moneta
4
4
  module Adapters
5
5
  # Sqlite3 backend
6
6
  # @api public
7
- class Sqlite
8
- include Defaults
7
+ class Sqlite < Adapter
9
8
  include IncrementSupport
10
9
 
11
10
  supports :create, :each_key
12
- attr_reader :backend
11
+
12
+ config :table, default: 'moneta'
13
+ config :busy_timeout, default: 1000
14
+ config :journal_mode
15
+
16
+ backend { |file:| ::SQLite3::Database.new(file) }
13
17
 
14
18
  # @param [Hash] options
15
19
  # @option options [String] :file Database file
@@ -18,31 +22,29 @@ module Moneta
18
22
  # @option options [::Sqlite3::Database] :backend Use existing backend instance
19
23
  # @option options [String, Symbol] :journal_mode Set the journal mode for the connection
20
24
  def initialize(options = {})
21
- @table = options[:table] || 'moneta'
22
- @backend = options[:backend] ||
23
- begin
24
- raise ArgumentError, 'Option :file is required' unless options[:file]
25
- ::SQLite3::Database.new(options[:file])
26
- end
27
- @backend.busy_timeout(options[:busy_timeout] || 1000)
28
- @backend.execute("create table if not exists #{@table} (k blob not null primary key, v blob)")
29
- if journal_mode = options[:journal_mode]
30
- @backend.journal_mode = journal_mode.to_s
25
+ super
26
+
27
+ backend.busy_timeout(config.busy_timeout)
28
+ backend.execute("create table if not exists #{config.table} (k blob not null primary key, v blob)")
29
+
30
+ if journal_mode = config.journal_mode
31
+ backend.journal_mode = journal_mode.to_s
31
32
  end
33
+
32
34
  @stmts =
33
- [@exists = @backend.prepare("select exists(select 1 from #{@table} where k = ?)"),
34
- @select = @backend.prepare("select v from #{@table} where k = ?"),
35
- @replace = @backend.prepare("replace into #{@table} values (?, ?)"),
36
- @delete = @backend.prepare("delete from #{@table} where k = ?"),
37
- @clear = @backend.prepare("delete from #{@table}"),
38
- @create = @backend.prepare("insert into #{@table} values (?, ?)"),
39
- @keys = @backend.prepare("select k from #{@table}"),
40
- @count = @backend.prepare("select count(*) from #{@table}")]
41
-
42
- version = @backend.execute("select sqlite_version()").first.first
35
+ [@exists = backend.prepare("select exists(select 1 from #{config.table} where k = ?)"),
36
+ @select = backend.prepare("select v from #{config.table} where k = ?"),
37
+ @replace = backend.prepare("replace into #{config.table} values (?, ?)"),
38
+ @delete = backend.prepare("delete from #{config.table} where k = ?"),
39
+ @clear = backend.prepare("delete from #{config.table}"),
40
+ @create = backend.prepare("insert into #{config.table} values (?, ?)"),
41
+ @keys = backend.prepare("select k from #{config.table}"),
42
+ @count = backend.prepare("select count(*) from #{config.table}")]
43
+
44
+ version = backend.execute("select sqlite_version()").first.first
43
45
  if @can_upsert = ::Gem::Version.new(version) >= ::Gem::Version.new('3.24.0')
44
- @stmts << (@increment = @backend.prepare <<-SQL)
45
- insert into #{@table} values (?, ?)
46
+ @stmts << (@increment = backend.prepare <<-SQL)
47
+ insert into #{config.table} values (?, ?)
46
48
  on conflict (k)
47
49
  do update set v = cast(cast(v as integer) + ? as blob)
48
50
  where v = '0' or v = X'30' or cast(v as integer) != 0
@@ -76,8 +78,8 @@ module Moneta
76
78
 
77
79
  # (see Proxy#increment)
78
80
  def increment(key, amount = 1, options = {})
79
- @backend.transaction(:exclusive) { return super } unless @can_upsert
80
- @backend.transaction do
81
+ backend.transaction(:exclusive) { return super } unless @can_upsert
82
+ backend.transaction do
81
83
  @increment.execute!(key, amount.to_s, amount)
82
84
  return Integer(load(key))
83
85
  end
@@ -103,14 +105,14 @@ module Moneta
103
105
  # (see Proxy#close)
104
106
  def close
105
107
  @stmts.each { |s| s.close }
106
- @backend.close
108
+ backend.close
107
109
  nil
108
110
  end
109
111
 
110
112
  # (see Proxy#slice)
111
113
  def slice(*keys, **options)
112
- query = "select k, v from #{@table} where k in (#{(['?'] * keys.length).join(',')})"
113
- @backend.execute(query, keys)
114
+ query = "select k, v from #{config.table} where k in (#{(['?'] * keys.length).join(',')})"
115
+ backend.execute(query, keys)
114
116
  end
115
117
 
116
118
  # (see Proxy#values_at)
@@ -134,7 +136,7 @@ module Moneta
134
136
 
135
137
  # (see Proxy#merge!)
136
138
  def merge!(pairs, options = {})
137
- transaction = @backend.transaction if block_given?
139
+ transaction = backend.transaction if block_given?
138
140
 
139
141
  if block_given?
140
142
  existing = Hash[slice(*pairs.map { |k, _| k }.to_a)]
@@ -146,13 +148,13 @@ module Moneta
146
148
  pairs = pairs.to_a
147
149
  end
148
150
 
149
- query = "replace into #{@table} (k, v) values" + (['(?, ?)'] * pairs.length).join(',')
150
- @backend.query(query, pairs.flatten).close
151
+ query = "replace into #{config.table} (k, v) values" + (['(?, ?)'] * pairs.length).join(',')
152
+ backend.query(query, pairs.flatten).close
151
153
  rescue
152
- @backend.rollback if transaction
154
+ backend.rollback if transaction
153
155
  raise
154
156
  else
155
- @backend.commit if transaction
157
+ backend.commit if transaction
156
158
  self
157
159
  end
158
160
 
@@ -4,34 +4,28 @@ module Moneta
4
4
  module Adapters
5
5
  # TDB backend
6
6
  # @api public
7
- class TDB
8
- include Defaults
7
+ class TDB < Adapter
9
8
  include HashAdapter
10
9
  include IncrementSupport
11
10
  include EachKeySupport
12
11
 
13
12
  supports :create
14
13
 
15
- # @param [Hash] options
16
- # @option options [String] :file Database file
17
- # @option options [::TDB] :backend Use existing backend instance
18
- def initialize(options)
19
- @backend = options[:backend] ||
20
- begin
21
- raise ArgumentError, 'Option :file is required' unless file = options.delete(:file)
22
- ::TDB.new(file, options)
23
- end
24
- end
14
+ # @!method initialize(options = {})
15
+ # @param [Hash] options
16
+ # @option options [String] :file Database file
17
+ # @option options [::TDB] :backend Use existing backend instance
18
+ backend { |file:, **options| ::TDB.new(file, options) }
25
19
 
26
20
  # (see Proxy#close)
27
21
  def close
28
- @backend.close
22
+ backend.close
29
23
  nil
30
24
  end
31
25
 
32
26
  # (see Proxy#create)
33
27
  def create(key, value, options = {})
34
- @backend.insert!(key, value)
28
+ backend.insert!(key, value)
35
29
  true
36
30
  rescue ::TDB::ERR::EXISTS
37
31
  false
@@ -4,29 +4,31 @@ module Moneta
4
4
  module Adapters
5
5
  # TokyoCabinet backend
6
6
  # @api public
7
- class TokyoCabinet
8
- include Defaults
7
+ class TokyoCabinet < Adapter
9
8
  include HashAdapter
10
9
  include IncrementSupport
11
10
  include CreateSupport
12
11
  include EachKeySupport
13
12
 
14
- # @param [Hash] options
15
- # @option options [String] :file Database file
16
- # @option options [Symbol] :type (:hdb) Database type (:bdb and :hdb possible)
17
- # @option options [::TokyoCabinet::*DB] :backend Use existing backend instance
18
- def initialize(options = {})
19
- if options[:backend]
20
- @backend = options[:backend]
13
+ # @!method initialize(options = {})
14
+ # @param [Hash] options
15
+ # @option options [String] :file Database file
16
+ # @option options [Symbol] :type (:hdb) Database type (:bdb and :hdb possible)
17
+ # @option options [::TokyoCabinet::*DB] :backend Use existing backend instance
18
+ backend do |file:, type: :hdb|
19
+ case type
20
+ when :bdb
21
+ ::TokyoCabinet::BDB.new.tap do |backend|
22
+ backend.open(file, ::TokyoCabinet::BDB::OWRITER | ::TokyoCabinet::BDB::OCREAT) or
23
+ raise backend.errmsg(backend.ecode)
24
+ end
25
+ when :hdb
26
+ ::TokyoCabinet::HDB.new.tap do |backend|
27
+ backend.open(file, ::TokyoCabinet::HDB::OWRITER | ::TokyoCabinet::HDB::OCREAT) or
28
+ raise backend.errmsg(backend.ecode)
29
+ end
21
30
  else
22
- raise ArgumentError, 'Option :file is required' unless options[:file]
23
- if options[:type] == :bdb
24
- @backend = ::TokyoCabinet::BDB.new
25
- @backend.open(options[:file], ::TokyoCabinet::BDB::OWRITER | ::TokyoCabinet::BDB::OCREAT)
26
- else
27
- @backend = ::TokyoCabinet::HDB.new
28
- @backend.open(options[:file], ::TokyoCabinet::HDB::OWRITER | ::TokyoCabinet::HDB::OCREAT)
29
- end or raise @backend.errmsg(@backend.ecode)
31
+ raise ArgumentError, ":type must be :bdb or :hdb"
30
32
  end
31
33
  end
32
34
 
@@ -10,51 +10,50 @@ module Moneta
10
10
  module Adapters
11
11
  # TokyoTyrant backend
12
12
  # @api public
13
- class TokyoTyrant
14
- include Defaults
13
+ class TokyoTyrant < Adapter
15
14
  include HashAdapter
16
15
 
17
16
  # error code: no record found
18
17
  ENOREC = 7
19
18
 
20
19
  supports :create, :increment
21
- attr_reader :backend
20
+
21
+ backend do |host: '127.0.0.1', port: 1978|
22
+ if defined?(::TokyoTyrant::RDB)
23
+ # Use ruby client
24
+ ::TokyoTyrant::RDB.new.tap do |backend|
25
+ backend.open(host, port) or raise backend.errmsg
26
+ end
27
+ else
28
+ # Use native client
29
+ ::TokyoTyrant::DB.new(host, port)
30
+ end
31
+ end
22
32
 
23
33
  # @param [Hash] options
24
34
  # @option options [String] :host ('127.0.0.1') Server host name
25
35
  # @option options [Integer] :port (1978) Server port
26
36
  # @option options [::TokyoTyrant::RDB] :backend Use existing backend instance
27
37
  def initialize(options = {})
28
- options[:host] ||= '127.0.0.1'
29
- options[:port] ||= 1978
30
- if options[:backend]
31
- @backend = options[:backend]
32
- elsif defined?(::TokyoTyrant::RDB)
33
- # Use ruby client
34
- @backend = ::TokyoTyrant::RDB.new
35
- @backend.open(options[:host], options[:port]) or error
36
- else
37
- # Use native client
38
- @backend = ::TokyoTyrant::DB.new(options[:host], options[:port])
39
- end
40
- @native = @backend.class.name != 'TokyoTyrant::RDB'
38
+ super
39
+ @native = backend.class.name != 'TokyoTyrant::RDB'
41
40
  probe = '__tokyotyrant_endianness_probe'
42
- @backend.delete(probe)
43
- @backend.addint(probe, 1)
44
- @pack = @backend.delete(probe) == [1].pack('l>') ? 'l>' : 'l<'
41
+ backend.delete(probe)
42
+ backend.addint(probe, 1)
43
+ @pack = backend.delete(probe) == [1].pack('l>') ? 'l>' : 'l<'
45
44
  end
46
45
 
47
46
  # (see Proxy#load)
48
47
  def load(key, options = {})
49
- value = @backend[key]
48
+ value = backend[key]
50
49
  # raise if there is an error and the error is not "no record"
51
- error if value == nil && @backend.ecode != ENOREC
50
+ error if value == nil && backend.ecode != ENOREC
52
51
  value && unpack(value)
53
52
  end
54
53
 
55
54
  # (see Proxy#store)
56
55
  def store(key, value, options = {})
57
- @backend.put(key, pack(value)) or error
56
+ backend.put(key, pack(value)) or error
58
57
  value
59
58
  end
60
59
 
@@ -62,14 +61,14 @@ module Moneta
62
61
  def delete(key, options = {})
63
62
  value = load(key, options)
64
63
  if value
65
- @backend.delete(key) or error
64
+ backend.delete(key) or error
66
65
  value
67
66
  end
68
67
  end
69
68
 
70
69
  # (see Proxy#increment)
71
70
  def increment(key, amount = 1, options = {})
72
- @backend.addint(key, amount) or error
71
+ backend.addint(key, amount) or error
73
72
  end
74
73
 
75
74
  # (see Proxy#create)
@@ -77,18 +76,18 @@ module Moneta
77
76
  if @native
78
77
  begin
79
78
  # Native client throws an exception
80
- @backend.putkeep(key, pack(value))
79
+ backend.putkeep(key, pack(value))
81
80
  rescue TokyoTyrantError
82
81
  false
83
82
  end
84
83
  else
85
- @backend.putkeep(key, pack(value))
84
+ backend.putkeep(key, pack(value))
86
85
  end
87
86
  end
88
87
 
89
88
  # (see Proxy#close)
90
89
  def close
91
- @backend.close
90
+ backend.close
92
91
  nil
93
92
  end
94
93
 
@@ -96,10 +95,10 @@ module Moneta
96
95
  def slice(*keys, **options)
97
96
  hash =
98
97
  if @native
99
- @backend.mget(*keys)
98
+ backend.mget(*keys)
100
99
  else
101
100
  hash = Hash[keys.map { |key| [key] }]
102
- raise unless @backend.mget(hash) >= 0
101
+ raise unless backend.mget(hash) >= 0
103
102
  hash
104
103
  end
105
104
 
@@ -142,7 +141,7 @@ module Moneta
142
141
  end
143
142
 
144
143
  def error
145
- raise "#{@backend.class.name} error: #{@backend.errmsg}"
144
+ raise "#{backend.class.name} error: #{backend.errmsg}"
146
145
  end
147
146
  end
148
147
  end
@@ -5,11 +5,7 @@ module Moneta
5
5
  # YAML::Store backend
6
6
  # @api public
7
7
  class YAML < PStore
8
- protected
9
-
10
- def new_store(options)
11
- ::YAML::Store.new(options[:file])
12
- end
8
+ backend { |file:| ::YAML::Store.new(file) }
13
9
  end
14
10
  end
15
11
  end
@@ -0,0 +1,101 @@
1
+ require 'set'
2
+
3
+ module Moneta
4
+ # Some docs here
5
+ module Config
6
+ # @api private
7
+ module ClassMethods
8
+ def config(name, coerce: nil, default: nil, required: false, &block)
9
+ raise ArgumentError, 'name must be a symbol' unless Symbol === name
10
+
11
+ defaults = config_defaults
12
+
13
+ raise ArgumentError, "#{name} is already a config option" if defaults.key?(name)
14
+ raise ArgumentError, "coerce must respond to :to_proc" if coerce && !coerce.respond_to?(:to_proc)
15
+
16
+ defaults.merge!(name => default.freeze).freeze
17
+ instance_variable_set :@config_defaults, defaults
18
+
19
+ instance_variable_set :@config_coercions, config_coercions.merge!(name => coerce.to_proc) if coerce
20
+ instance_variable_set :@config_required_keys, config_required_keys.add(name).freeze if required
21
+ instance_variable_set :@config_blocks, config_blocks.merge!(name => block) if block
22
+ end
23
+
24
+ def config_variable(name)
25
+ if instance_variable_defined?(name)
26
+ instance_variable_get(name).dup
27
+ elsif superclass.respond_to?(:config_variable)
28
+ superclass.config_variable(name)
29
+ end
30
+ end
31
+
32
+ def config_defaults
33
+ config_variable(:@config_defaults) || {}
34
+ end
35
+
36
+ def config_required_keys
37
+ config_variable(:@config_required_keys) || Set.new
38
+ end
39
+
40
+ def config_coercions
41
+ config_variable(:@config_coercions) || {}
42
+ end
43
+
44
+ def config_blocks
45
+ config_variable(:@config_blocks) || {}
46
+ end
47
+
48
+ def config_struct
49
+ unless @config_struct
50
+ keys = config_defaults.keys
51
+ @config_struct = Struct.new(*keys) unless keys.empty?
52
+ end
53
+
54
+ @config_struct
55
+ end
56
+ end
57
+
58
+ def config
59
+ raise "Not configured" unless defined?(@config)
60
+ @config
61
+ end
62
+
63
+ def self.included(base)
64
+ base.extend(ClassMethods)
65
+ end
66
+
67
+ protected
68
+
69
+ def configure(**options)
70
+ raise 'Already configured' if defined?(@config)
71
+
72
+ self.class.config_required_keys.each do |key|
73
+ raise ArgumentError, "#{key} is required" unless options.key? key
74
+ end
75
+
76
+ defaults = self.class.config_defaults
77
+
78
+ overrides, remainder = options
79
+ .partition { |key,| defaults.key? key }
80
+ .map { |pairs| pairs.to_h }
81
+
82
+ self.class.config_coercions.each do |key, coerce|
83
+ overrides[key] = coerce.call(overrides[key]) if overrides.key?(key)
84
+ end
85
+
86
+ overridden = defaults.merge!(overrides)
87
+
88
+ config_blocks = self.class.config_blocks
89
+ values = overridden.map do |key, value|
90
+ if config_block = config_blocks[key]
91
+ instance_exec(**overridden, &config_block)
92
+ else
93
+ value
94
+ end
95
+ end
96
+
97
+ @config = self.class.config_struct&.new(*values).freeze
98
+ remainder
99
+ end
100
+ end
101
+ end
@@ -14,7 +14,6 @@ module Moneta
14
14
  def initialize(adapter, options = {})
15
15
  raise 'Store already supports feature :expires' if adapter.supports?(:expires)
16
16
  super
17
- self.default_expires = options[:expires]
18
17
  end
19
18
 
20
19
  # (see Proxy#key?)
@@ -3,8 +3,6 @@ module Moneta
3
3
  #
4
4
  #
5
5
  module ExpiresSupport
6
- attr_accessor :default_expires
7
-
8
6
  protected
9
7
 
10
8
  # Calculates the time when something will expire.
@@ -19,7 +17,7 @@ module Moneta
19
17
  # @return [false] if it should not expire
20
18
  # @return [Time] the time when something should expire
21
19
  # @return [nil] if it is not known
22
- def expires_at(options, default = @default_expires)
20
+ def expires_at(options, default = config.expires)
23
21
  value = expires_value(options, default)
24
22
  Numeric === value ? Time.now + value : value
25
23
  end
@@ -36,7 +34,7 @@ module Moneta
36
34
  # @return [false] if it should not expire
37
35
  # @return [Numeric] seconds until expiration
38
36
  # @return [nil] if it is not known
39
- def expires_value(options, default = @default_expires)
37
+ def expires_value(options, default = config.expires)
40
38
  case value = options[:expires]
41
39
  when 0, false
42
40
  false
@@ -54,6 +52,7 @@ module Moneta
54
52
  class << self
55
53
  def included(base)
56
54
  base.supports(:expires) if base.respond_to?(:supports)
55
+ base.config :expires
57
56
  end
58
57
  end
59
58
  end
data/lib/moneta/pool.rb CHANGED
@@ -87,6 +87,7 @@ module Moneta
87
87
  @waiting_since = [] if @timeout
88
88
  @last_checkout = nil
89
89
  @stopping = false
90
+ @idle_time = nil
90
91
 
91
92
  # Launch the manager thread
92
93
  @thread = run
@@ -126,10 +127,16 @@ module Moneta
126
127
  populate_stores
127
128
 
128
129
  until @stopping && @stores.empty?
130
+ loop_start = Time.now
131
+
129
132
  # Block until a message arrives, or until we time out for some reason
130
- if request = pop
131
- handle_request(request)
132
- end
133
+ request = pop
134
+
135
+ # Record how long we were idle, for stats purposes
136
+ @idle_time = Time.now - loop_start
137
+
138
+ # If a message arrived, handle it
139
+ handle_request(request) if request
133
140
 
134
141
  # Handle any stale checkout requests
135
142
  handle_timed_out_requests
@@ -186,7 +193,7 @@ module Moneta
186
193
  # @return [Integer, nil]
187
194
  def timeout
188
195
  # Time to wait before there will be stores that should be closed
189
- ttl = if @ttl && @last_checkout && !@available.empty?
196
+ ttl = if @ttl && @last_checkout && stores_available? && stores_unneeded?
190
197
  [@ttl - (Time.now - @last_checkout), 0].max
191
198
  end
192
199
 
@@ -199,6 +206,18 @@ module Moneta
199
206
  [ttl, timeout].compact.min
200
207
  end
201
208
 
209
+ def stores_available?
210
+ !@available.empty?
211
+ end
212
+
213
+ def stores_unneeded?
214
+ @stores.length > @min
215
+ end
216
+
217
+ def stores_maxed?
218
+ @max != nil && @stores.length == @max
219
+ end
220
+
202
221
  def pop
203
222
  @mutex.synchronize do
204
223
  @resource.wait(@mutex, timeout) if @inbox.empty?
@@ -218,7 +237,7 @@ module Moneta
218
237
  reply.resolve(ShutdownError.new("Shutting down"))
219
238
  elsif !@available.empty?
220
239
  reply.resolve(@available.pop)
221
- elsif !@max || @stores.length < @max
240
+ elsif !stores_maxed?
222
241
  begin
223
242
  reply.resolve(add_store)
224
243
  rescue => e
@@ -258,7 +277,8 @@ module Moneta
258
277
  waiting: @waiting.length,
259
278
  longest_wait: @timeout && !@waiting_since.empty? ? @waiting_since.first.dup : nil,
260
279
  stopping: @stopping,
261
- last_checkout: @last_checkout && @last_checkout.dup)
280
+ last_checkout: @last_checkout && @last_checkout.dup,
281
+ idle_time: @idle_time.dup)
262
282
  end
263
283
 
264
284
  def handle_request(request)
@@ -289,9 +309,9 @@ module Moneta
289
309
  # store to become available. If not specified, will wait forever.
290
310
  # @yield A builder context for speciying how to construct stores
291
311
  def initialize(options = {}, &block)
292
- super(nil)
293
312
  @id = "Moneta::Pool(#{object_id})"
294
313
  @manager = PoolManager.new(Builder.new(&block), **options)
314
+ super(nil, options)
295
315
  end
296
316
 
297
317
  # Closing has no effect on the pool, as stores are closed in the background
data/lib/moneta/proxy.rb CHANGED
@@ -3,6 +3,7 @@ module Moneta
3
3
  # @api public
4
4
  class Proxy
5
5
  include Defaults
6
+ include Config
6
7
 
7
8
  attr_reader :adapter
8
9
 
@@ -10,6 +11,7 @@ module Moneta
10
11
  # @param [Hash] options
11
12
  def initialize(adapter, options = {})
12
13
  @adapter = adapter
14
+ configure(**options)
13
15
  end
14
16
 
15
17
  # (see Defaults#key?)
@@ -133,5 +135,32 @@ module Moneta
133
135
  super
134
136
  end
135
137
  end
138
+
139
+ # Overrides the default implementation of the config method to:
140
+ #
141
+ # * pass the adapter's config, if this proxy has no configuration of its
142
+ # own
143
+ # * return a merged configuration, allowing the proxy have precedence over
144
+ # the adapter
145
+ def config
146
+ unless @proxy_config
147
+ config = super
148
+ adapter_config = adapter&.config
149
+
150
+ @proxy_config =
151
+ if config && adapter_config
152
+ adapter_members = adapter_config.members - config.members
153
+ members = config.members + adapter_members
154
+ struct = Struct.new(*members)
155
+
156
+ values = config.values + adapter_config.to_h.values_at(*adapter_members)
157
+ struct.new(*values)
158
+ else
159
+ config || adapter_config
160
+ end
161
+ end
162
+
163
+ @proxy_config
164
+ end
136
165
  end
137
166
  end