keymap 0.1.0 → 0.2.0

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