moneta 1.4.2 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +35 -38
  3. data/CHANGES +9 -0
  4. data/CONTRIBUTORS +2 -0
  5. data/Gemfile +143 -55
  6. data/README.md +5 -4
  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 +1 -1
  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/moneta/adapters/client/client_helper.rb +4 -3
  55. data/spec/moneta/adapters/faraday_helper.rb +3 -2
  56. data/spec/moneta/adapters/lruhash/adapter_lruhash_spec.rb +10 -6
  57. data/spec/moneta/adapters/mongo/adapter_mongo_spec.rb +2 -2
  58. data/spec/moneta/config_spec.rb +219 -0
  59. data/spec/moneta/proxies/transformer/transformer_bson_spec.rb +3 -1
  60. data/spec/moneta/proxies/transformer/transformer_marshal_escape_spec.rb +2 -0
  61. data/spec/rack/session_moneta_spec.rb +44 -25
  62. data/spec/restserver.rb +3 -14
  63. metadata +24 -6
@@ -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
@@ -309,9 +309,9 @@ module Moneta
309
309
  # store to become available. If not specified, will wait forever.
310
310
  # @yield A builder context for speciying how to construct stores
311
311
  def initialize(options = {}, &block)
312
- super(nil)
313
312
  @id = "Moneta::Pool(#{object_id})"
314
313
  @manager = PoolManager.new(Builder.new(&block), **options)
314
+ super(nil, options)
315
315
  end
316
316
 
317
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
data/lib/moneta/server.rb CHANGED
@@ -4,14 +4,17 @@ module Moneta
4
4
  # Moneta server to be used together with Moneta::Adapters::Client
5
5
  # @api public
6
6
  class Server
7
- TIMEOUT = 1
8
- MAXSIZE = 0x100000
7
+ include Config
8
+
9
+ config :timeout, default: 1
10
+ config :max_size, default: 0x100000
9
11
 
10
12
  # @api private
11
13
  class Connection
12
- def initialize(io, store)
14
+ def initialize(io, store, max_size)
13
15
  @io = io
14
16
  @store = store
17
+ @max_size = max_size
15
18
  @fiber = Fiber.new { run }
16
19
  end
17
20
 
@@ -58,7 +61,7 @@ module Moneta
58
61
 
59
62
  def read_msg
60
63
  size = read(4).unpack('N').first
61
- throw :closed, 'Message too big' if size > MAXSIZE
64
+ throw :closed, 'Message too big' if size > @max_size
62
65
  Marshal.load(read(size))
63
66
  end
64
67
 
@@ -155,9 +158,13 @@ module Moneta
155
158
  # @param [Hash] options
156
159
  # @option options [Integer] :port (9000) TCP port
157
160
  # @option options [String] :socket Alternative Unix socket file name
161
+ # @option options [Integer] :timeout (1) Number of seconds to timeout on IO.select
162
+ # @option options [Integer] :max_size (0x100000) Maximum number of bytes
163
+ # allowed to be sent by clients in requests
158
164
  def initialize(store, options = {})
165
+ options = configure(**options)
159
166
  @store = store
160
- @server = start(options)
167
+ @server = start(**options)
161
168
  @ios = [@server]
162
169
  @reads = @ios.dup
163
170
  @writes = []
@@ -187,7 +194,7 @@ module Moneta
187
194
  @ios
188
195
  .reject { |io| io == @server }
189
196
  .each { |io| close_connection(io) }
190
- File.unlink(@socket) if @socket rescue nil
197
+ File.unlink(config.socket) if config.socket rescue nil
191
198
  end
192
199
  end
193
200
 
@@ -202,7 +209,7 @@ module Moneta
202
209
  private
203
210
 
204
211
  def mainloop
205
- if ready = IO.select(@reads, @writes, @ios, TIMEOUT)
212
+ if ready = IO.select(@reads, @writes, @ios, config.timeout)
206
213
  reads, writes, errors = ready
207
214
  errors.each { |io| close_connection(io) }
208
215
 
@@ -229,7 +236,7 @@ module Moneta
229
236
 
230
237
  def accept_connection
231
238
  io = @server.accept
232
- @connections[io] = Connection.new(io, @store)
239
+ @connections[io] = Connection.new(io, @store, config.max_size)
233
240
  @ios << io
234
241
  resume(io)
235
242
  ensure
@@ -261,21 +268,21 @@ module Moneta
261
268
  end
262
269
  end
263
270
 
264
- def start(options)
265
- if @socket = options[:socket]
271
+ def start(host: '127.0.0.1', port: 9000, socket: nil)
272
+ if socket
266
273
  begin
267
- UNIXServer.open(@socket)
274
+ UNIXServer.open(socket)
268
275
  rescue Errno::EADDRINUSE
269
- if client = (UNIXSocket.open(@socket) rescue nil)
276
+ if client = (UNIXSocket.open(socket) rescue nil)
270
277
  client.close
271
278
  raise
272
279
  end
273
- File.unlink(@socket)
280
+ File.unlink(socket)
274
281
  tries ||= 0
275
282
  (tries += 1) < 3 ? retry : raise
276
283
  end
277
284
  else
278
- TCPServer.open(options[:host] || '127.0.0.1', options[:port] || 9000)
285
+ TCPServer.open(host, port)
279
286
  end
280
287
  end
281
288
 
@@ -1,5 +1,5 @@
1
1
  module Moneta
2
2
  # Moneta version number
3
3
  # @api public
4
- VERSION = '1.4.2'.freeze
4
+ VERSION = '1.5.0'.freeze
5
5
  end
@@ -71,5 +71,10 @@ module Moneta
71
71
  def merge!(pairs, options = {})
72
72
  wrap(:merge!, pairs, options) { super }
73
73
  end
74
+
75
+ # (see Proxy#config)
76
+ def config
77
+ wrap(:config) { super }
78
+ end
74
79
  end
75
80
  end
data/lib/moneta.rb CHANGED
@@ -3,9 +3,11 @@
3
3
  # * {Moneta.new}
4
4
  # * {Moneta.build}
5
5
  module Moneta
6
+ autoload :Adapter, 'moneta/adapter'
6
7
  autoload :Builder, 'moneta/builder'
7
8
  autoload :Cache, 'moneta/cache'
8
9
  autoload :CreateSupport, 'moneta/create_support'
10
+ autoload :Config, 'moneta/config'
9
11
  autoload :DBMAdapter, 'moneta/dbm_adapter'
10
12
  autoload :Defaults, 'moneta/defaults'
11
13
  autoload :EachKeySupport, 'moneta/each_key_support'
data/moneta.gemspec CHANGED
@@ -26,6 +26,7 @@ Gem::Specification.new do |s|
26
26
 
27
27
  s.required_ruby_version = '>= 2.3.0'
28
28
 
29
+ s.add_development_dependency 'multi_json', '~> 1.15.0'
29
30
  s.add_development_dependency 'parallel_tests', '~> 2.29.2'
30
31
  s.add_development_dependency 'rantly', '~> 1.2.0'
31
32
  s.add_development_dependency 'rspec', '~> 3.0'
@@ -12,13 +12,14 @@ RSpec.shared_context :start_server do |**options|
12
12
  puts "Failed to start server - #{ex.message}"
13
13
  tries ||= 0
14
14
  tries += 1
15
- sleep Moneta::Server::TIMEOUT
15
+ timeout = options[:timeout] || Moneta::Server.config_defaults[:timeout]
16
+ sleep 1
16
17
  tries < 3 ? retry : raise
17
18
  end
18
19
  end
19
20
 
20
21
  after :context do
21
- @server.stop
22
- @thread.join
22
+ @server&.stop
23
+ @thread&.join
23
24
  end
24
25
  end
@@ -1,9 +1,10 @@
1
1
  RSpec.shared_context :faraday_adapter do
2
2
  before :context do
3
- require 'faraday/adapter/manticore' if defined?(JRUBY_VERSION)
3
+ #require 'faraday/adapter/manticore' if defined?(JRUBY_VERSION)
4
4
  end
5
5
 
6
6
  let(:faraday_adapter) do
7
- defined?(JRUBY_VERSION) ? :manticore : :net_http
7
+ #defined?(JRUBY_VERSION) ? :manticore : :net_http
8
+ :net_http
8
9
  end
9
10
  end
@@ -52,8 +52,10 @@ describe 'adapter_lruhash', adapter: :LRUHash do
52
52
 
53
53
  it 'adds a value that is as large as the default max_size when max_size is missing' do
54
54
  store = Moneta::Adapters::LRUHash.new
55
+ expect(store.config.max_size).to eq Moneta::Adapters::LRUHash.config_defaults[:max_size]
56
+
55
57
  large_item = 'Really big'
56
- allow(large_item).to receive(:bytesize).and_return(Moneta::Adapters::LRUHash::DEFAULT_MAX_SIZE)
58
+ allow(large_item).to receive(:bytesize).and_return(Moneta::Adapters::LRUHash.config_defaults[:max_size])
57
59
  store[:really_big] = large_item
58
60
  store[:really_big].should eq(large_item)
59
61
  end
@@ -61,7 +63,7 @@ describe 'adapter_lruhash', adapter: :LRUHash do
61
63
  it 'does not add values that are larger than the default max_size when max_size is missing' do
62
64
  store = Moneta::Adapters::LRUHash.new
63
65
  large_item = 'Really big'
64
- allow(large_item).to receive(:bytesize).and_return(Moneta::Adapters::LRUHash::DEFAULT_MAX_SIZE + 1)
66
+ allow(large_item).to receive(:bytesize).and_return(Moneta::Adapters::LRUHash.config_defaults[:max_size] + 1)
65
67
  store[:really_big] = large_item
66
68
  store[:really_big].should be_nil
67
69
  end
@@ -69,7 +71,7 @@ describe 'adapter_lruhash', adapter: :LRUHash do
69
71
  it 'adds values that are larger than the default max_size when max_size is nil' do
70
72
  store = Moneta::Adapters::LRUHash.new(max_size: nil)
71
73
  large_item = 'Really big'
72
- allow(large_item).to receive(:bytesize).and_return(Moneta::Adapters::LRUHash::DEFAULT_MAX_SIZE + 1)
74
+ allow(large_item).to receive(:bytesize).and_return(Moneta::Adapters::LRUHash.config_defaults[:max_size] + 1)
73
75
  store[:really_big] = large_item
74
76
  store[:really_big].should eq(large_item)
75
77
  end
@@ -94,7 +96,8 @@ describe 'adapter_lruhash', adapter: :LRUHash do
94
96
  end
95
97
 
96
98
  it 'only allows the default number of items when max_count is missing' do
97
- stub_const('Moneta::Adapters::LRUHash::DEFAULT_MAX_COUNT', 5)
99
+ defaults = Moneta::Adapters::LRUHash.config_defaults
100
+ allow(Moneta::Adapters::LRUHash).to receive(:config_defaults).and_return(defaults.merge(max_count: 5))
98
101
  store = Moneta::Adapters::LRUHash.new(max_value: nil, max_size: nil)
99
102
  (1..6).each { |n| store[n] = n }
100
103
  store.key?(1).should be false
@@ -103,8 +106,9 @@ describe 'adapter_lruhash', adapter: :LRUHash do
103
106
  store[6].should eq(6)
104
107
  end
105
108
 
106
- it 'adds more values than DEFAULT_MAX_COUNT allows when max_count is nil' do
107
- stub_const('Moneta::Adapters::LRUHash::DEFAULT_MAX_COUNT', 5)
109
+ it 'adds more values than the default max_count allows when max_count is nil' do
110
+ defaults = Moneta::Adapters::LRUHash.config_defaults
111
+ allow(Moneta::Adapters::LRUHash).to receive(:config_defaults).and_return(defaults.merge(max_count: 5))
108
112
  store = Moneta::Adapters::LRUHash.new(max_count: nil, max_value: nil, max_size: nil)
109
113
  (1..6).each { |n| store[n] = n }
110
114
  store[1].should eq(1)
@@ -28,7 +28,7 @@ describe 'adapter_mongo', adapter: :Mongo do
28
28
  end
29
29
 
30
30
  it 'uses the database specified via the :database option' do
31
- expect(store.backend.database.name).to eq database
31
+ expect(store.config.database).to eq database
32
32
  end
33
33
 
34
34
  it 'uses the database specified via the :db option' do
@@ -36,6 +36,6 @@ describe 'adapter_mongo', adapter: :Mongo do
36
36
  db: database,
37
37
  collection: 'adapter_mongo'
38
38
  )
39
- expect(store.backend.database.name).to eq database
39
+ expect(store.config.database).to eq database
40
40
  end
41
41
  end
@@ -0,0 +1,219 @@
1
+ describe Moneta::Config do
2
+ describe 'without any configuration' do
3
+ it 'does not set the config attribute' do
4
+ klass = Class.new do
5
+ include ::Moneta::Config
6
+
7
+ def initialize(**options)
8
+ configure(**options)
9
+ end
10
+ end
11
+
12
+ instance = klass.new(k: 'v')
13
+ expect(instance.config).to be nil
14
+ end
15
+ end
16
+
17
+ describe 'basic functionality' do
18
+ subject :klass do
19
+ Class.new do
20
+ include ::Moneta::Config
21
+
22
+ config :a
23
+ config :b
24
+
25
+ def initialize(**options)
26
+ configure(**options)
27
+ end
28
+ end
29
+ end
30
+
31
+ it 'sets all config values to nil by default' do
32
+ instance = klass.new
33
+ expect(instance.config.a).to eq nil
34
+ expect(instance.config.b).to be nil
35
+ end
36
+
37
+ it 'sets config values with values provided to #configure' do
38
+ instance = klass.new(a: 1)
39
+ expect(instance.config.a).to eq 1
40
+ expect(instance.config.b).to be nil
41
+ end
42
+
43
+ it 'freezes the config' do
44
+ instance = klass.new
45
+ expect(instance.config.frozen?).to be true
46
+ end
47
+ end
48
+
49
+ describe 'with required arguments' do
50
+ subject :klass do
51
+ Class.new do
52
+ include ::Moneta::Config
53
+
54
+ config :a, required: true
55
+ config :b, default: 'x', required: true
56
+
57
+ def initialize(**options)
58
+ configure(**options)
59
+ end
60
+ end
61
+ end
62
+
63
+ it 'raises an ArgumentError if #configure is called without one of the required arguments' do
64
+ expect { klass.new(a: 1) }.to raise_error ArgumentError, 'b is required'
65
+ expect { klass.new(b: 1) }.to raise_error ArgumentError, 'a is required'
66
+ end
67
+ end
68
+
69
+ describe 'with defaults' do
70
+ subject :klass do
71
+ Class.new do
72
+ include ::Moneta::Config
73
+
74
+ config :a, default: 't'
75
+ config :b, default: 's'
76
+
77
+ def initialize(**options)
78
+ configure(**options)
79
+ end
80
+ end
81
+ end
82
+
83
+ it 'uses the defaults if no argument is provided' do
84
+ instance = klass.new(a: 1)
85
+
86
+ expect(instance.config.a).to eq 1
87
+ expect(instance.config.b).to eq 's'
88
+ end
89
+
90
+ it 'allows falsy values to override truthy defaults' do
91
+ instance = klass.new(a: nil, b: false)
92
+
93
+ expect(instance.config.a).to be nil
94
+ expect(instance.config.b).to be false
95
+ end
96
+ end
97
+
98
+ describe 'with coercion' do
99
+ describe 'using a symbol' do
100
+ subject :klass do
101
+ Class.new do
102
+ include ::Moneta::Config
103
+
104
+ config :a, coerce: :to_s
105
+
106
+ def initialize(**options)
107
+ configure(**options)
108
+ end
109
+ end
110
+ end
111
+
112
+ it "uses the symbol's to_proc property" do
113
+ instance = klass.new(a: :x)
114
+ expect(instance.config.a).to eq 'x'
115
+ end
116
+ end
117
+
118
+ describe 'using a lambda' do
119
+ subject :klass do
120
+ Class.new do
121
+ include ::Moneta::Config
122
+
123
+ config :a, coerce: lambda { |a| a.to_sym }
124
+
125
+ def initialize(**options)
126
+ configure(**options)
127
+ end
128
+ end
129
+ end
130
+
131
+ it "calls the lambda" do
132
+ instance = klass.new(a: 'x')
133
+ expect(instance.config.a).to eq :x
134
+ end
135
+ end
136
+ end
137
+
138
+ describe 'with a block' do
139
+ subject :klass do
140
+ Class.new do
141
+ include ::Moneta::Config
142
+
143
+ config :a do |a:, b:|
144
+ { a: a, b: b, test: @test }
145
+ end
146
+
147
+ config :b, default: 'b default'
148
+
149
+ def initialize(test: nil, **options)
150
+ @test = test
151
+ configure(**options)
152
+ end
153
+ end
154
+ end
155
+
156
+ it 'calls the block after all arguments and defaults have been processed' do
157
+ instance1 = klass.new(a: 'a value')
158
+ expect(instance1.config.a).to include(a: 'a value', b: 'b default')
159
+
160
+ instance2 = klass.new(b: 'b value')
161
+ expect(instance2.config.a).to include(a: nil, b: 'b value')
162
+ end
163
+
164
+ it 'calls the block using instance_exec' do
165
+ instance = klass.new(test: 'test value')
166
+ expect(instance.config.a).to include(test: 'test value')
167
+ end
168
+ end
169
+
170
+ describe 'with inheritance' do
171
+ subject :klass do
172
+ Class.new do
173
+ include ::Moneta::Config
174
+
175
+ config :a
176
+
177
+ def initialize(**options)
178
+ configure(**options)
179
+ end
180
+ end
181
+ end
182
+
183
+ it 'does not allow subclasses to override superclass config' do
184
+ expect do
185
+ Class.new(klass) do
186
+ config :a
187
+ end
188
+ end.to raise_error ArgumentError, 'a is already a config option'
189
+ end
190
+
191
+ it 'does not affect the superclass when additional config is added to the subclass' do
192
+ klass2 = Class.new(klass) do
193
+ config :b
194
+ end
195
+
196
+ instance1 = klass.new(a: 1, b: 2)
197
+ expect(instance1.config.to_h).to eq(a: 1)
198
+
199
+ instance2 = klass2.new(a: 1, b: 2)
200
+ expect(instance2.config.to_h).to eq(a: 1, b: 2)
201
+ end
202
+
203
+ it 'is possible for two subclasses to have the same additional config' do
204
+ klass2 = Class.new(klass) do
205
+ config :b
206
+ end
207
+
208
+ klass3 = Class.new(klass) do
209
+ config :b
210
+ end
211
+
212
+ instance2 = klass2.new(a: 2, b: 1)
213
+ expect(instance2.config.to_h).to eq(a: 2, b: 1)
214
+
215
+ instance3 = klass3.new(a: 1, b: 2)
216
+ expect(instance3.config.to_h).to eq(a: 1, b: 2)
217
+ end
218
+ end
219
+ end
@@ -1,4 +1,6 @@
1
- describe 'transformer_bson', proxy: :Transformer do
1
+ # Currently broken in JRuby 9.3 - see https://github.com/jruby/jruby/issues/6941
2
+
3
+ describe 'transformer_bson', proxy: :Transformer, broken: defined?(JRUBY_VERSION) && ::Gem::Version.new(JRUBY_VERSION) >= ::Gem::Version.new('9.3.0.0') do
2
4
  moneta_build do
3
5
  Moneta.build do
4
6
  use :Transformer, key: :bson, value: :bson
@@ -1,3 +1,5 @@
1
+ require 'uri'
2
+
1
3
  describe 'transformer_marshal_escape', proxy: :Transformer do
2
4
  moneta_build do
3
5
  Moneta.build do