moneta 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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'