keymap 0.1.0 → 0.2.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.
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ script: bundle exec rake spec
3
+ rvm:
4
+ - 1.9.2
5
+ gemfile:
6
+ - Gemfile
7
+ notifications:
8
+ recipients:
9
+ - buck.robert.j@gmail.com
10
+ branches:
11
+ only:
12
+ master
@@ -0,0 +1,93 @@
1
+ = Keymap
2
+
3
+ Helping Ruby developers and their companies, unlock their key-value store data,
4
+ through associative and sequential based access, providing unprecedented support
5
+ for map reduce behaviors, native to the Ruby language.
6
+
7
+ A NoSQL database abstraction similar in design to ActiveRecord but solely
8
+ focussed on NoSQL databases. These are the goals for this project:
9
+
10
+ 1. Provide a simple abstraction layer over NoSQL databases, allowing easy
11
+ migration from one to another without having to rewrite application code.
12
+
13
+ 2. Provide a natural Ruby integration with NoSQL database, allowing direct
14
+ use of Enumerable operations on returned values (implying returned values
15
+ are always lists or hashes, or are manipulated to be represented in these
16
+ forms).
17
+
18
+ {<img src="https://secure.travis-ci.org/rbuck/keymap.png?branch=master" alt="Continuous Integration Status" />}[http://travis-ci.org/rbuck/keymap]
19
+
20
+ == Installation
21
+
22
+ Add this line to your application's Gemfile:
23
+
24
+ gem 'keymap'
25
+
26
+ And then execute:
27
+
28
+ $ bundle
29
+
30
+ Or install it yourself as:
31
+
32
+ $ gem install keymap
33
+
34
+ == Usage
35
+
36
+ TBD
37
+
38
+ == Contributing
39
+
40
+ 1. Fork it
41
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
42
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
43
+ 4. Push to the branch (`git push origin my-new-feature`)
44
+ 5. Create new Pull Request
45
+
46
+ == Writing New Adapters
47
+
48
+ 1. Create a new adapter file named #{database_name}_adapter.rb, placing it
49
+ in the connection adapters directory.
50
+ 2. Add an entry to the list of supported databases in the Rakefile.
51
+ 3. Add test configuration entries in the spec/support/config.yml file.
52
+ 4. Verify all tests pass when the KEYCONN=#{database_name} environment
53
+ variable is set.
54
+
55
+ == Configure databases
56
+
57
+ Copy test/config.example.yml to test/config.yml and edit as needed. Or just run the tests for
58
+ the first time, which will do the copy automatically and use the default (sqlite3).
59
+
60
+ You can build postgres and mysql databases using the build_postgresql and build_mysql rake tasks.
61
+
62
+ == Running the tests
63
+
64
+ You can run a particular test file from the command line, e.g.
65
+
66
+ $ ruby -Itest test/cases/base_test.rb
67
+
68
+ To run a specific test:
69
+
70
+ $ ruby -Itest test/cases/base_test.rb -n test_something_works
71
+
72
+ You can run with a database other than the default you set in test/config.yml, using the ARCONN
73
+ environment variable:
74
+
75
+ $ ARCONN=postgresql ruby -Itest test/cases/base_test.rb
76
+
77
+ You can run all the tests for a given database via rake:
78
+
79
+ $ rake test_mysql
80
+
81
+ The 'rake test' task will run all the tests for mysql, mysql2, sqlite3 and postgresql.
82
+
83
+ == Identity Map
84
+
85
+ By default the tests run with the Identity Map turned off. But all tests should pass whether or
86
+ not the identity map is on or off. You can turn it on using the IM env variable:
87
+
88
+ $ IM=true ruby -Itest test/case/base_test.rb
89
+
90
+ == Config file
91
+
92
+ By default, the config file is expected to be at the path test/config.yml. You can specify a
93
+ custom location with the ARCONFIG environment variable.
data/Rakefile CHANGED
@@ -48,7 +48,7 @@ desc "Release version #{Keymap::VERSION}"
48
48
  task :release => [:tag, :push]
49
49
 
50
50
  desc "Provides tasks for each adapter type, e.g. test_redis"
51
- %w( redis ).each do |adapter|
51
+ %w( ).each do |adapter|
52
52
  Rake::TestTask.new("test_#{adapter}") { |t|
53
53
  adapter_short = adapter[/^[a-z0-9]+/]
54
54
  t.libs << 'test'
@@ -63,13 +63,27 @@ desc "Provides tasks for each adapter type, e.g. test_redis"
63
63
  # Set the connection environment for the adapter
64
64
  namespace adapter do
65
65
  task :test => "test_#{adapter}"
66
- task(:env) { ENV['KEYMAPPCONN'] = adapter }
66
+ task(:env) { ENV['KEYCONN'] = adapter }
67
67
  end
68
68
 
69
69
  # Make sure the adapter test evaluates the env setting task
70
70
  task "test_#{adapter}" => "#{adapter}:env"
71
71
  end
72
72
 
73
+ namespace :redis do
74
+ task :start_server do
75
+ config = KeymapTest.config['connections']['redis']
76
+ puts %x( echo "daemonize yes\nport #{config['test']['port']}\ndir #{File.dirname(__FILE__)}" | redis-server - )
77
+ end
78
+
79
+ task :stop_server do
80
+ config = KeymapTest.config['connections']['redis']
81
+ puts %x( redis-cli -p #{config['test']['port']} shutdown )
82
+ end
83
+
84
+ task :restart_server => [:stop_server, :start_server]
85
+ end
86
+
73
87
  desc "Prints lines of code metrics"
74
88
  task :lines do
75
89
  lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
@@ -0,0 +1,9 @@
1
+ machine:
2
+ timezone:
3
+ America/New_York
4
+ ruby:
5
+ version: ruby-1.9.3-p125
6
+
7
+ test:
8
+ post:
9
+ - bundle exec rake spec
@@ -23,6 +23,6 @@ Gem::Specification.new do |spec|
23
23
  spec.add_dependency "activesupport", "~> 3.2.8"
24
24
 
25
25
  %w(rake rdoc simplecov).each { |gem| spec.add_development_dependency gem }
26
+ %w(ruby-prof).each { |gem| spec.add_development_dependency gem }
26
27
  %w(rspec rspec-core rspec-expectations rspec-mocks).each { |gem| spec.add_development_dependency gem, "~> 2.11.0" }
27
-
28
28
  end
@@ -25,22 +25,18 @@ module Keymap
25
25
  #
26
26
  # development:
27
27
  # adapter: redis
28
- # database: db/development.sqlite3
29
28
  #
30
29
  # production:
31
30
  # adapter: redis
32
- # database: db/production.sqlite3
33
31
  #
34
32
  # ...would result in Keymap::Base.configurations to look like this:
35
33
  #
36
34
  # {
37
35
  # 'development' => {
38
- # 'adapter' => 'sqlite3',
39
- # 'database' => 'db/development.sqlite3'
36
+ # 'adapter' => 'redis',
40
37
  # },
41
38
  # 'production' => {
42
- # 'adapter' => 'sqlite3',
43
- # 'database' => 'db/production.sqlite3'
39
+ # 'adapter' => 'redis',
44
40
  # }
45
41
  # }
46
42
  cattr_accessor :configurations, :instance_writer => false
@@ -122,8 +122,8 @@ module Keymap
122
122
 
123
123
  # Returns the configuration of the associated connection as a hash:
124
124
  #
125
- # ActiveRecord::Base.connection_config
126
- # # => {:pool=>5, :timeout=>5000, :database=>"db/development.sqlite3", :adapter=>"sqlite3"}
125
+ # Keymap::Base.connection_config
126
+ # # => {:pool=>5, :timeout=>5000, :adapter=>"redis"}
127
127
  #
128
128
  # Please use only for reading.
129
129
  def connection_config
@@ -0,0 +1,15 @@
1
+ module Keymap
2
+ module ConnectionAdapters
3
+ module DataManagement #:nodoc:
4
+
5
+ def list (key)
6
+ nil
7
+ end
8
+
9
+ def delete(key)
10
+ false
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -1,8 +1,22 @@
1
1
  require 'keymap/errors'
2
2
 
3
3
  module Keymap
4
+
4
5
  module ConnectionAdapters # :nodoc:
6
+
5
7
  module TransactionManagement
8
+ # Checks whether there is currently no transaction active. This is done
9
+ # by querying the database driver, and does not use the transaction
10
+ # house-keeping information recorded by #increment_open_transactions and
11
+ # friends.
12
+ #
13
+ # Returns true if there is no transaction active, false if there is a
14
+ # transaction active, and nil if this information is unknown.
15
+ #
16
+ # Not all adapters supports transaction state introspection.
17
+ def outside_transaction?
18
+ nil
19
+ end
6
20
 
7
21
  # Runs the given block in a database transaction, and returns the result
8
22
  # of the block.
@@ -1,7 +1,6 @@
1
1
  require 'monitor'
2
2
  require 'active_support/callbacks'
3
3
  require 'active_support/dependencies/autoload'
4
- require 'keymap/connection_adapters/abstract/transaction_management'
5
4
 
6
5
  module Keymap
7
6
 
@@ -14,12 +13,15 @@ module Keymap
14
13
  autoload :ConnectionHandler, 'keymap/connection_adapters/abstract/connection_pool'
15
14
  autoload :ConnectionManagement, 'keymap/connection_adapters/abstract/connection_pool'
16
15
  autoload :ConnectionSpecification
16
+ autoload :TransactionManagement
17
+ autoload :DataManagement
17
18
  end
18
19
 
19
20
  class AbstractAdapter
20
21
 
21
22
  include ActiveSupport::Callbacks
22
23
  include TransactionManagement
24
+ include DataManagement
23
25
  include MonitorMixin
24
26
 
25
27
  define_callbacks :checkout, :checkin
@@ -70,42 +70,84 @@ module Keymap
70
70
  raw_connection.discard
71
71
  end
72
72
 
73
- # Retrieves the hash whose name is identified by key.
74
- def hash (key)
75
- coll = KeyStoreHash.new
76
- coll.connection = raw_connection
77
- coll.key = key
78
- coll
79
- #
80
- # Perhaps there is a more experienced ruby developer who could help with this.
81
- # I'd rather do the following but could not figure out how to get it to work:
82
- #
83
- #def create_getter(connection, key)
84
- # lambda { |field| connection.hget key, field }
85
- #end
86
- #
87
- #def create_setter(connection, key)
88
- # lambda { |field, value| connection.hset key, field, value }
89
- #end
90
- #
91
- #coll.class.send :define_method, "[]", create_getter(raw_connection, key)
92
- #coll.class.send :define_method, "[]=", create_setter(raw_connection, key)
93
- #coll
73
+ def delete(key)
74
+ raw_connection.del(key) != 0
75
+ end
76
+
77
+ # todo idea: add an optional argument where we specify the data type for elements in the collection
78
+ def list (key)
79
+ List.new(raw_connection, key)
94
80
  end
95
81
  end
96
82
 
97
83
  private
98
84
 
99
- class KeyStoreHash < Hash
85
+ class List
86
+
87
+ include Enumerable
88
+
89
+ attr_reader :connection, :key
90
+
91
+ def initialize(connection, key)
92
+ @connection = connection
93
+ @key = key
94
+ self << nil # sentinel to force creation of an "empty list"
95
+ end
96
+
97
+ def each
98
+ if block_given?
99
+ step_size = 100
100
+ (0..length % step_size).step(step_size) do |step|
101
+ first = step_size * step
102
+ last = first + step_size
103
+ list = connection.lrange key, first + 1, last
104
+ list.each do |item|
105
+ yield item
106
+ end
107
+ end
108
+ else
109
+ ::Enumerable::Enumerator.new(self, :each)
110
+ end
111
+ end
112
+
113
+ def <<(value)
114
+ connection.rpush key, value
115
+ self
116
+ end
117
+
118
+ alias :push :<<
119
+
120
+ def [](index)
121
+ connection.lindex key, index + 1
122
+ end
123
+
124
+ def []=(index, value)
125
+ connection.lset key, index + 1, value
126
+ end
100
127
 
101
- attr_accessor :connection, :key
128
+ def length
129
+ connection.llen(key) -1
130
+ end
131
+
132
+ alias size length
133
+
134
+ def empty?()
135
+ length != 1
136
+ end
137
+
138
+ def pop()
139
+ connection.rpop key unless length == 0
140
+ end
102
141
 
103
- def [] (field)
104
- connection.hget key, field
142
+ def delete(value)
143
+ value = connection.lrem(key, 0, value) == 0 ? nil : value
144
+ yield value if block_given?
145
+ value
105
146
  end
106
147
 
107
- def []=(field, value)
108
- connection.hset key, field, value
148
+ def delete_if(&block)
149
+ # todo
150
+ self
109
151
  end
110
152
  end
111
153
 
@@ -1,3 +1,3 @@
1
1
  module Keymap
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe Keymap::Base do
4
+ before do
5
+ @connection = Keymap::Base.connection
6
+ end
7
+
8
+ context "a connection yields enumerable lists" do
9
+
10
+ before(:each) do
11
+ @connection.delete :what
12
+ end
13
+
14
+ after(:each) do
15
+ @connection.delete :what
16
+ end
17
+
18
+ it "adds an empty list automatically upon assignment" do
19
+ list = @connection.list :what
20
+ list.nil?.should be_false
21
+ end
22
+
23
+ it "lists support an empty? operation" do
24
+ list = @connection.list :what
25
+ list.respond_to?(:empty?).should be_true
26
+ end
27
+
28
+ it "lists are empty upon creation" do
29
+ list = @connection.list :what
30
+ list.empty?.should be_true
31
+ end
32
+
33
+ it "returns false when trying to delete a key that does not exist" do
34
+ exists = @connection.delete :what
35
+ exists.should be_false
36
+ end
37
+
38
+ it "returns true when trying to delete a key that does exist" do
39
+ @connection.list :what
40
+ exists = @connection.delete :what
41
+ exists.should be_true
42
+ end
43
+
44
+ it "lists implement enumerable" do
45
+ list = @connection.list :what
46
+ list.respond_to?(:each).should be_true
47
+ end
48
+
49
+ it "lists support pop" do
50
+ list = @connection.list :what
51
+ list << 1
52
+ list.pop.to_i.should eq(1)
53
+ end
54
+
55
+ it "lists support append operators and inject" do
56
+ list = @connection.list :what
57
+ (0..10).each do |value|
58
+ list << value
59
+ end
60
+ sum = list.inject(0) do |result, item|
61
+ result + item.to_i
62
+ end
63
+ sum.should eq(55)
64
+ end
65
+ end
66
+ end
@@ -15,5 +15,7 @@ SimpleCov.start do
15
15
  add_group 'Libraries', 'lib'
16
16
  end
17
17
 
18
- #RSpec.configure do |config|
19
- #end
18
+ require 'support/config'
19
+ require 'support/connection'
20
+
21
+ KeymapTest.connect
@@ -14,12 +14,12 @@ module KeymapTest
14
14
  private
15
15
 
16
16
  def config_file
17
- Pathname.new(ENV['KEYMAP_CONFIG'] || SPEC_ROOT + '/support/config.yml')
17
+ Pathname.new(ENV['KEYMAP_CONFIG'] || File.join(SPEC_ROOT, 'config.yml'))
18
18
  end
19
19
 
20
20
  def read_config
21
21
  unless config_file.exist?
22
- FileUtils.cp SPEC_ROOT + '/support/config.example.yml', config_file
22
+ FileUtils.cp(File.join(SPEC_ROOT, 'config.example.yml'), config_file)
23
23
  end
24
24
 
25
25
  erb = Erubis::Eruby.new(config_file.read)
@@ -27,18 +27,6 @@ module KeymapTest
27
27
  end
28
28
 
29
29
  def expand_config(config)
30
- config['connections'].each do |adapter, connection|
31
- dbs = [['arunit', 'activerecord_unittest'], ['arunit2', 'activerecord_unittest2']]
32
- dbs.each do |name, dbname|
33
- unless connection[name].is_a?(Hash)
34
- connection[name] = {'database' => connection[name]}
35
- end
36
-
37
- connection[name]['database'] ||= dbname
38
- connection[name]['adapter'] ||= adapter
39
- end
40
- end
41
-
42
30
  config
43
31
  end
44
32
  end
@@ -1,10 +1,8 @@
1
- default_connection: <%= defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3' %>
2
-
3
- with_manual_interventions: false
1
+ default_connection: redis
4
2
 
5
3
  connections:
6
4
  redis:
5
+ test:
7
6
  adapter: redis
8
7
  host: localhost
9
- password: kmunit
10
- database: kmunit
8
+ port: 4000
@@ -2,7 +2,7 @@ require 'logger'
2
2
 
3
3
  module KeymapTest
4
4
  def self.connection_name
5
- ENV['KEYMAP_CONN'] || config['default_connection']
5
+ ENV['KEYCONN'] || config['default_connection']
6
6
  end
7
7
 
8
8
  def self.connection_config
@@ -12,6 +12,6 @@ module KeymapTest
12
12
  def self.connect
13
13
  Keymap::Base.logger = Logger.new("debug.log")
14
14
  Keymap::Base.configurations = connection_config
15
- Keymap::Base.establish_connection 'kmunit'
15
+ Keymap::Base.establish_connection 'test'
16
16
  end
17
17
  end
@@ -1,7 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
3
 
4
- GEM_ROOT = File.expand_path(File.join(File.dirname(__FILE__), ".."))
4
+ GEM_ROOT ||= File.expand_path(File.join(File.dirname(__FILE__), ".."))
5
5
 
6
6
  begin
7
7
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keymap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -107,6 +107,22 @@ dependencies:
107
107
  - - ! '>='
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: ruby-prof
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
110
126
  - !ruby/object:Gem::Dependency
111
127
  name: rspec
112
128
  requirement: !ruby/object:Gem::Requirement
@@ -179,15 +195,18 @@ extra_rdoc_files: []
179
195
  files:
180
196
  - .gitignore
181
197
  - .rspec
198
+ - .travis.yml
182
199
  - Gemfile
183
200
  - LICENSE.txt
184
- - README.md
201
+ - README.rdoc
185
202
  - Rakefile
203
+ - circle.yml
186
204
  - keymap.gemspec
187
205
  - lib/keymap.rb
188
206
  - lib/keymap/base.rb
189
207
  - lib/keymap/connection_adapters/abstract/connection_pool.rb
190
208
  - lib/keymap/connection_adapters/abstract/connection_specification.rb
209
+ - lib/keymap/connection_adapters/abstract/data_management.rb
191
210
  - lib/keymap/connection_adapters/abstract/transaction_management.rb
192
211
  - lib/keymap/connection_adapters/abstract_adapter.rb
193
212
  - lib/keymap/connection_adapters/redis_adapter.rb
@@ -197,6 +216,7 @@ files:
197
216
  - spec/functional/abstract_adapter_spec.rb
198
217
  - spec/functional/adapter_spec.rb
199
218
  - spec/functional/adapters/redis/connection_spec.rb
219
+ - spec/functional/list_spec.rb
200
220
  - spec/rcov.opts
201
221
  - spec/spec.opts
202
222
  - spec/spec_helper.rb
@@ -236,6 +256,7 @@ test_files:
236
256
  - spec/functional/abstract_adapter_spec.rb
237
257
  - spec/functional/adapter_spec.rb
238
258
  - spec/functional/adapters/redis/connection_spec.rb
259
+ - spec/functional/list_spec.rb
239
260
  - spec/rcov.opts
240
261
  - spec/spec.opts
241
262
  - spec/spec_helper.rb
data/README.md DELETED
@@ -1,47 +0,0 @@
1
- # Keymap
2
-
3
- A NoSQL database abstraction similar in design to ActiveRecord but solely
4
- focussed on NoSQL databases. These are the goals for this project:
5
-
6
- 1. Provide a simple abstraction layer over NoSQL databases, allowing easy
7
- migration from one to another without having to rewrite application code.
8
-
9
- 2. Provide a natural Ruby integration with NoSQL database, allowing direct
10
- use of Enumerable operations on returned values (implying returned values
11
- are always lists or hashes, or are manipulated to be represented in these
12
- forms).
13
-
14
- ## Installation
15
-
16
- Add this line to your application's Gemfile:
17
-
18
- gem 'keymap'
19
-
20
- And then execute:
21
-
22
- $ bundle
23
-
24
- Or install it yourself as:
25
-
26
- $ gem install keymap
27
-
28
- ## Usage
29
-
30
- TBD
31
-
32
- ## Contributing
33
-
34
- 1. Fork it
35
- 2. Create your feature branch (`git checkout -b my-new-feature`)
36
- 3. Commit your changes (`git commit -am 'Add some feature'`)
37
- 4. Push to the branch (`git push origin my-new-feature`)
38
- 5. Create new Pull Request
39
-
40
- ## Writing New Adapters
41
-
42
- 1. Create a new adapter file named #{database_name}_adapter.rb, placing it
43
- in the connection adapters directory.
44
- 2. Add an entry to the list of supported databases in the Rakefile.
45
- 3. Add test configuration entries in the spec/support/config.yml file.
46
- 4. Verify all tests pass when the KEYMAP_CONN=#{database_name} environment
47
- variable is set.