canery 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Matthias Kalb, Railsmechanic
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,52 @@
1
+ # Canery
2
+ Canery is a simple and easy to use key/value store with several commands to store and retrieve data. As the backend uses [Sequel](https://github.com/jeremyevans/sequel/) for persistence, Canery can be used in virtually any environment where a SQL database is available. (For more information please check out the section with the available database adapters.)
3
+
4
+ ## Installation
5
+ gem install canery
6
+
7
+ ## Usage
8
+ Canery provides a simple and understandable API which is a bit inspired by Redis. So everyone with a little knowledge of Redis should be able to use Canery, too.
9
+
10
+ require 'canery'
11
+
12
+ # Create a Client interface with an in-memory SQLite database
13
+ client = Canery::Client.new
14
+
15
+ # Create a Client interface connecting to a PostgreSQL database
16
+ client = Canery::Client.new("postgres://username:password@host:port/database_name")
17
+
18
+ # Create a new namespace
19
+ store = client.tub("store")
20
+
21
+ # Set a simple value in this defined namespace
22
+ store.set("github", "github is awesome") # => "github"
23
+
24
+ # Get this previously set value
25
+ store.get("github") # => "github is awesome"
26
+
27
+ # Canery can also handle complex data types
28
+ store.set("demo_hash", {:first_name => "John", :last_name => "Doe"}) # => "demo_hash"
29
+
30
+ # Get this complex value
31
+ store.get("demo_hash") # => {:first_name => "John", :last_name => "Doe"}
32
+
33
+ For more information consider the wiki. (Comming soon... I promise!)
34
+
35
+
36
+ ## Information about data types
37
+ ### Keys & Tub names
38
+ Theoretically every string can be used as a key/tub name, from a string like "foo" to the content of a JPEG file. Even an empty string is a valid key. But for better performance, short keys should be your first choice.
39
+
40
+ Please keep in mind that Canery uses strings for all keys/tub names, so any other data type will be converted to a string!
41
+
42
+ ### Values
43
+ Canery makes use of Ruby's marshaling library for serializing the given values. So every data type which supports marshalling is compatible with Canery. If you want to add serialization support to your class (for example, if you want to serialize in some specific format), or if it contains objects that would otherwise not be serializable, you can implement your own serialization strategy. See [Ruby's Mashal Documentation](http://www.ruby-doc.org/core-1.9.3/Marshal.html) for more information.
44
+
45
+ ## License
46
+ Copyright (c) 2012 Matthias Kalb, Railsmechanic
47
+
48
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
49
+
50
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
51
+
52
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path('../lib/canery/version', __FILE__)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.authors = ["Railsmechanic"]
7
+ gem.email = ["info@railsmechanic.de"]
8
+ gem.description = %q{A simple but handy key/value store which is able to use a bunch of SQL databases as its backend.}
9
+ gem.summary = %q{Canery is a simple and easy to use key/value store with several commands to store and retrieve data. Because it uses a SQL database for storing the data, it can be used in many environments.}
10
+ gem.homepage = "https://github.com/railsmechanic/canery"
11
+
12
+ gem.files = `git ls-files`.split($\)
13
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.name = "canery"
16
+ gem.require_paths = ["lib"]
17
+ gem.version = Canery::VERSION
18
+
19
+ # dependencies
20
+ gem.add_dependency "sequel", "~> 3.38.0"
21
+
22
+ # development dependencies
23
+ gem.add_development_dependency "rspec", ">= 2.11.0"
24
+ gem.add_dependency "sqlite3-ruby", "~> 1.3.3"
25
+ end
@@ -0,0 +1,2 @@
1
+ require "canery/client"
2
+ require "canery/version"
@@ -0,0 +1,150 @@
1
+ # encoding: utf-8
2
+
3
+ require "sequel"
4
+ require "base64"
5
+
6
+ module Canery
7
+
8
+ class Backend
9
+
10
+ TABLE_PREFIX = "canery_"
11
+
12
+ def initialize(connection_uri)
13
+ raise ArgumentError, "connection_uri must be a String or nil" unless NilClass === connection_uri || String === connection_uri
14
+ @connection = connection_uri.nil? ? Sequel.sqlite : Sequel.connect(connection_uri)
15
+ @namespace_cache = {}
16
+ end
17
+
18
+ # Basic namespace methods #
19
+ ###########################
20
+
21
+ def create_namespace(name)
22
+ connection.create_table basic_tub_name(name) do
23
+ String :key, :primary_key => true
24
+ String :value, :text => true
25
+ end
26
+ end
27
+
28
+ def delete_namespace(name)
29
+ @namespace_cache.delete(basic_tub_name(name)) if @namespace_cache[basic_tub_name(name)]
30
+ connection.drop_table?(basic_tub_name(name)) && "OK"
31
+ end
32
+
33
+ def namespace(name)
34
+ @namespace_cache[basic_tub_name(name)] ||= connection.dataset.select(:key, :value).from(basic_tub_name(name))
35
+ end
36
+
37
+ def namespaces
38
+ connection.tables.select{ |name| name =~ /^#{TABLE_PREFIX}/ }.map{ |tub_name| tub_name.to_s[TABLE_PREFIX.length..-1] }
39
+ end
40
+
41
+ def namespace?(name)
42
+ connection.table_exists?(basic_tub_name(name))
43
+ end
44
+
45
+
46
+ # Methods for get, set, etc. #
47
+ ##############################
48
+
49
+ def get(name, key)
50
+ data = namespace(name).first(:key => build_key(key))
51
+ data.nil? ? nil : load(data[:value])
52
+ end
53
+
54
+ def mget(name, keys)
55
+ raise ArgumentError, "keys argument must be an Array of keys" unless Array === keys
56
+ keys.map!{ |key| build_key(key) }
57
+
58
+ # Sort data in the order they appear in the 'keys' Array
59
+ namespace(name).where(:key => keys).sort_by { |element| keys.index(element[:key]) }.map {|dataset| load(dataset[:value]) }
60
+ end
61
+
62
+ def set(name, key, value)
63
+ begin
64
+ namespace(name).insert(:key => build_key(key), :value => dump(value)) && build_key(key)
65
+ rescue Sequel::DatabaseError # raised if the key already exists, than update
66
+ update(name, key, value)
67
+ rescue
68
+ "ERROR"
69
+ end
70
+ end
71
+
72
+ def mset(name, data)
73
+ raise ArgumentError, "data must be a Hash with keys and values" unless Hash === data
74
+ namespace(name).multi_insert(data.map{ |key, value| {:key => build_key(key), :value => dump(value)} }) && "OK"
75
+ end
76
+
77
+ def delete(name, key)
78
+ namespace(name).where(:key => build_key(key)).limit(1).delete && "OK"
79
+ end
80
+
81
+ def clear(name)
82
+ namespace(name).delete && "OK"
83
+ end
84
+
85
+ def update(name, key, value)
86
+ namespace(name).where(:key => build_key(key)).limit(1).update(:value => dump(value)) && build_key(key)
87
+ end
88
+
89
+ def has_key?(name, key)
90
+ !!get(name, key)
91
+ end
92
+
93
+ def keys(name)
94
+ namespace(name).map{ |dataset| dataset[:key] }
95
+ end
96
+
97
+ def values(name)
98
+ namespace(name).map{ |dataset| load(dataset[:value]) }
99
+ end
100
+
101
+ def sort(name, order, limit)
102
+ data = case order.downcase
103
+ when :asc, 'asc'
104
+ namespace(name).order(Sequel.asc(:key))
105
+ when :desc, 'desc'
106
+ namespace(name).order(Sequel.desc(:key))
107
+ else
108
+ namespace(name).order(Sequel.asc(:key))
109
+ end
110
+
111
+ unless limit.nil?
112
+ raise ArgumentError, "limit must be a positive Integer" unless Integer === limit
113
+ data = data.limit(limit)
114
+ end
115
+ data.map{ |dataset| dataset[:key] }
116
+ end
117
+
118
+ def rename(name, old_key, new_key)
119
+ namespace(name).where(:key => build_key(old_key)).limit(1).update(:key => build_key(new_key)) && "OK"
120
+ end
121
+
122
+ def length(name)
123
+ namespace(name).count
124
+ end
125
+
126
+ private
127
+
128
+ # Sequel seems to have problems with escaping, so we use Base64 encoding/decoding as a simple work around
129
+ def load(data)
130
+ Marshal.load(Base64.urlsafe_decode64(data))
131
+ end
132
+
133
+ def dump(data)
134
+ Base64.urlsafe_encode64(Marshal.dump(data))
135
+ end
136
+
137
+ def connection
138
+ @connection
139
+ end
140
+
141
+ def build_key(key)
142
+ key.strip.to_s
143
+ end
144
+
145
+ def basic_tub_name(name)
146
+ "#{TABLE_PREFIX}#{name.strip}"
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,59 @@
1
+ # encoding: utf-8
2
+
3
+ require "canery/backend"
4
+ require "canery/tub"
5
+
6
+ module Canery
7
+
8
+ class CaneryError < StandardError; end
9
+
10
+ class Client
11
+
12
+ def initialize(connection_uri = nil)
13
+ @backend = Backend.new(connection_uri)
14
+ end
15
+
16
+ def tub(name)
17
+ create_tub(tub_name(name)) unless tub?(tub_name(name))
18
+ begin
19
+ @tub_cache ||= {}
20
+ @tub_cache[tub_name(name)] ||= Tub.new(backend, tub_name(name))
21
+ rescue
22
+ raise Canery::CaneryError, "This tub does not exist! You must create it before you can use it."
23
+ end
24
+ end
25
+
26
+ def delete_tub(name)
27
+ @tub_cache.delete(tub_name(name)) if @tub_cache[tub_name(name)]
28
+ backend.delete_namespace(tub_name(name))
29
+ end
30
+
31
+ def has_tub?(name)
32
+ backend.namespace?(tub_name(name))
33
+ end
34
+ alias :tub? :has_tub?
35
+
36
+ def tubs
37
+ backend.namespaces
38
+ end
39
+
40
+ private
41
+
42
+ def backend
43
+ @backend
44
+ end
45
+
46
+ def create_tub(name)
47
+ begin
48
+ backend.create_namespace(tub_name(name))
49
+ rescue => exception
50
+ raise Canery::CaneryError, exception
51
+ end
52
+ end
53
+
54
+ def tub_name(name)
55
+ name.strip.to_s
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,83 @@
1
+ # encoding: utf-8
2
+
3
+ require "securerandom"
4
+
5
+ module Canery
6
+ class Tub
7
+
8
+ attr_reader :name
9
+
10
+ def initialize(backend, name)
11
+ raise ArgumentError, "unknown backend type" unless Canery::Backend === backend
12
+ raise ArgumentError, "name of tub must be a String" unless String === name
13
+ @backend, @name = backend, name
14
+ end
15
+
16
+ def get(key)
17
+ backend.get(name, key)
18
+ end
19
+ alias :[] :get
20
+
21
+ def mget(keys)
22
+ backend.mget(name, keys)
23
+ end
24
+
25
+ def set(key, value)
26
+ backend.set(name, key || uuid, value)
27
+ end
28
+ alias :[]= :set
29
+
30
+ def mset(data)
31
+ backend.mset(name, data)
32
+ end
33
+
34
+ def delete(key)
35
+ backend.delete(name, key)
36
+ end
37
+
38
+ def clear
39
+ backend.clear(name)
40
+ end
41
+
42
+ def has_key?(key)
43
+ backend.has_key?(name, key)
44
+ end
45
+ alias :key? :has_key?
46
+
47
+ def keys
48
+ backend.keys(name)
49
+ end
50
+
51
+ def values
52
+ backend.values(name)
53
+ end
54
+
55
+ def sort(order, limit = nil)
56
+ begin
57
+ backend.sort(name, order, limit)
58
+ rescue ArgumentError
59
+ raise Canery::CaneryError, "limit parameter must be an Integer"
60
+ end
61
+ end
62
+
63
+ def rename(old_key, new_key)
64
+ backend.rename(name, old_key, new_key)
65
+ end
66
+
67
+ def length
68
+ backend.length(name)
69
+ end
70
+ alias :size :length
71
+
72
+ private
73
+
74
+ def backend
75
+ @backend
76
+ end
77
+
78
+ def uuid
79
+ SecureRandom.uuid
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ module Canery
4
+ VERSION = "0.1.1"
5
+ end
@@ -0,0 +1,71 @@
1
+ # encoding: utf-8
2
+
3
+ require "canery"
4
+
5
+ describe Canery::Client do
6
+ context "initialization" do
7
+ it "should not require a connection string" do
8
+ expect { Canery::Client.new }.not_to raise_error
9
+ end
10
+
11
+ it "should hide the backend" do
12
+ @client = Canery::Client.new
13
+ expect { @client.backend }.to raise_error
14
+ end
15
+
16
+ it "should have no tubs initialized" do
17
+ @client = Canery::Client.new
18
+ @client.tubs.should be_empty
19
+ end
20
+
21
+ end
22
+
23
+ context "tub method" do
24
+ before(:each) do
25
+ @client = Canery::Client.new
26
+ end
27
+
28
+ describe "#tub" do
29
+ it "should create a new tub" do
30
+ @client.tub("store")
31
+ @client.tubs.include?("store").should be_true
32
+ end
33
+
34
+ it "tub should be a Tub object" do
35
+ @client.tub("store").should be_kind_of(Canery::Tub)
36
+ end
37
+
38
+ it "should use the an existing tub instead of creating a new one" do
39
+ @client.tub("store").should == @client.tub("store")
40
+ end
41
+ end
42
+
43
+ describe "#delete_tub" do
44
+ it "should delete a specified tub" do
45
+ @client.tub("store")
46
+ @client.tubs.length.should == 1
47
+ @client.delete_tub("store")
48
+ @client.tubs.length.should == 0
49
+ end
50
+ end
51
+
52
+ describe "#has_tub?" do
53
+ it "should check return true if a tub already exists" do
54
+ @client.tub("store")
55
+ @client.has_tub?("store").should be_true
56
+ end
57
+ end
58
+
59
+ describe "#tubs" do
60
+ it "should return a list of all tubs" do
61
+ @client.tub("first_store")
62
+ @client.tub("another_tub")
63
+ @client.tubs.should be_kind_of(Array)
64
+ @client.tubs.length.should == 2
65
+ @client.tubs.include?("first_store").should be_true
66
+ @client.tubs.include?("another_tub").should be_true
67
+ end
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,160 @@
1
+ # encoding: utf-8
2
+
3
+ require "spec_helper"
4
+ require "canery"
5
+
6
+ describe Canery::Tub do
7
+ before(:each) do
8
+ @client = Canery::Client.new
9
+ end
10
+
11
+ context "on retrieving values" do
12
+ describe "#get" do
13
+ it "should retrieve value" do
14
+ store = @client.tub("store")
15
+ store.set("hello", "hello world")
16
+ store.get("hello").should == "hello world"
17
+ end
18
+
19
+ it "should retrieve value using the alias method" do
20
+ store = @client.tub("store")
21
+ store.set("hello", "hello world")
22
+ store["hello"].should == "hello world"
23
+ end
24
+
25
+ it "should return nil if value is not set" do
26
+ store = @client.tub("store")
27
+ store.get("unset_key").should be_nil
28
+ end
29
+ end
30
+
31
+ describe "#mget" do
32
+ it "should retrieve multiple values at once" do
33
+ store = @client.tub("store")
34
+ store.mset(demo_hash)
35
+ store.mget(demo_hash.keys).each do |value|
36
+ demo_hash.values.should include(value)
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ context "on setting values" do
43
+ describe "#set" do
44
+ it "should set value and return key" do
45
+ store = @client.tub("store")
46
+ store.set("hello", "hello world").should == "hello"
47
+ end
48
+
49
+ it "should set value using the alias method" do
50
+ store = @client.tub("store")
51
+ (store["hello"] = "hello world") == "hello"
52
+ end
53
+
54
+ it "should overwrite an existing value" do
55
+ store = @client.tub("store")
56
+ store.set("hello", "hello world").should == "hello"
57
+ store.get("hello") == "hello world"
58
+ store.set("hello", "new hello world").should == "hello"
59
+ store.get("hello") == "new hello world"
60
+ end
61
+
62
+ it "should automatically create an uuid key if key is nil" do
63
+ store = @client.tub("store")
64
+ uuid?(store.set(nil, "hello world")).should be_true
65
+ end
66
+ end
67
+
68
+ describe "#mset" do
69
+ it "should set multiple values at once" do
70
+ store = @client.tub("store")
71
+ store.mset(demo_hash).should == "OK"
72
+ demo_hash.each do |key, value|
73
+ store.get(key).should == value
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ describe "#delete" do
80
+ it "should delete a value" do
81
+ store = @client.tub("store")
82
+ store.set("hello", "hello world")
83
+ store.delete("hello").should == "OK"
84
+ end
85
+ end
86
+
87
+ describe "#clear" do
88
+ it "should delete all values of a tub" do
89
+ store = @client.tub("store")
90
+ store.mset(demo_hash)
91
+ store.size.should == demo_hash.length
92
+ store.clear
93
+ store.size.should == 0
94
+ end
95
+ end
96
+
97
+ describe "#has_key?" do
98
+ it "should return true if key is present in tub" do
99
+ store = @client.tub("store")
100
+ store.set("hello", "hello world")
101
+ store.has_key?("hello").should be_true
102
+ end
103
+ end
104
+
105
+ describe "#keys" do
106
+ it "should return all keys present in a tub" do
107
+ store = @client.tub("store")
108
+ store.mset(demo_hash)
109
+ (store.keys & demo_hash.keys).should == demo_hash.keys
110
+ end
111
+ end
112
+
113
+ describe "#values" do
114
+ it "should return all values present in a tub" do
115
+ store = @client.tub("store")
116
+ store.mset(demo_hash)
117
+ (store.values & demo_hash.values).should == demo_hash.values
118
+ end
119
+ end
120
+
121
+ describe "#sort" do
122
+ it "should sort keys in ascending order" do
123
+ store = @client.tub("store")
124
+ store.mset(demo_hash)
125
+ (store.sort(:asc) & demo_hash.keys.sort).should == demo_hash.keys.sort
126
+ end
127
+
128
+ it "should sort keys in descending order" do
129
+ store = @client.tub("store")
130
+ store.mset(demo_hash)
131
+ data_hash = demo_hash.keys.sort{|a,b| b <=> a}
132
+ (store.sort(:desc) & data_hash).should == data_hash
133
+ end
134
+
135
+ it "should sort keys and limit" do
136
+ store = @client.tub("store")
137
+ store.mset(demo_hash)
138
+ (store.sort(:asc, 3) & demo_hash.keys.sort[0...3]).should == demo_hash.keys.sort[0...3]
139
+ end
140
+ end
141
+
142
+ describe "#rename" do
143
+ it "should rename a key without touching the value" do
144
+ store = @client.tub("store")
145
+ store.set("hello", "hello world")
146
+ store.get("hello").should == "hello world"
147
+ store.rename("hello", "new_hello")
148
+ store.get("new_hello").should == "hello world"
149
+ end
150
+ end
151
+
152
+ describe "#length" do
153
+ it "should return the number of elements in a tub" do
154
+ store = @client.tub("store")
155
+ store.mset(demo_hash)
156
+ store.length.should == demo_hash.length
157
+ end
158
+ end
159
+
160
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ def uuid?(value)
4
+ (value =~ /^[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}$/) != nil
5
+ end
6
+
7
+ def demo_hash
8
+ {"homer" => "Homer Simpson", "marge" => "Marge Simpson", "lisa" => "Lisa Simpson", "bart" => "Bart Simpson", "maggy" => "Maggy Simpson"}
9
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: canery
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Railsmechanic
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sequel
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 3.38.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 3.38.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 2.11.0
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 2.11.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: sqlite3-ruby
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 1.3.3
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 1.3.3
62
+ description: A simple but handy key/value store which is able to use a bunch of SQL
63
+ databases as its backend.
64
+ email:
65
+ - info@railsmechanic.de
66
+ executables: []
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - Gemfile
71
+ - LICENSE
72
+ - README.md
73
+ - Rakefile
74
+ - canery.gemspec
75
+ - lib/canery.rb
76
+ - lib/canery/backend.rb
77
+ - lib/canery/client.rb
78
+ - lib/canery/tub.rb
79
+ - lib/canery/version.rb
80
+ - spec/canery_client_spec.rb
81
+ - spec/canery_tub_spec.rb
82
+ - spec/spec_helper.rb
83
+ homepage: https://github.com/railsmechanic/canery
84
+ licenses: []
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ segments:
96
+ - 0
97
+ hash: 1096333460484850874
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ segments:
105
+ - 0
106
+ hash: 1096333460484850874
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 1.8.24
110
+ signing_key:
111
+ specification_version: 3
112
+ summary: Canery is a simple and easy to use key/value store with several commands
113
+ to store and retrieve data. Because it uses a SQL database for storing the data,
114
+ it can be used in many environments.
115
+ test_files:
116
+ - spec/canery_client_spec.rb
117
+ - spec/canery_tub_spec.rb
118
+ - spec/spec_helper.rb