quandl_location 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +98 -0
- data/Rakefile +1 -0
- data/lib/quandl/location.rb +65 -0
- data/lib/quandl/location/data.rb +77 -0
- data/lib/quandl/location/exceptions.rb +24 -0
- data/lib/quandl/location/storage.rb +48 -0
- data/lib/quandl/location/storage/file_system.rb +43 -0
- data/lib/quandl/location/storage/redis.rb +81 -0
- data/lib/quandl/location/version.rb +5 -0
- data/quandl_location.gemspec +28 -0
- data/spec/quandl/location/data_spec.rb +101 -0
- data/spec/quandl/location/storage/file_system_spec.rb +90 -0
- data/spec/quandl/location/storage/redis_spec.rb +120 -0
- data/spec/quandl/location/storage_spec.rb +43 -0
- data/spec/quandl/location_spec.rb +61 -0
- data/spec/quandl/support/fake_redis.rb +0 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/config/file_storage.yml +2 -0
- data/spec/support/config/redis.yml +4 -0
- metadata +190 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Jason Byck
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# Quandl Location
|
2
|
+
|
3
|
+
[![Build Status](https://magnum.travis-ci.com/quandl/quandl_location.png?token=Jqe6yYPbokJgKw6u73eU&branch=master)](https://magnum.travis-ci.com/quandl/quandl_location)
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'quandl_location'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install quandl_location
|
18
|
+
|
19
|
+
|
20
|
+
## About
|
21
|
+
|
22
|
+
quandl_location provides a getter/setter class to store key-value data, but abstracts the storage engine used to store the data.
|
23
|
+
|
24
|
+
Any supported storage engine can be configured at run time. Currently supports Redis and Unix file system.
|
25
|
+
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
#### Configure Redis storage
|
32
|
+
|
33
|
+
``` ruby
|
34
|
+
Quandl::Location.use(:redis) do
|
35
|
+
with_options { host: localhost, port: 6379 }
|
36
|
+
end
|
37
|
+
```
|
38
|
+
Redis storage takes two optional configuration settings: expiry and encoding.
|
39
|
+
|
40
|
+
For example:
|
41
|
+
|
42
|
+
```yaml
|
43
|
+
production:
|
44
|
+
host: localhost
|
45
|
+
port: 6379
|
46
|
+
expiry: 3600
|
47
|
+
encoding: ASCII-8BIT
|
48
|
+
```
|
49
|
+
|
50
|
+
This configuration will set a timeout of 1 day for each key in Redis created by Quandl::Location, and force the encoding for all retreived objects to 'ASCII-8BIT'.
|
51
|
+
|
52
|
+
There is no default expiry. If it is not included in configuration, then no timeout will be set for the created key.
|
53
|
+
|
54
|
+
If encoding is not included, retrieved data from Redis will be encoded with Encoding::default_external (which is default behaviour of the redis-rb gem).
|
55
|
+
|
56
|
+
#### Configure file system storage
|
57
|
+
|
58
|
+
``` ruby
|
59
|
+
Quandl::Location.use(:file_system) do
|
60
|
+
with_options { data_dir: '/data/locations/' }
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
File system stores location data using the location_id as the file name.
|
65
|
+
|
66
|
+
#### Save data
|
67
|
+
|
68
|
+
Creates new key, or overwrites existing key with new data.
|
69
|
+
|
70
|
+
``` ruby
|
71
|
+
location_data = Quandl::Location::Data.find(location_id)
|
72
|
+
location_data.data = [1,2,3]
|
73
|
+
location_data.save
|
74
|
+
```
|
75
|
+
#### Retrieve data
|
76
|
+
|
77
|
+
Returns the data, or returns nil if data does not exist.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
raw_data = Quandl::Location::Data.find(location_id).data
|
81
|
+
```
|
82
|
+
|
83
|
+
#### Check for data
|
84
|
+
|
85
|
+
Use exists? method to check if the data exits in the storage engine. The get method returns nil if exists? is false, so it is not necessary to check for existance before using get.
|
86
|
+
|
87
|
+
Quandl::Storage::Redis returns true if there is a key with value location_id, and Quandl::Storage::FileSystem returns true if there is a file present with name location_id.
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
location_data = Quandl::Location::Data.find(new_location_id)
|
91
|
+
location_data.exists?
|
92
|
+
=> false
|
93
|
+
location_data.data = [1,2,3]
|
94
|
+
location_data.exists?
|
95
|
+
=> false
|
96
|
+
location_data.save
|
97
|
+
location_data.exists?
|
98
|
+
=> true
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'active_support/core_ext/hash'
|
2
|
+
require 'active_support/core_ext/object'
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
4
|
+
require 'active_support/inflector'
|
5
|
+
|
6
|
+
require 'quandl/location/exceptions'
|
7
|
+
require 'quandl/location/data'
|
8
|
+
require 'quandl/location/storage'
|
9
|
+
|
10
|
+
module Quandl
|
11
|
+
module Location
|
12
|
+
|
13
|
+
STORAGE_ENGINES = [:redis, :file_system]
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
# The list of supported storage engines for locations
|
18
|
+
#
|
19
|
+
# Returns array
|
20
|
+
def storage_engines
|
21
|
+
STORAGE_ENGINES
|
22
|
+
end
|
23
|
+
|
24
|
+
# The name of the current active storage engine, or nil if not set
|
25
|
+
#
|
26
|
+
# Returns string or nil
|
27
|
+
def storage_engine_type
|
28
|
+
@@storage_engine_type || nil
|
29
|
+
end
|
30
|
+
|
31
|
+
# Set the storage engine for the location module
|
32
|
+
#
|
33
|
+
# Returns Quandl::Location::Storage
|
34
|
+
# Raises Quandl::Location::UnknownStorageEngineError
|
35
|
+
def use(storage_engine, &block)
|
36
|
+
unless storage_engines.include?(storage_engine.to_sym)
|
37
|
+
error_message = %Q{
|
38
|
+
Unknown storage engine #{storage_engine}.
|
39
|
+
Please use one of supported engines #{storage_engines.collect{|s| s.to_s}.join}
|
40
|
+
}
|
41
|
+
raise(Quandl::Location::UnknownStorageEngineError, error_message)
|
42
|
+
end
|
43
|
+
@@storage_engine_type = storage_engine
|
44
|
+
storage_engine_class = storage_engine.to_s.downcase.camelize
|
45
|
+
@@storage_engine = "Quandl::Location::Storage::#{storage_engine_class}".constantize.new
|
46
|
+
@@storage_engine.instance_eval(&block) if block_given?
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
# The intance of the Storage class that was configured
|
51
|
+
#
|
52
|
+
# Returns Quandl::Location::Storage
|
53
|
+
# Raises Quandl::Location::UninitializedStorageEngineError
|
54
|
+
def storage_engine
|
55
|
+
unless @@storage_engine.present?
|
56
|
+
raise(Quandl::Location::UninitializedStorageEngineError, "You must configure a valid storage engine.")
|
57
|
+
end
|
58
|
+
@@storage_engine
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Quandl
|
2
|
+
module Location
|
3
|
+
class Data
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
# Create a new location data instance
|
8
|
+
#
|
9
|
+
# id - The location id
|
10
|
+
#
|
11
|
+
# Returns Quandl::Location::Data
|
12
|
+
def find(id)
|
13
|
+
self.new(id)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :id, :data
|
19
|
+
|
20
|
+
# Constructor
|
21
|
+
#
|
22
|
+
# Returns Quandl::Location::Data
|
23
|
+
def initialize(*args)
|
24
|
+
self.id = args.first
|
25
|
+
end
|
26
|
+
|
27
|
+
# Set the data for the location, through the storage engine
|
28
|
+
#
|
29
|
+
# data - The raw location data as a string
|
30
|
+
#
|
31
|
+
# Returns .
|
32
|
+
def data=(data)
|
33
|
+
@raw_data = data
|
34
|
+
@data_changed = true
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get the location data, through the storage engine
|
38
|
+
#
|
39
|
+
# Returns string
|
40
|
+
def data
|
41
|
+
storage.get(id)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Has the raw data changed
|
45
|
+
#
|
46
|
+
# Return boolean
|
47
|
+
def changed?
|
48
|
+
@data_changed == true
|
49
|
+
end
|
50
|
+
|
51
|
+
# Save the raw_data that was set through data=
|
52
|
+
#
|
53
|
+
# Returns boolean
|
54
|
+
def save
|
55
|
+
return false unless @data_changed
|
56
|
+
storage.set(id, @raw_data)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Check if data exists in storage engine
|
60
|
+
#
|
61
|
+
# Return boolean
|
62
|
+
def exists?
|
63
|
+
storage.exists?(id)
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
# Access to currently configured storage engine
|
69
|
+
#
|
70
|
+
# Returns Quandl::Location::Storage
|
71
|
+
def storage
|
72
|
+
@storage ||= Quandl::Location.storage_engine
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Quandl
|
2
|
+
module Location
|
3
|
+
|
4
|
+
# Unknown storage engine
|
5
|
+
class UnknownStorageEngineError < RuntimeError; end
|
6
|
+
|
7
|
+
# Storage not initialized
|
8
|
+
class UninitializedStorageEngineError < RuntimeError; end
|
9
|
+
|
10
|
+
# Storage options
|
11
|
+
class InvalidStorageOptionsError < RuntimeError; end
|
12
|
+
|
13
|
+
# Storage engine not complete
|
14
|
+
class IncompleteStorageEngineError < RuntimeError; end
|
15
|
+
|
16
|
+
# Locaiton data not present when expected
|
17
|
+
class MissingLocationDataError < RuntimeError; end
|
18
|
+
|
19
|
+
# Bad storage configuration
|
20
|
+
class StorageConfigurationError < RuntimeError; end
|
21
|
+
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Quandl
|
2
|
+
module Location
|
3
|
+
|
4
|
+
class Storage
|
5
|
+
|
6
|
+
# Auto assign storage options from configuration
|
7
|
+
#
|
8
|
+
# storage_options - A hash of key value options to configure the storage engine
|
9
|
+
#
|
10
|
+
# Return nil
|
11
|
+
def with_options(storage_options)
|
12
|
+
unless storage_options.present? and storage_options.kind_of?(Hash)
|
13
|
+
raise(Quandl::Location::InvalidStorageOptionsError, "Storage options must be present and kind of Hash")
|
14
|
+
end
|
15
|
+
storage_options.collect do |k,v|
|
16
|
+
self.send("#{k}=", v) if self.respond_to?("#{k}=")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get the data from the storage engine
|
21
|
+
# This is a method stub that raises an error unless get has been implemented in the child class.
|
22
|
+
#
|
23
|
+
# Raises Quandl::Location::IncompleteStorageEngineError
|
24
|
+
def get
|
25
|
+
raise(Quandl::Location::IncompleteStorageEngineError, "get method must be implemented in class.")
|
26
|
+
end
|
27
|
+
|
28
|
+
# Set the data in the storage engine
|
29
|
+
# This is a method stub that raises an error unless set has been implemented in the child class.
|
30
|
+
#
|
31
|
+
# Raises Quandl::Location::IncompleteStorageEngineError
|
32
|
+
def set
|
33
|
+
raise(Quandl::Location::IncompleteStorageEngineError, "set method must be implemented in class.")
|
34
|
+
end
|
35
|
+
|
36
|
+
# Check if the location_id exists in the storage engine
|
37
|
+
# This is a method stub that raises an error unless set has been implemented in the child class.
|
38
|
+
def exists?
|
39
|
+
raise(Quandl::Location::IncompleteStorageEngineError, "exists? method must be implemented in class.")
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
require 'quandl/location/storage/file_system'
|
48
|
+
require 'quandl/location/storage/redis'
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Quandl
|
2
|
+
module Location
|
3
|
+
|
4
|
+
class Storage::FileSystem < Storage
|
5
|
+
|
6
|
+
attr_accessor :data_dir
|
7
|
+
|
8
|
+
def get(location_id)
|
9
|
+
return nil unless self.exists?(location_id)
|
10
|
+
file = File.open(location_file_name(location_id), 'rb')
|
11
|
+
file.read
|
12
|
+
end
|
13
|
+
|
14
|
+
def set(location_id, data)
|
15
|
+
ensure_file_location_exists(location_id)
|
16
|
+
File.open(location_file_name(location_id), 'w') { |file| file.write(data) }
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def exists?(location_id)
|
21
|
+
File.exists?(location_file_name(location_id))
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def ensure_file_location_exists(location_id)
|
27
|
+
FileUtils.mkdir_p location_directory
|
28
|
+
FileUtils.touch location_file_name(location_id.to_s)
|
29
|
+
end
|
30
|
+
|
31
|
+
def location_directory
|
32
|
+
@location_directory ||= ::File.join(data_dir, 'location')
|
33
|
+
end
|
34
|
+
|
35
|
+
def location_file_name(location_id)
|
36
|
+
File.join(location_directory, location_id.to_s)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'redis'
|
2
|
+
|
3
|
+
module Quandl
|
4
|
+
module Location
|
5
|
+
|
6
|
+
class Storage::Redis < Storage
|
7
|
+
|
8
|
+
attr_accessor :host, :port, :expiry, :encoding
|
9
|
+
|
10
|
+
# Retrieve the Redis store location id
|
11
|
+
#
|
12
|
+
# Returns string
|
13
|
+
def get(location_id)
|
14
|
+
data = connection.get location_id
|
15
|
+
data.force_encoding(encoding) if encodable?
|
16
|
+
data
|
17
|
+
end
|
18
|
+
|
19
|
+
# Save the data to Redis key location_id
|
20
|
+
# If Storage::Redis instance has expiry setting, then set expire time for the location
|
21
|
+
#
|
22
|
+
# Returns boolean
|
23
|
+
def set(location_id, data)
|
24
|
+
connection.set location_id, data
|
25
|
+
connection.expire(location_id, expiry) if expirable?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
# Check if key exists in Redis
|
30
|
+
#
|
31
|
+
# location_id - The Redis key to
|
32
|
+
#
|
33
|
+
# Returns boolean
|
34
|
+
def exists?(location_id)
|
35
|
+
connection.exists location_id
|
36
|
+
end
|
37
|
+
|
38
|
+
# Establish a connection to redis
|
39
|
+
#
|
40
|
+
# Returns boolean
|
41
|
+
def connect
|
42
|
+
connection
|
43
|
+
return true
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
# Determines if Storage instance is configured to override default encoding
|
49
|
+
#
|
50
|
+
# Returns boolean
|
51
|
+
def encodable?
|
52
|
+
self.encoding.present? and self.encoding.kind_of?(String)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Determines if the Storage instance is configured to set TTL for keys
|
56
|
+
#
|
57
|
+
# Returns boolean
|
58
|
+
def expirable?
|
59
|
+
self.expiry.present? and self.expiry.kind_of?(Fixnum)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Return a Redis connection
|
63
|
+
#
|
64
|
+
# Returns Redis::Client
|
65
|
+
# Raises Quandl::Location::StorageConfigurationError
|
66
|
+
def connection
|
67
|
+
unless @connection
|
68
|
+
unless host.present? and port.present?
|
69
|
+
raise(Quandl::Location::StorageConfigurationError, "Redis port and host required.")
|
70
|
+
end
|
71
|
+
@connection = ::Redis.new(host: host, port: port)
|
72
|
+
end
|
73
|
+
@connection
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'quandl/location/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "quandl_location"
|
8
|
+
spec.version = Quandl::Location::VERSION
|
9
|
+
spec.authors = ["Jason Byck"]
|
10
|
+
spec.email = ["jasonbyck@gmail.com"]
|
11
|
+
spec.description = %q{Provides a library for accessing data from arbitrary key-value storage}
|
12
|
+
spec.summary = %q{Abstracted key value storage library}
|
13
|
+
spec.homepage = "https://github.com/quandl/quandl_location"
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
21
|
+
spec.add_development_dependency 'rspec'
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "pry"
|
24
|
+
spec.add_development_dependency "fakeredis"
|
25
|
+
|
26
|
+
spec.add_runtime_dependency "redis"
|
27
|
+
spec.add_runtime_dependency "activesupport", "~> 3"
|
28
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Quandl::Location::Data do
|
4
|
+
|
5
|
+
let(:location_id) { (1..1000).to_a.sample }
|
6
|
+
let(:fake_data) { (0...8).map { (65 + rand(26)).chr }.join }
|
7
|
+
let(:data_object) { Quandl::Location::Data.new(location_id) }
|
8
|
+
let(:storage_mock) { storage_double = double('Quandl::Location::Storage') }
|
9
|
+
|
10
|
+
# Mock the storage implentation
|
11
|
+
before(:each) do
|
12
|
+
Quandl::Location::Data.any_instance.stub(:storage).and_return(storage_mock)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#find' do
|
16
|
+
|
17
|
+
it 'should return a new Quandl::Location::Data instance' do
|
18
|
+
Quandl::Location::Data.find(230).should be_kind_of(Quandl::Location::Data)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#data=' do
|
24
|
+
|
25
|
+
before(:each) do
|
26
|
+
data_object.data = fake_data
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'assign the data to be saved as raw_data' do
|
30
|
+
data_object.instance_variable_get(:@raw_data).should be fake_data
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should mark the data as changed' do
|
34
|
+
data_object.changed?.should be true
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#changed?' do
|
40
|
+
|
41
|
+
subject { data_object }
|
42
|
+
|
43
|
+
context 'data has been set' do
|
44
|
+
|
45
|
+
before(:each) { subject.data = fake_data }
|
46
|
+
|
47
|
+
its(:changed?) { should be true }
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'data has not been set' do
|
52
|
+
|
53
|
+
its(:changed?) { should be false }
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#data' do
|
60
|
+
|
61
|
+
it 'should request the data through the storage engine' do
|
62
|
+
storage_mock.should_receive(:get).with(location_id)
|
63
|
+
data_object.data
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '#save' do
|
69
|
+
|
70
|
+
context 'data is ready to be saved' do
|
71
|
+
|
72
|
+
before(:each) { data_object.data = fake_data }
|
73
|
+
|
74
|
+
it 'should persist data through the storage engine' do
|
75
|
+
storage_mock.should_receive(:set).with(location_id, fake_data)
|
76
|
+
data_object.save
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'data is not ready to be saved' do
|
82
|
+
|
83
|
+
it 'should return false' do
|
84
|
+
data_object.save.should be false
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '#exists?' do
|
93
|
+
|
94
|
+
it 'should return status through storage engine' do
|
95
|
+
storage_mock.should_receive(:exists?).with(location_id)
|
96
|
+
data_object.exists?
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Quandl::Location::Storage::FileSystem do
|
4
|
+
|
5
|
+
let(:data_dir) { '/etc/data' }
|
6
|
+
let(:location_id) { (1..1000).to_a.sample }
|
7
|
+
let(:fake_data) { (0...8).map { (65 + rand(26)).chr }.join }
|
8
|
+
let(:file_storage) do
|
9
|
+
f = Quandl::Location::Storage::FileSystem.new
|
10
|
+
f.with_options({ data_dir: data_dir })
|
11
|
+
f
|
12
|
+
end
|
13
|
+
|
14
|
+
it { should respond_to(:data_dir) }
|
15
|
+
|
16
|
+
before(:each) do
|
17
|
+
FileUtils.stub(:touch).and_return(true)
|
18
|
+
FileUtils.stub(:mkdir_p).and_return(true)
|
19
|
+
File.stub(:open).and_return(double('File'))
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#set' do
|
23
|
+
|
24
|
+
it 'should ensure the file location exists before writing data' do
|
25
|
+
FileUtils.should_receive(:touch).once
|
26
|
+
file_storage.set(location_id, fake_data)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should return true if successful' do
|
30
|
+
file_storage.set(location_id, fake_data).should be true
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#exists?' do
|
36
|
+
|
37
|
+
context 'location file exists' do
|
38
|
+
|
39
|
+
before(:each) { File.stub(:exists?).and_return(true) }
|
40
|
+
|
41
|
+
it 'should return true' do
|
42
|
+
file_storage.exists?(location_id).should be true
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'location file does not exist' do
|
48
|
+
|
49
|
+
before(:each) { File.stub(:exists?).and_return(false) }
|
50
|
+
|
51
|
+
it 'should return false' do
|
52
|
+
File.stub(:exists?).and_return(false)
|
53
|
+
file_storage.exists?(location_id).should be false
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#get' do
|
61
|
+
|
62
|
+
context 'location file exists' do
|
63
|
+
|
64
|
+
before(:each) do
|
65
|
+
File.stub(:exists?).and_return(true)
|
66
|
+
Object.any_instance.stub(:read).and_return(fake_data)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should return the saved location data' do
|
70
|
+
file_storage.get(location_id).should be fake_data
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'location file does not exist' do
|
76
|
+
|
77
|
+
before(:each) do
|
78
|
+
File.stub(:exists?).and_return(false)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should return nil' do
|
82
|
+
file_storage.get(location_id).should be nil
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Quandl::Location::Storage::Redis do
|
4
|
+
|
5
|
+
let(:redis_storage) do
|
6
|
+
r = Quandl::Location::Storage::Redis.new
|
7
|
+
r.with_options(host: 'localhost', port: 6379)
|
8
|
+
r
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:redis_storage_expiry) do
|
12
|
+
r = Quandl::Location::Storage::Redis.new
|
13
|
+
r.with_options(host: 'localhost', port: 6379, expiry: 1)
|
14
|
+
r
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:redis_storage_encoding) do
|
18
|
+
r = Quandl::Location::Storage::Redis.new
|
19
|
+
r.with_options(host: 'localhost', port: 6379, encoding: 'ASCII-8BIT')
|
20
|
+
r
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
let(:location_id) { (1..1000).to_a.sample }
|
25
|
+
let(:fake_data) { (0...8).map { (65 + rand(26)).chr }.join }
|
26
|
+
|
27
|
+
it { should respond_to(:host) }
|
28
|
+
it { should respond_to(:port) }
|
29
|
+
|
30
|
+
|
31
|
+
describe '#get' do
|
32
|
+
|
33
|
+
it 'should fetch data from Redis' do
|
34
|
+
Redis.any_instance.should_receive(:get)
|
35
|
+
redis_storage.get(location_id)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should return data saved to a location' do
|
39
|
+
redis_storage.set(location_id, fake_data)
|
40
|
+
redis_storage.get(location_id).should eq fake_data
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'encoding is configured' do
|
44
|
+
|
45
|
+
it 'should use force encoding from configuration' do
|
46
|
+
redis_storage_encoding.set(location_id, fake_data)
|
47
|
+
redis_storage_encoding.get(location_id).encoding.name.should eq redis_storage_encoding.encoding
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#exists?' do
|
55
|
+
|
56
|
+
context 'key is present in Redis' do
|
57
|
+
|
58
|
+
before(:each) { Redis.any_instance.stub(:exists).and_return(true) }
|
59
|
+
|
60
|
+
it 'should return true' do
|
61
|
+
redis_storage.exists?(location_id).should be true
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'key is not present in Redis' do
|
67
|
+
|
68
|
+
before(:each) { Redis.any_instance.stub(:exists).and_return(false) }
|
69
|
+
|
70
|
+
it 'should return false' do
|
71
|
+
redis_storage.exists?(location_id).should be false
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#set' do
|
79
|
+
|
80
|
+
it 'should persit the data to Redis' do
|
81
|
+
Redis.any_instance.should_receive(:set)
|
82
|
+
redis_storage.set(location_id, fake_data)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should return true if save was successful' do
|
86
|
+
redis_storage.set(location_id, fake_data).should be true
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should set expiry time if option present' do
|
90
|
+
Redis.any_instance.should_receive(:expire)
|
91
|
+
redis_storage_expiry.set(location_id, fake_data)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should not set expiry time if expiry option is not present' do
|
95
|
+
Redis.any_instance.should_not_receive(:expire)
|
96
|
+
redis_storage.set(location_id, fake_data)
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
describe '#connect' do
|
104
|
+
|
105
|
+
it 'should raise an error unless host and port are present' do
|
106
|
+
r = Quandl::Location::Storage::Redis.new
|
107
|
+
expect { r.connect }.to raise_error
|
108
|
+
r.host = 'test'
|
109
|
+
expect { r.connect }.to raise_error
|
110
|
+
r.host = nil
|
111
|
+
r.port = 'test'
|
112
|
+
expect { r.connect }.to raise_error
|
113
|
+
r.host = 'test'
|
114
|
+
expect { r.connect }.not_to raise_error
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Quandl::Location::Storage do
|
4
|
+
|
5
|
+
let(:storage) { Quandl::Location::Storage.new }
|
6
|
+
|
7
|
+
describe '#get' do
|
8
|
+
|
9
|
+
it 'should raise exception for incomplete implementation' do
|
10
|
+
expect { storage.get }.to raise_error
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#exists?' do
|
16
|
+
|
17
|
+
it 'should raiser for incomplete implentation' do
|
18
|
+
expect { storage.exists? }.to raise_error
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#set' do
|
24
|
+
|
25
|
+
it 'should raise exception for incomplete implementation' do
|
26
|
+
expect { storage.set }.to raise_error
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#with_options' do
|
32
|
+
|
33
|
+
context 'storage options not provided' do
|
34
|
+
|
35
|
+
it 'should raise exception' do
|
36
|
+
expect { storage.with_options }.to raise_error
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Quandl::Location do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
# Clear class variables between tests
|
7
|
+
Quandl::Location.class_variable_set(:@@storage_engine, nil)
|
8
|
+
Quandl::Location.class_variable_set(:@@storage_engine_type, nil)
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:valid_storage_engine) { Quandl::Location::storage_engines.sample }
|
12
|
+
|
13
|
+
describe '#storage_engines' do
|
14
|
+
|
15
|
+
it 'should return the supported storage engines' do
|
16
|
+
Quandl::Location.storage_engines.should be Quandl::Location::STORAGE_ENGINES
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#use' do
|
22
|
+
|
23
|
+
it 'it should raise an error for unsupported storage engines' do
|
24
|
+
expect { Quandl::Location.use(:quandl_fake_storage) }.to raise_error
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should allow supported storage engines' do
|
28
|
+
Quandl::Location.storage_engines.each do |engine|
|
29
|
+
expect { Quandl::Location.use(engine) }.not_to raise_error
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should send provided block as configuration to Storage instance' do
|
34
|
+
Quandl::Location::Storage.any_instance.should_receive(:my_configuration_function)
|
35
|
+
Quandl::Location.use(valid_storage_engine) { my_configuration_function }
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#storage_engine' do
|
41
|
+
|
42
|
+
context 'a storage engine has been initialized' do
|
43
|
+
|
44
|
+
it 'should raise an error' do
|
45
|
+
expect { Quandl::Location.storage_engine }.to raise_error
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'a storage engine has not been initialized' do
|
51
|
+
|
52
|
+
it 'should not raise an error' do
|
53
|
+
Quandl::Location.use(valid_storage_engine)
|
54
|
+
expect { Quandl::Location.storage_engine }.not_to raise_error
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
File without changes
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + "../lib")
|
2
|
+
|
3
|
+
# Testing
|
4
|
+
require 'rspec'
|
5
|
+
require 'fakeredis/rspec'
|
6
|
+
|
7
|
+
# Application
|
8
|
+
require 'quandl/location'
|
9
|
+
|
10
|
+
# Configure RSpec
|
11
|
+
RSpec.configure do |config|
|
12
|
+
|
13
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
14
|
+
config.formatter = :documentation
|
15
|
+
config.tty = true
|
16
|
+
config.color_enabled = true
|
17
|
+
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: quandl_location
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jason Byck
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-10-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
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: '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: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: pry
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: fakeredis
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: redis
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: activesupport
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '3'
|
126
|
+
description: Provides a library for accessing data from arbitrary key-value storage
|
127
|
+
email:
|
128
|
+
- jasonbyck@gmail.com
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- .gitignore
|
134
|
+
- .travis.yml
|
135
|
+
- CHANGELOG.md
|
136
|
+
- Gemfile
|
137
|
+
- LICENSE.txt
|
138
|
+
- README.md
|
139
|
+
- Rakefile
|
140
|
+
- lib/quandl/location.rb
|
141
|
+
- lib/quandl/location/data.rb
|
142
|
+
- lib/quandl/location/exceptions.rb
|
143
|
+
- lib/quandl/location/storage.rb
|
144
|
+
- lib/quandl/location/storage/file_system.rb
|
145
|
+
- lib/quandl/location/storage/redis.rb
|
146
|
+
- lib/quandl/location/version.rb
|
147
|
+
- quandl_location.gemspec
|
148
|
+
- spec/quandl/location/data_spec.rb
|
149
|
+
- spec/quandl/location/storage/file_system_spec.rb
|
150
|
+
- spec/quandl/location/storage/redis_spec.rb
|
151
|
+
- spec/quandl/location/storage_spec.rb
|
152
|
+
- spec/quandl/location_spec.rb
|
153
|
+
- spec/quandl/support/fake_redis.rb
|
154
|
+
- spec/spec_helper.rb
|
155
|
+
- spec/support/config/file_storage.yml
|
156
|
+
- spec/support/config/redis.yml
|
157
|
+
homepage: https://github.com/quandl/quandl_location
|
158
|
+
licenses: []
|
159
|
+
post_install_message:
|
160
|
+
rdoc_options: []
|
161
|
+
require_paths:
|
162
|
+
- lib
|
163
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
164
|
+
none: false
|
165
|
+
requirements:
|
166
|
+
- - ! '>='
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
170
|
+
none: false
|
171
|
+
requirements:
|
172
|
+
- - ! '>='
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
requirements: []
|
176
|
+
rubyforge_project:
|
177
|
+
rubygems_version: 1.8.23
|
178
|
+
signing_key:
|
179
|
+
specification_version: 3
|
180
|
+
summary: Abstracted key value storage library
|
181
|
+
test_files:
|
182
|
+
- spec/quandl/location/data_spec.rb
|
183
|
+
- spec/quandl/location/storage/file_system_spec.rb
|
184
|
+
- spec/quandl/location/storage/redis_spec.rb
|
185
|
+
- spec/quandl/location/storage_spec.rb
|
186
|
+
- spec/quandl/location_spec.rb
|
187
|
+
- spec/quandl/support/fake_redis.rb
|
188
|
+
- spec/spec_helper.rb
|
189
|
+
- spec/support/config/file_storage.yml
|
190
|
+
- spec/support/config/redis.yml
|