slipcover 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +96 -0
- data/Rakefile +1 -0
- data/lib/slipcover.rb +15 -0
- data/lib/slipcover/config.rb +22 -0
- data/lib/slipcover/database.rb +40 -0
- data/lib/slipcover/design_document.rb +57 -0
- data/lib/slipcover/document.rb +77 -0
- data/lib/slipcover/http_adapter.rb +74 -0
- data/lib/slipcover/query.rb +45 -0
- data/lib/slipcover/railtie.rb +7 -0
- data/lib/slipcover/server.rb +30 -0
- data/lib/slipcover/version.rb +3 -0
- data/slipcover.gemspec +27 -0
- data/spec/database_spec.rb +81 -0
- data/spec/design_document_spec.rb +56 -0
- data/spec/document_spec.rb +157 -0
- data/spec/http_adapter_spec.rb +32 -0
- data/spec/query_spec.rb +55 -0
- data/spec/server_spec.rb +24 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/slipcover.yml +8 -0
- data/spec/support/slipcover_views/by_name/map.js +3 -0
- metadata +154 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ad54f411cbdcf51d91104e6a64a0ab1e3b5e8c47
|
4
|
+
data.tar.gz: 5a438e303c5409b40a23ff201fa9559b50d73bc9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0115fb08c97b683454d17a889c2506aabc017627007e60b57bb937c2aae9ebc4b6d216be24e28a6cb7cdc383b180bb1b8f23fb73a5e559f6eb51210d5b862dcb
|
7
|
+
data.tar.gz: 5327028a2b60a08526415b984d037ad0d04abfbf3ba6cc6cceae41df754415b4f460f40ae8977ee872ee3047ac34f4c49dc3883e2aaf950c7ba7470faa665c07
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use ruby-2.0.0@slipcover --create
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 socialchorus
|
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,96 @@
|
|
1
|
+
# Slipcover
|
2
|
+
|
3
|
+
Slipcover is a really lite wrapper around CouchDB. It comes with a Railtie to get your Rails app setup fast.
|
4
|
+
It is YAML configured allowing easy connection to servers, without imposing database constraints ... since
|
5
|
+
one of the best parts of CouchDB is the ease at which you can create new databases.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'slipcover'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install slipcover
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### With Rails
|
24
|
+
Put a `slipcover.yml` in your config directory. Here is a sample:
|
25
|
+
|
26
|
+
development:
|
27
|
+
host: 127.0.0.1
|
28
|
+
port: 5984
|
29
|
+
|
30
|
+
production:
|
31
|
+
host: socialcoders.couchservice.com
|
32
|
+
username: username
|
33
|
+
password: password
|
34
|
+
|
35
|
+
#### Create a database:
|
36
|
+
|
37
|
+
Slipcover::Database.new('my_database_name').create
|
38
|
+
|
39
|
+
This will create a database with the Rails environment appended to the back of it: `database_name_development`.
|
40
|
+
This is a great convenience for testing etc, since you can create many database on the fly and they won't conflict
|
41
|
+
with development or other databases of the same name.
|
42
|
+
|
43
|
+
#### Create some documents:
|
44
|
+
|
45
|
+
doc = Slipcover::Document.new('my_database_name', {foo: ['a', 'row', 'of', 'bars']})
|
46
|
+
doc.save
|
47
|
+
|
48
|
+
#### Write views is js to query them:
|
49
|
+
|
50
|
+
Create a directory inside the Rails app called: `slipcover_views`. Organize each view/index in its own directory with a `map.js`
|
51
|
+
file. Where appropriate also add a `reduce.js` function. Since these files are real js files, they can be tested via Jasmine,
|
52
|
+
or your favorite testing framework.
|
53
|
+
|
54
|
+
#### Put a design document in your database to capture these views/indexes
|
55
|
+
|
56
|
+
design_doc = Slipcover::DesignDocument.new('my_database_name', 'design_it_good')
|
57
|
+
design_doc.save
|
58
|
+
|
59
|
+
It constructs views from the default app/slipcover_views directory. If you want to customize where the views live, do some
|
60
|
+
dependency injection:
|
61
|
+
|
62
|
+
design_doc = Slipcover::DesignDocument.new('my_database_name', 'degign_it_good', my_custom_directory)
|
63
|
+
design_doc.save
|
64
|
+
|
65
|
+
#### Query Data
|
66
|
+
|
67
|
+
desgin_document = Slipcover::DesignDocument.new('my_database_name', 'degign_it_good')
|
68
|
+
query = Slipcover::Query.new(design_document, view_name)
|
69
|
+
query.all
|
70
|
+
query.all(key: 'foo')
|
71
|
+
|
72
|
+
### Usages Sans-Rails
|
73
|
+
|
74
|
+
Put a YAML configuration file where it seems appropriate. Configure slipcover so that it knows where your YAML is:
|
75
|
+
|
76
|
+
Slipcover::Config.yaml_path = my_special_place
|
77
|
+
|
78
|
+
Let Slipcover know what environment your are operating under:
|
79
|
+
|
80
|
+
Slipcover.env = 'staging'
|
81
|
+
|
82
|
+
When constructing databases, Slipcover will use your yaml and environmental configuration to setup the right server.
|
83
|
+
|
84
|
+
If you are looking for more freedom, you can created databases with an optional second argument that is a server.
|
85
|
+
|
86
|
+
The slipcover views can also be put wherever you want and configured in at the Slipcover::Config level or the individual
|
87
|
+
design document.
|
88
|
+
|
89
|
+
|
90
|
+
## Contributing
|
91
|
+
|
92
|
+
1. Fork it
|
93
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
94
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
95
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
96
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/slipcover.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'rest-client'
|
3
|
+
require 'active_support/core_ext/hash'
|
4
|
+
require 'active_support/core_ext/module/delegation'
|
5
|
+
|
6
|
+
require "slipcover/version"
|
7
|
+
require "slipcover/http_adapter"
|
8
|
+
require "slipcover/server"
|
9
|
+
require "slipcover/config"
|
10
|
+
require "slipcover/database"
|
11
|
+
require "slipcover/document"
|
12
|
+
require "slipcover/design_document"
|
13
|
+
require "slipcover/query"
|
14
|
+
|
15
|
+
require 'slipcover/railtie' if defined?(Rails)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Slipcover
|
2
|
+
class Config
|
3
|
+
class << self
|
4
|
+
attr_accessor :view_dir, :yaml_path
|
5
|
+
attr_writer :env, :server
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.env
|
9
|
+
@env ||= ENV['RACK_ENV'] || 'development'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.server
|
13
|
+
@server ||= Server.new(yaml_path, env)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.rails!
|
17
|
+
self.env = Rails.env
|
18
|
+
self.yaml_path = Rails.root.join('config/slipcover.yml')
|
19
|
+
self.view_dir = Rails.root.join("/app/slipcover_views")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Slipcover
|
2
|
+
class Database
|
3
|
+
attr_reader :name, :server
|
4
|
+
|
5
|
+
def initialize(name, server=nil)
|
6
|
+
@name = name
|
7
|
+
@server = server || Slipcover::Config.server
|
8
|
+
end
|
9
|
+
|
10
|
+
def url
|
11
|
+
"#{server.url}/#{name}_#{Slipcover::Config.env}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def http_adapter
|
15
|
+
@http_adapter ||= HttpAdapter.new
|
16
|
+
end
|
17
|
+
|
18
|
+
delegate :get, :post, :put,
|
19
|
+
to: :http_adapter
|
20
|
+
|
21
|
+
def create
|
22
|
+
put(url)
|
23
|
+
rescue HttpAdapter::Conflict
|
24
|
+
# handled by the ensure, move along
|
25
|
+
ensure
|
26
|
+
return info
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete
|
30
|
+
http_adapter.delete(url)
|
31
|
+
true
|
32
|
+
rescue Exception => e
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def info
|
37
|
+
get(url)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Slipcover
|
2
|
+
class DesignDocument
|
3
|
+
attr_reader :database_name, :name, :view_dir
|
4
|
+
|
5
|
+
def initialize(database_name, name, view_dir=nil)
|
6
|
+
@database_name = database_name
|
7
|
+
@name = name
|
8
|
+
@view_dir = view_dir || Slipcover::Config.view_dir
|
9
|
+
end
|
10
|
+
|
11
|
+
def document
|
12
|
+
@document ||= Document.new(database_name, {
|
13
|
+
id: document_id,
|
14
|
+
language: 'javascript',
|
15
|
+
views: views
|
16
|
+
})
|
17
|
+
end
|
18
|
+
|
19
|
+
delegate :url, :attributes, :id, :rev, :delete, :database,
|
20
|
+
to: :document
|
21
|
+
|
22
|
+
def fetch
|
23
|
+
document.fetch
|
24
|
+
document.attributes[:views].symbolize_keys!
|
25
|
+
document.attributes[:views].each {|key, hash| document.attributes[:views][key] = hash.symbolize_keys!}
|
26
|
+
document
|
27
|
+
end
|
28
|
+
|
29
|
+
def save
|
30
|
+
document.save
|
31
|
+
rescue HttpAdapter::ConflictError
|
32
|
+
document.fetch
|
33
|
+
document[:views] = views
|
34
|
+
document.save
|
35
|
+
end
|
36
|
+
|
37
|
+
def document_id
|
38
|
+
"_design/#{name}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def [](key)
|
42
|
+
document[:views][key]
|
43
|
+
end
|
44
|
+
|
45
|
+
def views
|
46
|
+
Dir.entries(view_dir).inject({}) do |hash, path|
|
47
|
+
if !path.match(/\./)
|
48
|
+
dir = view_dir + "/" + path
|
49
|
+
hash[path.to_sym] ||= {}
|
50
|
+
hash[path.to_sym][:map] = File.read(dir + "/map.js")
|
51
|
+
hash[path.to_sym][:reduce] = File.read(dir + "/reduce.js") if File.exist?(dir + "/reduce.js")
|
52
|
+
end
|
53
|
+
hash
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Slipcover
|
2
|
+
class Document
|
3
|
+
attr_accessor :attributes, :id, :rev
|
4
|
+
attr_reader :database_name
|
5
|
+
|
6
|
+
def initialize(database_name, attributes={})
|
7
|
+
@database_name = database_name
|
8
|
+
self.attributes = attributes.symbolize_keys
|
9
|
+
set_intrinsic_values
|
10
|
+
end
|
11
|
+
|
12
|
+
delegate :[], :[]=, to: :attributes
|
13
|
+
|
14
|
+
def http_adapter
|
15
|
+
@http_adapter ||= HttpAdapter.new
|
16
|
+
end
|
17
|
+
|
18
|
+
delegate :get, :post, :put,
|
19
|
+
to: :http_adapter
|
20
|
+
|
21
|
+
def save
|
22
|
+
http_method = id ? :put : :post
|
23
|
+
doc_url = id ? url : database.url
|
24
|
+
|
25
|
+
response = send(http_method, doc_url, attributes_for_save)
|
26
|
+
set_intrinsic_values(response)
|
27
|
+
end
|
28
|
+
|
29
|
+
def fetch
|
30
|
+
self.attributes = get(url)
|
31
|
+
set_intrinsic_values
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete
|
35
|
+
http_adapter.delete("#{url}?rev=#{rev}")
|
36
|
+
set_intrinsic_values({})
|
37
|
+
nullify_intrinsic_attributes
|
38
|
+
true
|
39
|
+
rescue Exception => e
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def url
|
44
|
+
raise ArgumentError.new('no document id') unless id
|
45
|
+
"#{database.url}/#{id}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def database
|
49
|
+
return @database if @database
|
50
|
+
@database = Slipcover::Database.new(database_name)
|
51
|
+
@database.create
|
52
|
+
@database
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def attributes_for_save
|
58
|
+
attrs = attributes.clone
|
59
|
+
attrs[:_rev] = rev if rev
|
60
|
+
attrs[:_id] = id if id
|
61
|
+
attrs
|
62
|
+
end
|
63
|
+
|
64
|
+
def set_intrinsic_values(attrs=nil)
|
65
|
+
attrs ||= attributes
|
66
|
+
self.id = attrs[:id] || attrs[:_id]
|
67
|
+
self.rev = attrs[:rev] || attrs[:_rev]
|
68
|
+
end
|
69
|
+
|
70
|
+
def nullify_intrinsic_attributes
|
71
|
+
attributes[:_id] = nil
|
72
|
+
attributes[:id] = nil
|
73
|
+
attributes[:_rev] = nil
|
74
|
+
attributes[:rev] = nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Slipcover
|
2
|
+
class HttpAdapter
|
3
|
+
def get(url, data={})
|
4
|
+
try {
|
5
|
+
parse( RestClient.get(url + query_string(data), headers) )
|
6
|
+
}
|
7
|
+
end
|
8
|
+
|
9
|
+
def post(url, data={})
|
10
|
+
try {
|
11
|
+
parse( RestClient.post(url, data.to_json, headers) )
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def put(url, data={})
|
16
|
+
try {
|
17
|
+
parse( RestClient.put(url, data.to_json, headers) )
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete(url, data={})
|
22
|
+
try {
|
23
|
+
parse( RestClient.delete(url + query_string(data), headers) )
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def query_string(data)
|
28
|
+
return "" if data.empty?
|
29
|
+
|
30
|
+
query = data.map do |key, value|
|
31
|
+
"#{key}=#{value}"
|
32
|
+
end.join("&")
|
33
|
+
|
34
|
+
"?#{query}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse(response)
|
38
|
+
JSON.parse(response).symbolize_keys
|
39
|
+
end
|
40
|
+
|
41
|
+
def try
|
42
|
+
yield
|
43
|
+
rescue Exception => e
|
44
|
+
reraise(e)
|
45
|
+
end
|
46
|
+
|
47
|
+
def reraise(e)
|
48
|
+
raise error_class(e).new(e.message)
|
49
|
+
end
|
50
|
+
|
51
|
+
def error_class(e)
|
52
|
+
{
|
53
|
+
JSON::ParserError => ParseError,
|
54
|
+
RestClient::ResourceNotFound => NotFound,
|
55
|
+
RestClient::PreconditionFailed => ConflictError,
|
56
|
+
RestClient::Conflict => ConflictError
|
57
|
+
|
58
|
+
}[e.class] || e.class
|
59
|
+
end
|
60
|
+
|
61
|
+
class ConflictError < RuntimeError
|
62
|
+
end
|
63
|
+
|
64
|
+
class ParseError < RuntimeError
|
65
|
+
end
|
66
|
+
|
67
|
+
class NotFound < RuntimeError
|
68
|
+
end
|
69
|
+
|
70
|
+
def headers
|
71
|
+
{:content_type => :json, :accept => :json}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Slipcover
|
2
|
+
class Query
|
3
|
+
attr_reader :design_document, :view_name
|
4
|
+
|
5
|
+
def initialize(design_document, view_name)
|
6
|
+
@design_document = design_document
|
7
|
+
@view_name = view_name
|
8
|
+
end
|
9
|
+
|
10
|
+
def http_adapter
|
11
|
+
@http_adapter ||= HttpAdapter.new
|
12
|
+
end
|
13
|
+
|
14
|
+
delegate :get,
|
15
|
+
to: :http_adapter
|
16
|
+
|
17
|
+
def url
|
18
|
+
"#{design_document.url}/_view/#{view_name}" # todo adapter takes opts and converts to query string
|
19
|
+
end
|
20
|
+
|
21
|
+
def all(opts={})
|
22
|
+
get(url, repackage(opts))[:rows].map{|row| Document.new(database.name, row['doc'].symbolize_keys) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def repackage(opts)
|
26
|
+
opts.each do |key, value|
|
27
|
+
opts[key] = escape(value) if escape_key?(key)
|
28
|
+
end
|
29
|
+
|
30
|
+
{include_docs: true}.merge(opts)
|
31
|
+
end
|
32
|
+
|
33
|
+
def database
|
34
|
+
design_document.database
|
35
|
+
end
|
36
|
+
|
37
|
+
def escape(value)
|
38
|
+
URI.escape(value.inspect)
|
39
|
+
end
|
40
|
+
|
41
|
+
def escape_key?(key)
|
42
|
+
[:key, :keys, :startkey, :startkey_docid, :endkey, :endkey_docid, :stale].include?(key.to_sym)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Slipcover
|
2
|
+
class Server < Struct.new(:path, :key)
|
3
|
+
def server_configs
|
4
|
+
YAML.load(File.read(path))
|
5
|
+
end
|
6
|
+
|
7
|
+
def config
|
8
|
+
@config ||= server_configs[key]
|
9
|
+
end
|
10
|
+
|
11
|
+
def url
|
12
|
+
"#{user_info}#{host}#{port}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def user_info
|
16
|
+
info = config['username'] || ""
|
17
|
+
info << ":#{config['password']}" if config['password']
|
18
|
+
info << "@" unless info.empty?
|
19
|
+
info
|
20
|
+
end
|
21
|
+
|
22
|
+
def host
|
23
|
+
config['host']
|
24
|
+
end
|
25
|
+
|
26
|
+
def port
|
27
|
+
config['port'] ? ":#{config['port']}" : ''
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/slipcover.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'slipcover/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "slipcover"
|
8
|
+
spec.version = Slipcover::VERSION
|
9
|
+
spec.authors = ["Kane Baccigalupi", "Deepti Anand", "Fito von Zastow", 'SocialCoders @ SocialChorus']
|
10
|
+
spec.email = ["baccigalupi@gmail.com", "developers@socialchorus.com"]
|
11
|
+
spec.description = %q{Lite wrapper for CouchDB}
|
12
|
+
spec.summary = %q{Lite wrapper for CouchDB}
|
13
|
+
spec.homepage = "http://github.com/socialchorus/slipcover"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "rest-client"
|
22
|
+
spec.add_dependency 'activesupport'
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency 'rspec'
|
27
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Slipcover::Database, 'functional to the Slipcover database' do
|
4
|
+
let(:database) { Slipcover::Database.new(name, server) }
|
5
|
+
let(:name) { 'database_name' }
|
6
|
+
let(:server) { Slipcover::Server.new(File.dirname(__FILE__) + "/support/slipcover.yml", 'development') }
|
7
|
+
let(:database_url) { "#{database.server.url}/database_name_development" }
|
8
|
+
|
9
|
+
def ensure_database
|
10
|
+
RestClient.delete(database_url) rescue nil
|
11
|
+
RestClient.put(database_url, {}.to_json, {:content_type => :json, :accept => :json})
|
12
|
+
end
|
13
|
+
|
14
|
+
after do
|
15
|
+
RestClient.delete(database_url) rescue nil
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#info' do
|
19
|
+
before do
|
20
|
+
ensure_database
|
21
|
+
end
|
22
|
+
|
23
|
+
it "return information about the database" do
|
24
|
+
info = database.info
|
25
|
+
info.should be_a(Hash)
|
26
|
+
info.keys.should include(:doc_count)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#create" do
|
31
|
+
context 'database does not exist' do
|
32
|
+
before do
|
33
|
+
RestClient.delete(database_url) rescue nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'creates the database' do
|
37
|
+
info = database.create
|
38
|
+
info.should be_a(Hash)
|
39
|
+
info.keys.should include(:doc_count)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'database already exists' do
|
44
|
+
before do
|
45
|
+
ensure_database
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'returns info about the existing database' do
|
49
|
+
info = database.create
|
50
|
+
info.should be_a(Hash)
|
51
|
+
info.keys.should include(:doc_count)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#delete" do
|
57
|
+
context 'database exists' do
|
58
|
+
before do
|
59
|
+
ensure_database
|
60
|
+
end
|
61
|
+
|
62
|
+
it "deletes the database" do
|
63
|
+
database.delete.should == true
|
64
|
+
|
65
|
+
expect {
|
66
|
+
database.info
|
67
|
+
}.to raise_error( Slipcover::HttpAdapter::NotFound )
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'database does not exist' do
|
72
|
+
before do
|
73
|
+
RestClient.delete(database_url) rescue nil
|
74
|
+
end
|
75
|
+
|
76
|
+
it "returns false" do
|
77
|
+
database.delete.should == false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Slipcover::DesignDocument do
|
4
|
+
let(:document) { Slipcover::DesignDocument.new(database_name, name, view_dir) }
|
5
|
+
let(:database_name) { 'design_me' }
|
6
|
+
let(:database) { Slipcover::Database.new(database_name) }
|
7
|
+
let(:name) { 'designation' }
|
8
|
+
let(:view_dir) { support_dir + "/slipcover_views" }
|
9
|
+
|
10
|
+
let(:support_dir) { File.dirname(__FILE__) + "/support" }
|
11
|
+
let(:map_function) { File.read(view_dir + "/by_name/map.js") }
|
12
|
+
|
13
|
+
before do
|
14
|
+
database.delete
|
15
|
+
database.create
|
16
|
+
end
|
17
|
+
|
18
|
+
it "defaults to javascript as a language" do
|
19
|
+
document.attributes[:language].should == 'javascript'
|
20
|
+
end
|
21
|
+
|
22
|
+
it "has the right url" do
|
23
|
+
document.url.should == "#{database.url}/_design/designation"
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'views' do
|
27
|
+
it "finds them via the mapping of the name to the view directory" do
|
28
|
+
document[:by_name][:map].should == map_function
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#save' do
|
33
|
+
context "when the design document does not yet exist" do
|
34
|
+
it "saves it and delegates good stuff to it" do
|
35
|
+
document.save
|
36
|
+
document.fetch
|
37
|
+
document[:by_name][:map].should == map_function
|
38
|
+
document.id.should_not be_nil
|
39
|
+
document.rev.should_not be_nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'when the design document already exists' do
|
44
|
+
it "updates it without raising an error" do
|
45
|
+
document.save
|
46
|
+
|
47
|
+
duplicate_document = Slipcover::DesignDocument.new(database_name, name, view_dir)
|
48
|
+
|
49
|
+
duplicate_document.save
|
50
|
+
duplicate_document.rev.should_not be_nil
|
51
|
+
|
52
|
+
# expect { duplicate_document.save }.not_to raise_error
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Slipcover::Document, 'functional' do
|
4
|
+
let(:document) { Slipcover::Document.new(database_name, attributes) }
|
5
|
+
let(:database) { Slipcover::Database.new(database_name) }
|
6
|
+
let(:database_name) { 'hello_database' }
|
7
|
+
let(:attributes) {
|
8
|
+
{
|
9
|
+
type: 'foo',
|
10
|
+
author: 'something'
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
before do
|
15
|
+
Slipcover::Config.server ||= Slipcover::Server.new(File.dirname(__FILE__) + "/support/slipcover.yml", 'development')
|
16
|
+
database.create
|
17
|
+
end
|
18
|
+
|
19
|
+
after do
|
20
|
+
database.delete
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#initialize' do
|
24
|
+
let(:attributes) {
|
25
|
+
{
|
26
|
+
id: '298c2fd111c14633e445ef818b039ff3-us',
|
27
|
+
rev: '2-298c2fd111c14633e445ef818b039ff3',
|
28
|
+
type: 'foo',
|
29
|
+
author: 'something'
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
it "has an id" do
|
34
|
+
document.id.should == attributes[:id]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "has a rev" do
|
38
|
+
document.rev.should == attributes[:rev]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#fetch' do
|
43
|
+
before do
|
44
|
+
document.save
|
45
|
+
document.rev = nil
|
46
|
+
document.attributes = {}
|
47
|
+
document.fetch
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should return all the attributes" do
|
51
|
+
document.attributes[:type].should == 'foo'
|
52
|
+
document.attributes[:author].should == 'something'
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should update the rev" do
|
56
|
+
document.fetch
|
57
|
+
document.rev.should_not be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should raise an error if id does not exist' do
|
61
|
+
document.id = nil
|
62
|
+
expect{ document.fetch }.to raise_error
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#save' do
|
67
|
+
context 'when there is no id' do
|
68
|
+
before do
|
69
|
+
document.save
|
70
|
+
end
|
71
|
+
|
72
|
+
it "gets the id from the server response" do
|
73
|
+
document.id.should_not be_nil
|
74
|
+
end
|
75
|
+
|
76
|
+
it "gets the rev from the server response" do
|
77
|
+
document.rev.should_not be_nil
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should maintains its local attributes' do
|
81
|
+
document.attributes[:type].should == 'foo'
|
82
|
+
document.attributes[:author].should == 'something'
|
83
|
+
end
|
84
|
+
|
85
|
+
it "is stored in the database, and available" do
|
86
|
+
expect {
|
87
|
+
RestClient.get(document.url)
|
88
|
+
}.not_to raise_error
|
89
|
+
document.fetch
|
90
|
+
document.attributes[:type].should == 'foo'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'when there is an id passed in' do
|
95
|
+
before do
|
96
|
+
document.save
|
97
|
+
document.attributes[:bar] = 'what?'
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'gets a new revision' do
|
101
|
+
original_rev = document.rev.clone
|
102
|
+
document.save
|
103
|
+
document.rev.should_not == original_rev
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'updates the database' do
|
107
|
+
document.save
|
108
|
+
document.fetch
|
109
|
+
document.attributes[:bar].should == 'what?'
|
110
|
+
end
|
111
|
+
|
112
|
+
it "does not create another document" do
|
113
|
+
document.database.info[:doc_count].should == 1
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '#delete' do
|
119
|
+
context 'when the document is on the server' do
|
120
|
+
before do
|
121
|
+
document.save
|
122
|
+
document[:_rev] = '_rev'
|
123
|
+
document[:rev] = 'rev'
|
124
|
+
document[:_id] = '_id'
|
125
|
+
document[:id] = 'id'
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'should delete the record from the database' do
|
129
|
+
url = document.url
|
130
|
+
document.delete
|
131
|
+
expect {
|
132
|
+
RestClient.get(url)
|
133
|
+
}.to raise_error
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should return true" do
|
137
|
+
document.delete.should == true
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'should clear the id and rev' do
|
141
|
+
document.delete
|
142
|
+
document.id.should be_nil
|
143
|
+
document.rev.should be_nil
|
144
|
+
document[:_id].should be_nil
|
145
|
+
document[:id].should be_nil
|
146
|
+
document[:_rev].should be_nil
|
147
|
+
document[:rev].should be_nil
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'when the document is not on the sever' do
|
152
|
+
it "should return false" do
|
153
|
+
document.delete.should == false
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Slipcover::HttpAdapter do
|
4
|
+
let(:adapter) { Slipcover::HttpAdapter.new }
|
5
|
+
|
6
|
+
|
7
|
+
describe '#get' do
|
8
|
+
let(:response) { {}.to_json }
|
9
|
+
|
10
|
+
before do
|
11
|
+
RestClient.stub(:get).and_return(response)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should package up data into a query string" do
|
15
|
+
RestClient.should_receive(:get).with('http://url.com?foo=bar', anything).and_return(response)
|
16
|
+
adapter.get('http://url.com', {foo: 'bar'})
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#delete' do
|
21
|
+
let(:response) { {}.to_json }
|
22
|
+
|
23
|
+
before do
|
24
|
+
RestClient.stub(:delete).and_return(response)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should package up data into a query string" do
|
28
|
+
RestClient.should_receive(:delete).with('http://url.com?foo=bar', anything).and_return(response)
|
29
|
+
adapter.delete('http://url.com', {foo: 'bar'})
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/spec/query_spec.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Slipcover::Query do
|
4
|
+
let(:query) { Slipcover::Query.new(design_document, :by_name) }
|
5
|
+
let(:design_document) { Slipcover::DesignDocument.new(database_name, name, view_dir) }
|
6
|
+
|
7
|
+
let(:database_name) { 'my_database_name' }
|
8
|
+
let(:name) { 'designation' }
|
9
|
+
let(:view_dir) { support_dir + "/slipcover_views" }
|
10
|
+
let(:support_dir) { File.dirname(__FILE__) + "/support" }
|
11
|
+
|
12
|
+
before do
|
13
|
+
db = Slipcover::Database.new(database_name)
|
14
|
+
db.delete
|
15
|
+
db.create
|
16
|
+
|
17
|
+
design_document.save
|
18
|
+
|
19
|
+
Slipcover::Document.new(database_name, {name: 'Deepti'}).save
|
20
|
+
Slipcover::Document.new(database_name, {name: 'Fito'}).save
|
21
|
+
Slipcover::Document.new(database_name, {name: 'Kane'}).save
|
22
|
+
Slipcover::Document.new(database_name, {animal: 'gerbil'}).save
|
23
|
+
end
|
24
|
+
|
25
|
+
after do
|
26
|
+
db = Slipcover::Database.new(database_name)
|
27
|
+
db.delete
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#all' do
|
31
|
+
it 'returns all the documents that match the index/view' do
|
32
|
+
results = query.all
|
33
|
+
results.size.should == 3
|
34
|
+
results.map {|doc| doc[:name] }.should =~ ['Deepti', 'Fito', 'Kane']
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with options' do
|
38
|
+
context 'with key' do
|
39
|
+
it "returns only the related records" do
|
40
|
+
results = query.all({key: 'Deepti'})
|
41
|
+
results.size.should == 1
|
42
|
+
results.first[:name].should == 'Deepti'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'with a startkey and endkey' do
|
47
|
+
it "return the range of docs" do
|
48
|
+
results = query.all({startkey: 'Da', endkey: 'Fz'})
|
49
|
+
results.size.should == 2
|
50
|
+
results.map{ |doc| doc[:name] }.should =~ ['Deepti', 'Fito']
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/spec/server_spec.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Slipcover::Server do
|
4
|
+
let(:server) { Slipcover::Server.new(config_path, env) }
|
5
|
+
let(:config_path) { File.dirname(__FILE__) + "/support/slipcover.yml" }
|
6
|
+
let(:env) { 'development' }
|
7
|
+
|
8
|
+
context "when the host is a local address" do
|
9
|
+
describe "#url" do
|
10
|
+
it 'returns the right url' do
|
11
|
+
server.url.should == "127.0.0.1:5984"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "when the host is a remote address" do
|
17
|
+
let(:env) { 'production' }
|
18
|
+
describe "#url" do
|
19
|
+
it 'returns the right url' do
|
20
|
+
server.url.should == "username:password@socialcoders.couchserver.com"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
$: << File.dirname(__FILE__) + "/../lib"
|
8
|
+
require 'slipcover'
|
9
|
+
|
10
|
+
Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each { |f| require f }
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
14
|
+
config.run_all_when_everything_filtered = true
|
15
|
+
config.filter_run :focus
|
16
|
+
|
17
|
+
# Run specs in random order to surface order dependencies. If you find an
|
18
|
+
# order dependency and want to debug it, you can fix the order by providing
|
19
|
+
# the seed, which is printed after each run.
|
20
|
+
# --seed 1234
|
21
|
+
config.order = 'random'
|
22
|
+
|
23
|
+
config.before do
|
24
|
+
Slipcover::Config.yaml_path = File.dirname(__FILE__) + "/support/slipcover.yml"
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: slipcover
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kane Baccigalupi
|
8
|
+
- Deepti Anand
|
9
|
+
- Fito von Zastow
|
10
|
+
- SocialCoders @ SocialChorus
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2014-05-14 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rest-client
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activesupport
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - '>='
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: bundler
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ~>
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '1.3'
|
51
|
+
type: :development
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ~>
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '1.3'
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rake
|
60
|
+
requirement: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
type: :development
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: rspec
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
type: :development
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
description: Lite wrapper for CouchDB
|
87
|
+
email:
|
88
|
+
- baccigalupi@gmail.com
|
89
|
+
- developers@socialchorus.com
|
90
|
+
executables: []
|
91
|
+
extensions: []
|
92
|
+
extra_rdoc_files: []
|
93
|
+
files:
|
94
|
+
- .gitignore
|
95
|
+
- .rspec
|
96
|
+
- .rvmrc
|
97
|
+
- Gemfile
|
98
|
+
- LICENSE.txt
|
99
|
+
- README.md
|
100
|
+
- Rakefile
|
101
|
+
- lib/slipcover.rb
|
102
|
+
- lib/slipcover/config.rb
|
103
|
+
- lib/slipcover/database.rb
|
104
|
+
- lib/slipcover/design_document.rb
|
105
|
+
- lib/slipcover/document.rb
|
106
|
+
- lib/slipcover/http_adapter.rb
|
107
|
+
- lib/slipcover/query.rb
|
108
|
+
- lib/slipcover/railtie.rb
|
109
|
+
- lib/slipcover/server.rb
|
110
|
+
- lib/slipcover/version.rb
|
111
|
+
- slipcover.gemspec
|
112
|
+
- spec/database_spec.rb
|
113
|
+
- spec/design_document_spec.rb
|
114
|
+
- spec/document_spec.rb
|
115
|
+
- spec/http_adapter_spec.rb
|
116
|
+
- spec/query_spec.rb
|
117
|
+
- spec/server_spec.rb
|
118
|
+
- spec/spec_helper.rb
|
119
|
+
- spec/support/slipcover.yml
|
120
|
+
- spec/support/slipcover_views/by_name/map.js
|
121
|
+
homepage: http://github.com/socialchorus/slipcover
|
122
|
+
licenses:
|
123
|
+
- MIT
|
124
|
+
metadata: {}
|
125
|
+
post_install_message:
|
126
|
+
rdoc_options: []
|
127
|
+
require_paths:
|
128
|
+
- lib
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
requirements: []
|
140
|
+
rubyforge_project:
|
141
|
+
rubygems_version: 2.0.3
|
142
|
+
signing_key:
|
143
|
+
specification_version: 4
|
144
|
+
summary: Lite wrapper for CouchDB
|
145
|
+
test_files:
|
146
|
+
- spec/database_spec.rb
|
147
|
+
- spec/design_document_spec.rb
|
148
|
+
- spec/document_spec.rb
|
149
|
+
- spec/http_adapter_spec.rb
|
150
|
+
- spec/query_spec.rb
|
151
|
+
- spec/server_spec.rb
|
152
|
+
- spec/spec_helper.rb
|
153
|
+
- spec/support/slipcover.yml
|
154
|
+
- spec/support/slipcover_views/by_name/map.js
|