horcrux 0.0.1

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.
data/LICENSE.md ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011-* rick olson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # Horcrux
2
+
3
+ A Horcrux is a powerful object in which a Dark wizard or witch has hidden a
4
+ fragment of his or her soul for the purpose of attaining immortality.
5
+
6
+ The Horcrux ruby gem is an abstract key/value store adapter library.
7
+
8
+ Horcrux adapters are shims around key/value systems. They need to define at
9
+ least these three methods:
10
+
11
+ ```ruby
12
+ def get(key)
13
+ client[key]
14
+ end
15
+
16
+ def set(key, value)
17
+ client[key] = value
18
+ true
19
+ end
20
+
21
+ def delete(key)
22
+ client.delete(key) ? true : false
23
+ end
24
+ ```
25
+
26
+ See Horcrux::Memory for a simple example.
27
+
28
+ They should also include the Horcrux::Methods module. If the underlying
29
+ key/value system can perform some operations more efficiently, they can
30
+ be overridden:
31
+
32
+ ```ruby
33
+ # using a redis client
34
+ def set_all(*keys)
35
+ args = keys.to_a
36
+ args.flatten!
37
+ client.mset *args
38
+ Array.new(keys.size, true) # redis set always succeeds
39
+ end
40
+ ```
41
+
42
+ Adapters can also choose a Serializer object. A Serializer is any object that
43
+ responds to #dump and #load. This means Marshal and Yajl can be passed in
44
+ directly. Here's what a custom MessagePack serializer might look like:
45
+
46
+ ```ruby
47
+ module MessagePackSerializer
48
+ def self.pack(value)
49
+ value.to_msgpack
50
+ end
51
+
52
+ def self.unpack(str)
53
+ MessagePack.unpack(str)
54
+ end
55
+ end
56
+ ```
57
+
58
+ You can then pass this in while creating your Horcrux adapter:
59
+
60
+ ```ruby
61
+ @adapter = Horcrux::Memory.new({}, MessagePackSerializer)
62
+ ```
63
+
64
+ ## ToyStore Adapter
65
+
66
+ A lot of these ideas came from [the Adapter gem][adapter]. It ties into a rad
67
+ [Toystore ORM][toystore]. Check them out... if they work for you, use them!
68
+
69
+ Horcrux differs in a few areas:
70
+
71
+ * Focus on batch get/set/delete operations.
72
+ * Doesn't mimic the Hash API.
73
+ * Serializers are a separate object. BYOS.
74
+ * Boring test/unit tests.
75
+ * Ruby 1.8.7 and Ruby 1.9.x compatibility.
76
+
77
+ [adapter]: https://github.com/newtoy/adapter
78
+ [toystore]: https://github.com/newtoy/toystore
79
+
80
+ ## Note on Patches/Pull Requests
81
+
82
+ 1. Fork the project.
83
+ 2. Make your feature addition or bug fix.
84
+ 3. Add tests for it. This is important so I don't break it in a future version
85
+ unintentionally.
86
+ 4. Commit, do not mess with rakefile, version, or history. (if you want to have
87
+ your own version, that is fine but bump version in a commit by itself I can
88
+ ignore when I pull)
89
+ 5. Send me a pull request. Bonus points for topic branches.
90
+
data/Rakefile ADDED
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'date'
4
+
5
+ #############################################################################
6
+ #
7
+ # Helper functions
8
+ #
9
+ #############################################################################
10
+
11
+ def name
12
+ @name ||= Dir['*.gemspec'].first.split('.').first
13
+ end
14
+
15
+ def version
16
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
17
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
18
+ end
19
+
20
+ def date
21
+ Date.today.to_s
22
+ end
23
+
24
+ def rubyforge_project
25
+ name
26
+ end
27
+
28
+ def gemspec_file
29
+ "#{name}.gemspec"
30
+ end
31
+
32
+ def gem_file
33
+ "#{name}-#{version}.gem"
34
+ end
35
+
36
+ def replace_header(head, header_name)
37
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
38
+ end
39
+
40
+ #############################################################################
41
+ #
42
+ # Standard tasks
43
+ #
44
+ #############################################################################
45
+
46
+ task :default => :test
47
+
48
+ require 'rake/testtask'
49
+ Rake::TestTask.new(:test) do |test|
50
+ test.libs << 'lib' << 'test'
51
+ test.pattern = 'test/**/*_test.rb'
52
+ test.verbose = true
53
+ end
54
+
55
+ desc "Open an irb session preloaded with this library"
56
+ task :console do
57
+ sh "irb -rubygems -r ./lib/#{name}.rb"
58
+ end
59
+
60
+ #############################################################################
61
+ #
62
+ # Custom tasks (add your own tasks here)
63
+ #
64
+ #############################################################################
65
+
66
+
67
+
68
+ #############################################################################
69
+ #
70
+ # Packaging tasks
71
+ #
72
+ #############################################################################
73
+
74
+ desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
75
+ task :release => :build do
76
+ unless `git branch` =~ /^\* master$/
77
+ puts "You must be on the master branch to release!"
78
+ exit!
79
+ end
80
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
81
+ sh "git tag v#{version}"
82
+ sh "git push origin master"
83
+ sh "git push origin v#{version}"
84
+ sh "gem push pkg/#{gem_file}"
85
+ end
86
+
87
+ desc "Build #{gem_file} into the pkg directory"
88
+ task :build => :gemspec do
89
+ sh "mkdir -p pkg"
90
+ sh "gem build #{gemspec_file}"
91
+ sh "mv #{gem_file} pkg"
92
+ end
93
+
94
+ desc "Generate #{gemspec_file}"
95
+ task :gemspec => :validate do
96
+ # read spec file and split out manifest section
97
+ spec = File.read(gemspec_file)
98
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
99
+
100
+ # replace name version and date
101
+ replace_header(head, :name)
102
+ replace_header(head, :version)
103
+ replace_header(head, :date)
104
+ #comment this out if your rubyforge_project has a different name
105
+ replace_header(head, :rubyforge_project)
106
+
107
+ # determine file list from git ls-files
108
+ files = `git ls-files`.
109
+ split("\n").
110
+ sort.
111
+ reject { |file| file =~ /^\./ }.
112
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
113
+ map { |file| " #{file}" }.
114
+ join("\n")
115
+
116
+ # piece file back together and write
117
+ manifest = " s.files = %w[\n#{files}\n ]\n"
118
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
119
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
120
+ puts "Updated #{gemspec_file}"
121
+ end
122
+
123
+ desc "Validate #{gemspec_file}"
124
+ task :validate do
125
+ libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
126
+ unless libfiles.empty?
127
+ puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
128
+ exit!
129
+ end
130
+ unless Dir['VERSION*'].empty?
131
+ puts "A `VERSION` file at root level violates Gem best practices."
132
+ exit!
133
+ end
134
+ end
data/horcrux.gemspec ADDED
@@ -0,0 +1,55 @@
1
+ ## This is the rakegem gemspec template. Make sure you read and understand
2
+ ## all of the comments. Some sections require modification, and others can
3
+ ## be deleted if you don't need them. Once you understand the contents of
4
+ ## this file, feel free to delete any comments that begin with two hash marks.
5
+ ## You can find comprehensive Gem::Specification documentation, at
6
+ ## http://docs.rubygems.org/read/chapter/20
7
+ Gem::Specification.new do |s|
8
+ s.specification_version = 2 if s.respond_to? :specification_version=
9
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.3.5") if s.respond_to? :required_rubygems_version=
10
+
11
+ ## Leave these as is they will be modified for you by the rake gemspec task.
12
+ ## If your rubyforge_project name is different, then edit it and comment out
13
+ ## the sub! line in the Rakefile
14
+ s.name = 'horcrux'
15
+ s.version = '0.0.1'
16
+ s.date = '2012-01-01'
17
+ s.rubyforge_project = 'horcrux'
18
+
19
+ ## Make sure your summary is short. The description may be as long
20
+ ## as you like.
21
+ s.summary = "Simple key/value adapter library."
22
+ s.description = "Simple key/value adapter library."
23
+
24
+ ## List the primary authors. If there are a bunch of authors, it's probably
25
+ ## better to set the email to an email list or something. If you don't have
26
+ ## a custom homepage, consider using your GitHub URL or the like.
27
+ s.authors = ["Rick Olson"]
28
+ s.email = 'technoweenie@gmail.com'
29
+ s.homepage = 'http://github.com/technoweenie/horcrux'
30
+
31
+ ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
32
+ ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
33
+ s.require_paths = %w[lib]
34
+
35
+ s.add_development_dependency 'rake'
36
+ s.add_development_dependency 'test-unit'
37
+
38
+ ## Leave this section as-is. It will be automatically generated from the
39
+ ## contents of your Git repository via the gemspec task. DO NOT REMOVE
40
+ ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
41
+ # = MANIFEST =
42
+ s.files = %w[
43
+ LICENSE.md
44
+ README.md
45
+ Rakefile
46
+ horcrux.gemspec
47
+ lib/horcrux.rb
48
+ test/memory_test.rb
49
+ ]
50
+ # = MANIFEST =
51
+
52
+ ## Test files will be grabbed from the file list. Make sure the path glob
53
+ ## matches what you actually use.
54
+ s.test_files = s.files.select { |path| path =~ %r{^test/*/.+\.rb} }
55
+ end
data/lib/horcrux.rb ADDED
@@ -0,0 +1,175 @@
1
+ # See the README.md
2
+ module Horcrux
3
+ VERSION = "0.0.1"
4
+
5
+ # Implements the optional methods of a Horcrux adapter.
6
+ module Methods
7
+ def self.included(klass)
8
+ klass.send :attr_reader, :client, :serializer
9
+ end
10
+
11
+ # Public: Sets up an adapter with the client.
12
+ #
13
+ # client - This is the object that the adapter uses to store and
14
+ # retrieve data.
15
+ # serializer - An object that responds to #pack and #unpack for
16
+ # serializing and deserializing values. Default: a
17
+ # StringSerializer.
18
+ def initialize(client, serializer = nil)
19
+ @client = client
20
+ @serializer = serializer || StringSerializer
21
+ end
22
+
23
+ # Public: Gets all the values for the given keys.
24
+ #
25
+ # @adapter.get_all('a', 'b')
26
+ # # => ['1', '2']
27
+ #
28
+ # keys - One or more String keys.
29
+ #
30
+ # Returns an Array of unpacked values in the order of their associated
31
+ # keys.
32
+ def get_all(*keys)
33
+ keys.map { |k| get(k) }
34
+ end
35
+
36
+ # Public: Sets the given values.
37
+ #
38
+ # @adapter.set_all 'a' => '1', 'b' => '2'
39
+ # # => ['a', 'b']
40
+ #
41
+ # values - A Hash of String keys and Object values.
42
+ #
43
+ # Returns an Array of the successfully written String keys.
44
+ def set_all(values)
45
+ good_keys = []
46
+ values.each do |key, value|
47
+ good_keys << key if set(key, value)
48
+ end
49
+ good_keys
50
+ end
51
+
52
+ # Public: Deletes the given keys.
53
+ #
54
+ # @adapter.delete_all 'a', 'b'
55
+ # # => [true, false]
56
+ #
57
+ # keys - One or more String keys.
58
+ #
59
+ # Returns an Array of Booleans specifying whether the deletes were
60
+ # successful.
61
+ def delete_all(*keys)
62
+ keys.map { |k| delete(k) }
63
+ end
64
+
65
+ # Public: Determines if the key is set.
66
+ #
67
+ # key - A String key.
68
+ #
69
+ # Returns true if the key is set, or false.
70
+ def key?(key)
71
+ !get(key).nil?
72
+ end
73
+
74
+ # Public: Either gets the value of the key, or sets it if it doesn't exist.
75
+ #
76
+ # # if 'some-cache' is not set, call #slow_method
77
+ # @adapter.fetch('some-cache') { slow_method }
78
+ #
79
+ # key - A String key.
80
+ #
81
+ # Yields if the key does not exist. The key is set to the return value of
82
+ # the block.
83
+ # Returns the Object value.
84
+ def fetch(key)
85
+ get(key) || begin
86
+ value = yield
87
+ set(key, value)
88
+ value
89
+ end
90
+ end
91
+
92
+ # Public: Transforms the given application key to the internal key that
93
+ # the storage system uses.
94
+ #
95
+ # key - The String key.
96
+ #
97
+ # Returns the String internal key for the adapter.
98
+ def key_for(key)
99
+ key.to_s
100
+ end
101
+ end
102
+
103
+ # Passes values through Horcrux untouched.
104
+ module NullSerializer
105
+ def self.dump(value)
106
+ value
107
+ end
108
+
109
+ def self.load(str)
110
+ str
111
+ end
112
+ end
113
+
114
+ # Ensures that Horcrux values are turned to strings.
115
+ module StringSerializer
116
+ def self.dump(value)
117
+ value.to_s
118
+ end
119
+
120
+ def self.load(str)
121
+ str
122
+ end
123
+ end
124
+
125
+ class Memory
126
+ include Methods
127
+
128
+ # Sample Horcrux adapter that stores unmodified values in a ruby Hash.
129
+ #
130
+ # client - Optional Hash.
131
+ def initialize(client = {}, serializer = NullSerializer)
132
+ @client = client
133
+ @serializer = serializer
134
+ end
135
+
136
+ # Public: Uses Hash#key? to check the existence of a key.
137
+ #
138
+ # key - String key.
139
+ #
140
+ # Returns true if the key exists, or false.
141
+ def key?(key)
142
+ client.key? key_for(key)
143
+ end
144
+
145
+ # Public: Gets the value for the given key.
146
+ #
147
+ # key - The String key.
148
+ #
149
+ # Returns the Object value.
150
+ def get(key)
151
+ serializer.load client[key_for(key)]
152
+ end
153
+
154
+ # Public: Sets the value for the given key.
155
+ #
156
+ # key - The String key.
157
+ # value - The Object value.
158
+ #
159
+ # Returns true if the operation succeeded, or false.
160
+ def set(key, value)
161
+ client[key_for(key)] = serializer.dump(value)
162
+ true
163
+ end
164
+
165
+ # Public: Deletes the value for the given key.
166
+ #
167
+ # key - The String key.
168
+ #
169
+ # Returns true if a value was deleted, or false.
170
+ def delete(key)
171
+ !client.delete(key_for(key)).nil?
172
+ end
173
+ end
174
+ end
175
+
@@ -0,0 +1,64 @@
1
+ require 'test/unit'
2
+ require File.expand_path('../../lib/horcrux', __FILE__)
3
+
4
+ module Horcrux
5
+ class MemoryTest < Test::Unit::TestCase
6
+ def setup
7
+ @adapter = Memory.new({})
8
+ end
9
+
10
+ def test_reads_set_values
11
+ assert_nil @adapter.get('a')
12
+ assert_equal true, @adapter.set('a', '1')
13
+ assert_equal '1', @adapter.get('a')
14
+ end
15
+
16
+ def test_deletes_values
17
+ assert_equal true, @adapter.set('a', '1')
18
+ assert_equal '1', @adapter.get('a')
19
+ assert_equal true, @adapter.delete('a')
20
+ assert_equal false, @adapter.delete('a')
21
+ end
22
+
23
+ def test_fetch_sets_fallback
24
+ assert_nil @adapter.get 'a'
25
+ assert_equal '1', @adapter.fetch('a') { '1' }
26
+ assert_equal '1', @adapter.get('a')
27
+ assert_equal '1', @adapter.fetch('a') { '2' }
28
+ end
29
+
30
+ def test_checks_for_existence_of_key
31
+ assert !@adapter.key?('a')
32
+ assert_equal true, @adapter.set('a', '1')
33
+ assert @adapter.key?('a')
34
+ end
35
+
36
+ def test_multi_get
37
+ @adapter.set 'a', '1'
38
+ @adapter.set 'c', '3'
39
+ assert_equal ['1', nil, '3'], @adapter.get_all('a', 'b', 'c')
40
+ end
41
+
42
+ def test_multi_set
43
+ assert_nil @adapter.get('a')
44
+ assert_nil @adapter.get('b')
45
+
46
+ @adapter.set_all 'a' => '1', 'b' => '2'
47
+
48
+ assert_equal '1', @adapter.get('a')
49
+ assert_equal '2', @adapter.get('b')
50
+ end
51
+
52
+ def test_multi_delete
53
+ @adapter.set_all 'a' => '1', 'b' => '2'
54
+
55
+ assert_equal '1', @adapter.get('a')
56
+ assert_equal '2', @adapter.get('b')
57
+
58
+ assert_equal [true, true, false], @adapter.delete_all('a', 'b', 'c')
59
+
60
+ assert_nil @adapter.get('a')
61
+ assert_nil @adapter.get('b')
62
+ end
63
+ end
64
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: horcrux
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Rick Olson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: &70345353049180 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70345353049180
25
+ - !ruby/object:Gem::Dependency
26
+ name: test-unit
27
+ requirement: &70345353048680 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70345353048680
36
+ description: Simple key/value adapter library.
37
+ email: technoweenie@gmail.com
38
+ executables: []
39
+ extensions: []
40
+ extra_rdoc_files: []
41
+ files:
42
+ - LICENSE.md
43
+ - README.md
44
+ - Rakefile
45
+ - horcrux.gemspec
46
+ - lib/horcrux.rb
47
+ - test/memory_test.rb
48
+ homepage: http://github.com/technoweenie/horcrux
49
+ licenses: []
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: 1.3.5
66
+ requirements: []
67
+ rubyforge_project: horcrux
68
+ rubygems_version: 1.8.11
69
+ signing_key:
70
+ specification_version: 2
71
+ summary: Simple key/value adapter library.
72
+ test_files:
73
+ - test/memory_test.rb