moneta 0.6.0 → 0.7.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.
Files changed (212) hide show
  1. data/.gitignore +7 -0
  2. data/.travis.yml +39 -0
  3. data/Gemfile +61 -0
  4. data/LICENSE +2 -2
  5. data/README.md +450 -0
  6. data/Rakefile +29 -51
  7. data/SPEC.md +75 -0
  8. data/benchmarks/run.rb +195 -0
  9. data/lib/action_dispatch/middleware/session/moneta_store.rb +11 -0
  10. data/lib/active_support/cache/moneta_store.rb +55 -0
  11. data/lib/moneta.rb +121 -67
  12. data/lib/moneta/adapters/activerecord.rb +87 -0
  13. data/lib/moneta/adapters/cassandra.rb +91 -0
  14. data/lib/moneta/adapters/client.rb +69 -0
  15. data/lib/moneta/adapters/cookie.rb +35 -0
  16. data/lib/moneta/adapters/couch.rb +57 -0
  17. data/lib/moneta/adapters/datamapper.rb +75 -0
  18. data/lib/moneta/adapters/dbm.rb +25 -0
  19. data/lib/moneta/adapters/file.rb +79 -0
  20. data/lib/moneta/adapters/fog.rb +51 -0
  21. data/lib/moneta/adapters/gdbm.rb +25 -0
  22. data/lib/moneta/adapters/hbase.rb +101 -0
  23. data/lib/moneta/adapters/leveldb.rb +35 -0
  24. data/lib/moneta/adapters/localmemcache.rb +28 -0
  25. data/lib/moneta/adapters/lruhash.rb +85 -0
  26. data/lib/moneta/adapters/memcached.rb +11 -0
  27. data/lib/moneta/adapters/memcached_dalli.rb +69 -0
  28. data/lib/moneta/adapters/memcached_native.rb +70 -0
  29. data/lib/moneta/adapters/memory.rb +10 -0
  30. data/lib/moneta/adapters/mongo.rb +50 -0
  31. data/lib/moneta/adapters/null.rb +30 -0
  32. data/lib/moneta/adapters/pstore.rb +69 -0
  33. data/lib/moneta/adapters/redis.rb +68 -0
  34. data/lib/moneta/adapters/riak.rb +57 -0
  35. data/lib/moneta/adapters/sdbm.rb +35 -0
  36. data/lib/moneta/adapters/sequel.rb +79 -0
  37. data/lib/moneta/adapters/sqlite.rb +65 -0
  38. data/lib/moneta/adapters/tokyocabinet.rb +41 -0
  39. data/lib/moneta/adapters/yaml.rb +15 -0
  40. data/lib/moneta/base.rb +78 -0
  41. data/lib/moneta/builder.rb +39 -0
  42. data/lib/moneta/cache.rb +84 -0
  43. data/lib/moneta/expires.rb +71 -0
  44. data/lib/moneta/lock.rb +25 -0
  45. data/lib/moneta/logger.rb +61 -0
  46. data/lib/moneta/mixins.rb +65 -0
  47. data/lib/moneta/net.rb +18 -0
  48. data/lib/moneta/optionmerger.rb +39 -0
  49. data/lib/moneta/proxy.rb +86 -0
  50. data/lib/moneta/server.rb +81 -0
  51. data/lib/moneta/shared.rb +60 -0
  52. data/lib/moneta/stack.rb +78 -0
  53. data/lib/moneta/transformer.rb +159 -0
  54. data/lib/moneta/transformer/config.rb +42 -0
  55. data/lib/moneta/transformer/helper.rb +37 -0
  56. data/lib/moneta/version.rb +5 -0
  57. data/lib/moneta/wrapper.rb +33 -0
  58. data/lib/rack/cache/moneta.rb +93 -0
  59. data/lib/rack/moneta_cookies.rb +64 -0
  60. data/lib/rack/session/moneta.rb +63 -0
  61. data/moneta.gemspec +19 -0
  62. data/spec/action_dispatch/fixtures/session_autoload_test/foo.rb +10 -0
  63. data/spec/action_dispatch/session_moneta_store_spec.rb +196 -0
  64. data/spec/active_support/cache_moneta_store_spec.rb +197 -0
  65. data/spec/generate.rb +1489 -0
  66. data/spec/helper.rb +91 -0
  67. data/spec/moneta/adapter_activerecord_spec.rb +32 -0
  68. data/spec/moneta/adapter_cassandra_spec.rb +30 -0
  69. data/spec/moneta/adapter_client_spec.rb +19 -0
  70. data/spec/moneta/adapter_cookie_spec.rb +18 -0
  71. data/spec/moneta/adapter_couch_spec.rb +18 -0
  72. data/spec/moneta/adapter_datamapper_spec.rb +49 -0
  73. data/spec/moneta/adapter_dbm_spec.rb +18 -0
  74. data/spec/moneta/adapter_file_spec.rb +18 -0
  75. data/spec/moneta/adapter_fog_spec.rb +23 -0
  76. data/spec/moneta/adapter_gdbm_spec.rb +18 -0
  77. data/spec/moneta/adapter_hbase_spec.rb +18 -0
  78. data/spec/moneta/adapter_leveldb_spec.rb +18 -0
  79. data/spec/moneta/adapter_localmemcache_spec.rb +18 -0
  80. data/spec/moneta/adapter_lruhash_spec.rb +31 -0
  81. data/spec/moneta/adapter_memcached_dalli_spec.rb +30 -0
  82. data/spec/moneta/adapter_memcached_native_spec.rb +31 -0
  83. data/spec/moneta/adapter_memcached_spec.rb +30 -0
  84. data/spec/moneta/adapter_memory_spec.rb +39 -0
  85. data/spec/moneta/adapter_mongo_spec.rb +18 -0
  86. data/spec/moneta/adapter_pstore_spec.rb +21 -0
  87. data/spec/moneta/adapter_redis_spec.rb +30 -0
  88. data/spec/moneta/adapter_riak_spec.rb +22 -0
  89. data/spec/moneta/adapter_sdbm_spec.rb +18 -0
  90. data/spec/moneta/adapter_sequel_spec.rb +18 -0
  91. data/spec/moneta/adapter_sqlite_spec.rb +18 -0
  92. data/spec/moneta/adapter_tokyocabinet_bdb_spec.rb +18 -0
  93. data/spec/moneta/adapter_tokyocabinet_hdb_spec.rb +18 -0
  94. data/spec/moneta/adapter_yaml_spec.rb +21 -0
  95. data/spec/moneta/cache_file_memory_spec.rb +34 -0
  96. data/spec/moneta/cache_memory_null_spec.rb +23 -0
  97. data/spec/moneta/expires_file_spec.rb +76 -0
  98. data/spec/moneta/expires_memory_spec.rb +65 -0
  99. data/spec/moneta/lock_spec.rb +42 -0
  100. data/spec/moneta/null_adapter_spec.rb +26 -0
  101. data/spec/moneta/optionmerger_spec.rb +92 -0
  102. data/spec/moneta/proxy_expires_memory_spec.rb +55 -0
  103. data/spec/moneta/proxy_redis_spec.rb +23 -0
  104. data/spec/moneta/shared_spec.rb +30 -0
  105. data/spec/moneta/simple_activerecord_spec.rb +51 -0
  106. data/spec/moneta/simple_activerecord_with_expires_spec.rb +52 -0
  107. data/spec/moneta/simple_cassandra_spec.rb +52 -0
  108. data/spec/moneta/simple_client_tcp_spec.rb +67 -0
  109. data/spec/moneta/simple_client_unix_spec.rb +53 -0
  110. data/spec/moneta/simple_couch_spec.rb +51 -0
  111. data/spec/moneta/simple_couch_with_expires_spec.rb +52 -0
  112. data/spec/moneta/simple_datamapper_spec.rb +53 -0
  113. data/spec/moneta/simple_datamapper_with_expires_spec.rb +54 -0
  114. data/spec/moneta/simple_datamapper_with_repository_spec.rb +53 -0
  115. data/spec/moneta/simple_dbm_spec.rb +51 -0
  116. data/spec/moneta/simple_dbm_with_expires_spec.rb +52 -0
  117. data/spec/moneta/simple_file_spec.rb +51 -0
  118. data/spec/moneta/simple_file_with_expires_spec.rb +52 -0
  119. data/spec/moneta/simple_fog_spec.rb +56 -0
  120. data/spec/moneta/simple_fog_with_expires_spec.rb +58 -0
  121. data/spec/moneta/simple_gdbm_spec.rb +51 -0
  122. data/spec/moneta/simple_gdbm_with_expires_spec.rb +52 -0
  123. data/spec/moneta/simple_hashfile_spec.rb +51 -0
  124. data/spec/moneta/simple_hashfile_with_expires_spec.rb +52 -0
  125. data/spec/moneta/simple_hbase_spec.rb +51 -0
  126. data/spec/moneta/simple_hbase_with_expires_spec.rb +52 -0
  127. data/spec/moneta/simple_leveldb_spec.rb +51 -0
  128. data/spec/moneta/simple_leveldb_with_expires_spec.rb +52 -0
  129. data/spec/moneta/simple_localmemcache_spec.rb +51 -0
  130. data/spec/moneta/simple_localmemcache_with_expires_spec.rb +52 -0
  131. data/spec/moneta/simple_lruhash_spec.rb +51 -0
  132. data/spec/moneta/simple_lruhash_with_expires_spec.rb +52 -0
  133. data/spec/moneta/simple_memcached_dalli_spec.rb +52 -0
  134. data/spec/moneta/simple_memcached_native_spec.rb +52 -0
  135. data/spec/moneta/simple_memcached_spec.rb +52 -0
  136. data/spec/moneta/simple_memory_spec.rb +51 -0
  137. data/spec/moneta/simple_memory_with_compress_spec.rb +51 -0
  138. data/spec/moneta/simple_memory_with_expires_spec.rb +52 -0
  139. data/spec/moneta/simple_memory_with_json_key_serializer_spec.rb +37 -0
  140. data/spec/moneta/simple_memory_with_json_serializer_spec.rb +28 -0
  141. data/spec/moneta/simple_memory_with_json_value_serializer_spec.rb +35 -0
  142. data/spec/moneta/simple_memory_with_prefix_spec.rb +51 -0
  143. data/spec/moneta/simple_memory_with_snappy_compress_spec.rb +51 -0
  144. data/spec/moneta/simple_mongo_spec.rb +51 -0
  145. data/spec/moneta/simple_mongo_with_expires_spec.rb +52 -0
  146. data/spec/moneta/simple_null_spec.rb +36 -0
  147. data/spec/moneta/simple_pstore_spec.rb +51 -0
  148. data/spec/moneta/simple_pstore_with_expires_spec.rb +52 -0
  149. data/spec/moneta/simple_redis_spec.rb +52 -0
  150. data/spec/moneta/simple_riak_spec.rb +55 -0
  151. data/spec/moneta/simple_riak_with_expires_spec.rb +56 -0
  152. data/spec/moneta/simple_sdbm_spec.rb +51 -0
  153. data/spec/moneta/simple_sdbm_with_expires_spec.rb +52 -0
  154. data/spec/moneta/simple_sequel_spec.rb +51 -0
  155. data/spec/moneta/simple_sequel_with_expires_spec.rb +52 -0
  156. data/spec/moneta/simple_sqlite_spec.rb +51 -0
  157. data/spec/moneta/simple_sqlite_with_expires_spec.rb +52 -0
  158. data/spec/moneta/simple_tokyocabinet_spec.rb +51 -0
  159. data/spec/moneta/simple_tokyocabinet_with_expires_spec.rb +52 -0
  160. data/spec/moneta/simple_yaml_spec.rb +50 -0
  161. data/spec/moneta/simple_yaml_with_expires_spec.rb +51 -0
  162. data/spec/moneta/stack_file_memory_spec.rb +25 -0
  163. data/spec/moneta/stack_memory_file_spec.rb +24 -0
  164. data/spec/moneta/transformer_bencode_spec.rb +30 -0
  165. data/spec/moneta/transformer_bert_spec.rb +30 -0
  166. data/spec/moneta/transformer_bson_spec.rb +30 -0
  167. data/spec/moneta/transformer_bzip2_spec.rb +27 -0
  168. data/spec/moneta/transformer_json_spec.rb +30 -0
  169. data/spec/moneta/transformer_lzma_spec.rb +27 -0
  170. data/spec/moneta/transformer_lzo_spec.rb +27 -0
  171. data/spec/moneta/transformer_marshal_base64_spec.rb +54 -0
  172. data/spec/moneta/transformer_marshal_escape_spec.rb +54 -0
  173. data/spec/moneta/transformer_marshal_hmac_spec.rb +54 -0
  174. data/spec/moneta/transformer_marshal_md5_spec.rb +54 -0
  175. data/spec/moneta/transformer_marshal_md5_spread_spec.rb +54 -0
  176. data/spec/moneta/transformer_marshal_prefix_spec.rb +54 -0
  177. data/spec/moneta/transformer_marshal_rmd160_spec.rb +54 -0
  178. data/spec/moneta/transformer_marshal_sha1_spec.rb +54 -0
  179. data/spec/moneta/transformer_marshal_sha256_spec.rb +54 -0
  180. data/spec/moneta/transformer_marshal_sha384_spec.rb +54 -0
  181. data/spec/moneta/transformer_marshal_sha512_spec.rb +54 -0
  182. data/spec/moneta/transformer_marshal_truncate_spec.rb +54 -0
  183. data/spec/moneta/transformer_marshal_uuencode_spec.rb +54 -0
  184. data/spec/moneta/transformer_msgpack_spec.rb +30 -0
  185. data/spec/moneta/transformer_ox_spec.rb +51 -0
  186. data/spec/moneta/transformer_quicklz_spec.rb +27 -0
  187. data/spec/moneta/transformer_snappy_spec.rb +27 -0
  188. data/spec/moneta/transformer_tnet_spec.rb +30 -0
  189. data/spec/moneta/transformer_yaml_spec.rb +51 -0
  190. data/spec/moneta/transformer_zlib_spec.rb +27 -0
  191. data/spec/monetaspecs.rb +2663 -0
  192. data/spec/rack/cache_moneta_spec.rb +355 -0
  193. data/spec/rack/moneta_cookies_spec.rb +81 -0
  194. data/spec/rack/session_moneta_spec.rb +305 -0
  195. metadata +359 -56
  196. data/README +0 -51
  197. data/TODO +0 -4
  198. data/lib/moneta/basic_file.rb +0 -111
  199. data/lib/moneta/berkeley.rb +0 -53
  200. data/lib/moneta/couch.rb +0 -63
  201. data/lib/moneta/datamapper.rb +0 -117
  202. data/lib/moneta/file.rb +0 -91
  203. data/lib/moneta/lmc.rb +0 -52
  204. data/lib/moneta/memcache.rb +0 -52
  205. data/lib/moneta/memory.rb +0 -11
  206. data/lib/moneta/mongodb.rb +0 -58
  207. data/lib/moneta/redis.rb +0 -49
  208. data/lib/moneta/rufus.rb +0 -41
  209. data/lib/moneta/s3.rb +0 -162
  210. data/lib/moneta/sdbm.rb +0 -33
  211. data/lib/moneta/tyrant.rb +0 -58
  212. data/lib/moneta/xattr.rb +0 -58
@@ -0,0 +1,18 @@
1
+ module Moneta
2
+ # @api private
3
+ module Net
4
+ DEFAULT_PORT = 9000
5
+
6
+ class Error < Exception; end
7
+
8
+ def read(io)
9
+ size = io.read(4).unpack('N').first
10
+ Marshal.load(io.read(size))
11
+ end
12
+
13
+ def write(io, o)
14
+ s = Marshal.dump(o)
15
+ io.write([s.bytesize].pack('N') << s)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,39 @@
1
+ module Moneta
2
+ # @api private
3
+ class OptionMerger < Wrapper
4
+ METHODS = [:key?, :load, :store, :delete, :increment, :clear].freeze
5
+
6
+ attr_reader :default_options
7
+
8
+ def initialize(adapter, options = {})
9
+ super(adapter, options)
10
+
11
+ @default_options = adapter.respond_to?(:default_options) ? adapter.default_options.dup : {}
12
+
13
+ if options.include?(:only)
14
+ raise ArgumentError, 'Either :only or :except is allowed' if options.include?(:except)
15
+ methods = [options.delete(:only)].compact.flatten
16
+ elsif options.include?(:except)
17
+ methods = METHODS - [options.delete(:except)].compact.flatten
18
+ else
19
+ methods = METHODS
20
+ end
21
+
22
+ methods.each do |method|
23
+ if oldopts = @default_options[method]
24
+ newopts = (@default_options[method] = oldopts.merge(options))
25
+ newopts[:prefix] = "#{oldopts[:prefix]}#{options[:prefix]}" if oldopts[:prefix] || options[:prefix]
26
+ else
27
+ @default_options[method] = options
28
+ end
29
+ end
30
+ end
31
+
32
+ def wrap(method, *args)
33
+ options = args.last
34
+ options.merge!(@default_options[method]) if Hash === options && @default_options.include?(method)
35
+ yield
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,86 @@
1
+ module Moneta
2
+ # Proxy base class
3
+ # @api public
4
+ class Proxy < Base
5
+ attr_reader :adapter
6
+
7
+ # Constructor
8
+ #
9
+ # @param [Moneta store] underlying adapter
10
+ # @param [Hash] options
11
+ def initialize(adapter, options = {})
12
+ @adapter = adapter
13
+ end
14
+
15
+ # Exists the value with key
16
+ #
17
+ # @param [Object] key
18
+ # @return [Boolean]
19
+ # @param [Hash] options
20
+ # @api public
21
+ def key?(key, options = {})
22
+ @adapter.key?(key, options)
23
+ end
24
+
25
+ # Fetch value with key. Return nil if the key doesn't exist
26
+ #
27
+ # @param [Object] key
28
+ # @param [Hash] options
29
+ # @return [Object] value
30
+ # @api public
31
+ def load(key, options = {})
32
+ @adapter.load(key, options)
33
+ end
34
+
35
+ # Store value with key
36
+ #
37
+ # @param [Object] key
38
+ # @param [Object] value
39
+ # @param [Hash] options
40
+ # @return value
41
+ # @api public
42
+ def store(key, value, options = {})
43
+ @adapter.store(key, value, options)
44
+ end
45
+
46
+ # Delete the key from the store and return the current value
47
+ #
48
+ # @param [Object] key
49
+ # @return [Object] current value
50
+ # @param [Hash] options
51
+ # @api public
52
+ def delete(key, options = {})
53
+ @adapter.delete(key, options)
54
+ end
55
+
56
+ # Atomically increment integer value with key
57
+ #
58
+ # Not every Moneta store implements this method,
59
+ # a NotImplementedError if it is not supported.
60
+ #
61
+ # @param [Object] key
62
+ # @param [Integer] amount
63
+ # @param [Hash] options
64
+ # @return [Object] value from store
65
+ # @api public
66
+ def increment(key, amount = 1, options = {})
67
+ @adapter.increment(key, amount, options)
68
+ end
69
+
70
+ # Clear all keys in this store
71
+ #
72
+ # @param [Hash] options
73
+ # @return [void]
74
+ # @api public
75
+ def clear(options = {})
76
+ @adapter.clear(options)
77
+ self
78
+ end
79
+
80
+ # Close this store
81
+ # @api public
82
+ def close
83
+ @adapter.close
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,81 @@
1
+ require 'socket'
2
+
3
+ module Moneta
4
+ # Moneta server
5
+ # @api public
6
+ class Server
7
+ TIMEOUT = 1
8
+ include Net
9
+
10
+ # Constructor
11
+ #
12
+ # @param [Hash] options
13
+ #
14
+ # Options:
15
+ # * :port - TCP port (default 9000)
16
+ # * :file - Unix socket file name (default none)
17
+ def initialize(store, options = {})
18
+ @store = store
19
+ @server = options[:file] ? UNIXServer.open(options[:file]) :
20
+ TCPServer.open(options[:port] || DEFAULT_PORT)
21
+ @clients = [@server]
22
+ @running = true
23
+ @thread = Thread.new do
24
+ mainloop while @running
25
+ File.unlink(options[:file]) if options[:file]
26
+ end
27
+ end
28
+
29
+ def stop
30
+ if @thread
31
+ @running = false
32
+ @server.close
33
+ @server = nil
34
+ @thread.join
35
+ @thread = nil
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def mainloop
42
+ client = accept
43
+ handle(client) if client
44
+ rescue Exception => ex
45
+ puts "#{ex.message}\n#{ex.backtrace.join("\n")}"
46
+ write(client, Error.new(ex.message)) if client
47
+ end
48
+
49
+ def accept
50
+ ios = IO.select(@clients, nil, @clients, TIMEOUT)
51
+ return nil unless ios
52
+ ios[2].each do |io|
53
+ io.close
54
+ @clients.delete(io)
55
+ end
56
+ ios[0].each do |io|
57
+ if io == @server
58
+ client = @server.accept
59
+ @clients << client if client
60
+ else
61
+ return io unless io.eof?
62
+ @clients.delete(io)
63
+ end
64
+ end
65
+ nil
66
+ end
67
+
68
+ def handle(client)
69
+ method, *args = read(client)
70
+ case method
71
+ when :key?, :load, :delete, :increment
72
+ write(client, @store.send(method, *args))
73
+ when :store, :clear
74
+ @store.send(method, *args)
75
+ write(client, nil)
76
+ else
77
+ raise 'Invalid method call'
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,60 @@
1
+ module Moneta
2
+ # Shares a store between processes
3
+ #
4
+ # @example Share a store
5
+ # Moneta.build do
6
+ # use :Transformer, :key => :marshal, :value => :marshal
7
+ # use :Shared do
8
+ # adapter :GDBM, :file => 'shared.db'
9
+ # end
10
+ # end
11
+ #
12
+ # @api public
13
+ class Shared < Wrapper
14
+ # Constructor
15
+ #
16
+ # @param [Hash] options
17
+ #
18
+ # Options:
19
+ # * :port - TCP port (default 9000)
20
+ # * :host - Hostname (default empty)
21
+ # * :file - Unix socket file name (default none)
22
+ def initialize(options = {}, &block)
23
+ @options = options
24
+ @builder = Builder.new(&block)
25
+ end
26
+
27
+ def close
28
+ if @server
29
+ @server.stop
30
+ @server = nil
31
+ end
32
+ if @adapter
33
+ @adapter.close
34
+ @adapter = nil
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def wrap(*args)
41
+ tries ||= 0
42
+ @adapter ||= Adapters::Client.new(@options)
43
+ yield
44
+ rescue Exception => ex
45
+ puts "Failed to connect: #{ex.message}"
46
+ begin
47
+ @adapter = Lock.new(@builder.build.last)
48
+ @server = Server.new(@adapter, @options)
49
+ rescue Exception => ex
50
+ puts "Failed to start server: #{ex.message}"
51
+ @adapter = nil
52
+ end
53
+ if (tries += 1) > 2
54
+ raise
55
+ else
56
+ retry
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,78 @@
1
+ module Moneta
2
+ # Combines multiple stores. Reads return the result from the first store,
3
+ # writes go to all stores.
4
+ #
5
+ # @example Add stack to chain
6
+ # Moneta.build do
7
+ # use(:Stack) do
8
+ # add { adapter :Redis }
9
+ # add { adapter :File, :dir => 'data' }
10
+ # add { adapter :File, :dir => 'replicate' }
11
+ # end
12
+ # end
13
+ #
14
+ # @api public
15
+ class Stack < Base
16
+ # @api private
17
+ class DSL
18
+ attr_reader :stack
19
+
20
+ def initialize(options, &block)
21
+ @stack = options[:stack].to_a
22
+ instance_eval(&block)
23
+ end
24
+
25
+ def add(store = nil, &block)
26
+ raise ArgumentError, 'Only argument or block allowed' if store && block
27
+ @stack << (store || Moneta.build(&block))
28
+ nil
29
+ end
30
+ end
31
+
32
+ attr_reader :stack
33
+
34
+ def initialize(options = {}, &block)
35
+ @stack = DSL.new(options, &block).stack
36
+ end
37
+
38
+ def key?(key, options = {})
39
+ @stack.any? {|s| s.key?(key) }
40
+ end
41
+
42
+ def load(key, options = {})
43
+ @stack.each do |s|
44
+ value = s.load(key, options)
45
+ return value if value
46
+ end
47
+ nil
48
+ end
49
+
50
+ def store(key, value, options = {})
51
+ @stack.each {|s| s.store(key, value, options) }
52
+ value
53
+ end
54
+
55
+ def delete(key, options = {})
56
+ @stack.inject(nil) do |value, s|
57
+ v = s.delete(key, options)
58
+ value || v
59
+ end
60
+ end
61
+
62
+ def increment(key, amount = 1, options = {})
63
+ last = nil
64
+ @stack.each {|s| last = s.increment(key, amount, options) }
65
+ last
66
+ end
67
+
68
+ def clear(options = {})
69
+ @stack.each {|s| s.clear }
70
+ self
71
+ end
72
+
73
+ def close
74
+ @stack.each {|s| s.close }
75
+ nil
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,159 @@
1
+ module Moneta
2
+ # Transforms keys and values (Marshal, YAML, JSON, Base64, MD5, ...).
3
+ # You can bypass the transformer (e.g. serialization) by using the `:raw` option.
4
+ #
5
+ # @example Add transformer to chain
6
+ # Moneta.build do
7
+ # transformer :key => [:marshal, :escape], :value => [:marshal]
8
+ # adapter :File, :dir => 'data'
9
+ # end
10
+ #
11
+ # @example Bypass serialization
12
+ # store.store('key', 'value', :raw => true)
13
+ # store['key'] => Error
14
+ # store.load('key', :raw => true) => 'value'
15
+ #
16
+ # store['key'] = 'value'
17
+ # store.load('key', :raw => true) => "\x04\bI\"\nvalue\x06:\x06ET"
18
+ #
19
+ # @api public
20
+ class Transformer < Proxy
21
+ class << self
22
+ alias_method :original_new, :new
23
+
24
+ # Constructor
25
+ #
26
+ # @param [Moneta store] adapter The underlying store
27
+ # @param [Hash] options
28
+ #
29
+ # Options:
30
+ # * :key - List of key transformers in the order in which they should be applied
31
+ # * :value - List of value transformers in the order in which they should be applied
32
+ # * :prefix - Prefix string for key namespacing (Used by the :prefix key transformer)
33
+ # * :secret - HMAC secret to verify values (Used by the :hmac value transformer)
34
+ # * :maxlen - Maximum key length (Used by the :truncate key transformer)
35
+ # * :quiet - Disable error message
36
+ def new(adapter, options = {})
37
+ keys = [options[:key]].flatten.compact
38
+ values = [options[:value]].flatten.compact
39
+ raise ArgumentError, 'Option :key or :value is required' if keys.empty? && values.empty?
40
+ options[:prefix] ||= '' if keys.include?(:prefix)
41
+ name = class_name(options[:quiet] ? 'Quiet' : '', keys, values)
42
+ const_set(name, compile(options, keys, values)) unless const_defined?(name)
43
+ const_get(name).original_new(adapter, options)
44
+ end
45
+
46
+ private
47
+
48
+ def compile(options, keys, values)
49
+ raise ArgumentError, 'Invalid key transformer chain' if KEY_TRANSFORMER !~ keys.map(&:inspect).join
50
+ raise ArgumentError, 'Invalid value transformer chain' if VALUE_TRANSFORMER !~ values.map(&:inspect).join
51
+
52
+ key = compile_transformer(keys, 'key')
53
+
54
+ klass = Class.new(self)
55
+ klass.class_eval <<-end_eval, __FILE__, __LINE__
56
+ def initialize(adapter, options = {})
57
+ super
58
+ #{compile_initializer('key', keys)}
59
+ #{compile_initializer('value', values)}
60
+ end
61
+ def key?(key, options = {})
62
+ @adapter.key?(#{key}, options)
63
+ end
64
+ def increment(key, amount = 1, options = {})
65
+ @adapter.increment(#{key}, amount, options)
66
+ end
67
+ end_eval
68
+
69
+ if values.empty?
70
+ klass.class_eval <<-end_eval, __FILE__, __LINE__
71
+ def load(key, options = {})
72
+ options.delete(:raw)
73
+ @adapter.load(#{key}, options)
74
+ end
75
+ def store(key, value, options = {})
76
+ options.delete(:raw)
77
+ @adapter.store(#{key}, value, options)
78
+ end
79
+ def delete(key, options = {})
80
+ options.delete(:raw)
81
+ @adapter.delete(#{key}, options)
82
+ end
83
+ end_eval
84
+ else
85
+ dump = compile_transformer(values, 'value')
86
+ load = compile_transformer(values.reverse, 'value', 1)
87
+
88
+ klass.class_eval <<-end_eval, __FILE__, __LINE__
89
+ def load(key, options = {})
90
+ raw = options.delete(:raw)
91
+ value = @adapter.load(#{key}, options)
92
+ begin
93
+ return #{load} if value && !raw
94
+ rescue Exception => ex
95
+ #{options[:quiet] ? '' : 'puts "Tried to load invalid value: #{ex.message}"'}
96
+ end
97
+ value
98
+ end
99
+ def store(key, value, options = {})
100
+ raw = options.delete(:raw)
101
+ @adapter.store(#{key}, raw ? value : #{dump}, options)
102
+ value
103
+ end
104
+ def delete(key, options = {})
105
+ raw = options.delete(:raw)
106
+ value = @adapter.delete(#{key}, options)
107
+ begin
108
+ return #{load} if value && !raw
109
+ rescue Exception => ex
110
+ #{options[:quiet] ? '' : 'puts "Tried to delete invalid value: #{ex.message}"'}
111
+ end
112
+ value
113
+ end
114
+ end_eval
115
+ end
116
+ klass
117
+ end
118
+
119
+ # Compile option initializer
120
+ def compile_initializer(type, transformers)
121
+ transformers.map do |name|
122
+ t = TRANSFORMER[name]
123
+ (t[1].to_s + t[2].to_s).scan(/@\w+/).uniq.map do |opt|
124
+ "raise ArgumentError, \"Option #{opt[1..-1]} is required for #{name} #{type} transformer\" unless #{opt} = options[:#{opt[1..-1]}]\n"
125
+ end
126
+ end.join("\n")
127
+ end
128
+
129
+ # Compile transformer validator regular expression
130
+ def compile_validator(s)
131
+ Regexp.new(s.gsub(/\w+/) do
132
+ '(' + TRANSFORMER.select {|k,v| v.first.to_s == $& }.map {|v| ":#{v.first}" }.join('|') + ')'
133
+ end.gsub(/\s+/, '').sub(/\A/, '\A').sub(/\Z/, '\Z'))
134
+ end
135
+
136
+ # Returned compiled transformer code string
137
+ def compile_transformer(transformer, var, i = 2)
138
+ transformer.inject(var) do |value, name|
139
+ raise ArgumentError, "Unknown transformer #{name}" unless t = TRANSFORMER[name]
140
+ require t[3] if t[3]
141
+ code = t[i]
142
+ if t[0] == :serialize && var == 'key'
143
+ "(tmp = #{value}; String === tmp ? tmp : #{code.gsub('value', 'tmp')})"
144
+ else
145
+ code.gsub('value', value)
146
+ end
147
+ end
148
+ end
149
+
150
+ def class_name(prefix, keys, values)
151
+ prefix << (keys.empty? ? '' : keys.map(&:to_s).map(&:capitalize).join << 'Key') <<
152
+ (values.empty? ? '' : values.map(&:to_s).map(&:capitalize).join << 'Value')
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ require 'moneta/transformer/helper'
159
+ require 'moneta/transformer/config'